提交 4a182a89 编写于 作者: M MissPenguin

split cpp inference

上级 0bf4fc37
...@@ -13,6 +13,7 @@ SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT") ...@@ -13,6 +13,7 @@ SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT")
set(DEMO_NAME "ocr_system") set(DEMO_NAME "ocr_system")
macro(safe_set_static_flag) macro(safe_set_static_flag)
foreach(flag_var foreach(flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
...@@ -37,8 +38,10 @@ endif() ...@@ -37,8 +38,10 @@ endif()
if (WIN32) if (WIN32)
include_directories("${PADDLE_LIB}/paddle/fluid/inference")
include_directories("${PADDLE_LIB}/paddle/include") include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib") link_directories("${PADDLE_LIB}/paddle/lib")
link_directories("${PADDLE_LIB}/paddle/fluid/inference")
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH) find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH)
else () else ()
...@@ -205,7 +208,7 @@ endif() ...@@ -205,7 +208,7 @@ endif()
set(DEPS ${DEPS} ${OpenCV_LIBS}) set(DEPS ${DEPS} ${OpenCV_LIBS})
AUX_SOURCE_DIRECTORY(./src SRCS) AUX_SOURCE_DIRECTORY(./src_system SRCS)
add_executable(${DEMO_NAME} ${SRCS}) add_executable(${DEMO_NAME} ${SRCS})
target_link_libraries(${DEMO_NAME} ${DEPS}) target_link_libraries(${DEMO_NAME} ${DEPS})
......
project(ocr_det CXX C)
option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON)
option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF)
option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON)
option(WITH_TENSORRT "Compile demo with TensorRT." OFF)
SET(PADDLE_LIB "" CACHE PATH "Location of libraries")
SET(OPENCV_DIR "" CACHE PATH "Location of libraries")
SET(CUDA_LIB "" CACHE PATH "Location of libraries")
SET(CUDNN_LIB "" CACHE PATH "Location of libraries")
SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT")
set(DEMO_NAME "ocr_det")
macro(safe_set_static_flag)
foreach(flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif(${flag_var} MATCHES "/MD")
endforeach(flag_var)
endmacro()
if (WITH_MKL)
ADD_DEFINITIONS(-DUSE_MKL)
endif()
if(NOT DEFINED PADDLE_LIB)
message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib")
endif()
if(NOT DEFINED OPENCV_DIR)
message(FATAL_ERROR "please set OPENCV_DIR with -DOPENCV_DIR=/path/opencv")
endif()
if (WIN32)
include_directories("${PADDLE_LIB}/paddle/fluid/inference")
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
link_directories("${PADDLE_LIB}/paddle/fluid/inference")
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH)
else ()
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/share/OpenCV NO_DEFAULT_PATH)
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
endif ()
include_directories(${OpenCV_INCLUDE_DIRS})
if (WIN32)
add_definitions("/DGOOGLE_GLOG_DLL_DECL=")
if(WITH_MKL)
set(FLAG_OPENMP "/openmp")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
if (WITH_STATIC_LIB)
safe_set_static_flag()
add_definitions(-DSTATIC_LIB)
endif()
message("cmake c debug flags " ${CMAKE_C_FLAGS_DEBUG})
message("cmake c release flags " ${CMAKE_C_FLAGS_RELEASE})
message("cmake cxx debug flags " ${CMAKE_CXX_FLAGS_DEBUG})
message("cmake cxx release flags " ${CMAKE_CXX_FLAGS_RELEASE})
else()
if(WITH_MKL)
set(FLAG_OPENMP "-fopenmp")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -o3 ${FLAG_OPENMP} -std=c++11")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
message("cmake cxx flags" ${CMAKE_CXX_FLAGS})
endif()
if (WITH_GPU)
if (NOT DEFINED CUDA_LIB OR ${CUDA_LIB} STREQUAL "")
message(FATAL_ERROR "please set CUDA_LIB with -DCUDA_LIB=/path/cuda-8.0/lib64")
endif()
if (NOT WIN32)
if (NOT DEFINED CUDNN_LIB)
message(FATAL_ERROR "please set CUDNN_LIB with -DCUDNN_LIB=/path/cudnn_v7.4/cuda/lib64")
endif()
endif(NOT WIN32)
endif()
include_directories("${PADDLE_LIB}/third_party/install/protobuf/include")
include_directories("${PADDLE_LIB}/third_party/install/glog/include")
include_directories("${PADDLE_LIB}/third_party/install/gflags/include")
include_directories("${PADDLE_LIB}/third_party/install/xxhash/include")
include_directories("${PADDLE_LIB}/third_party/install/zlib/include")
include_directories("${PADDLE_LIB}/third_party/boost")
include_directories("${PADDLE_LIB}/third_party/eigen3")
include_directories("${CMAKE_SOURCE_DIR}/")
if (NOT WIN32)
if (WITH_TENSORRT AND WITH_GPU)
include_directories("${TENSORRT_DIR}/include")
link_directories("${TENSORRT_DIR}/lib")
endif()
endif(NOT WIN32)
link_directories("${PADDLE_LIB}/third_party/install/zlib/lib")
link_directories("${PADDLE_LIB}/third_party/install/protobuf/lib")
link_directories("${PADDLE_LIB}/third_party/install/glog/lib")
link_directories("${PADDLE_LIB}/third_party/install/gflags/lib")
link_directories("${PADDLE_LIB}/third_party/install/xxhash/lib")
link_directories("${PADDLE_LIB}/paddle/lib")
if(WITH_MKL)
include_directories("${PADDLE_LIB}/third_party/install/mklml/include")
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.lib
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.lib)
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX}
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX})
execute_process(COMMAND cp -r ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} /usr/lib)
endif ()
set(MKLDNN_PATH "${PADDLE_LIB}/third_party/install/mkldnn")
if(EXISTS ${MKLDNN_PATH})
include_directories("${MKLDNN_PATH}/include")
if (WIN32)
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib)
else ()
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0)
endif ()
endif()
else()
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX})
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX})
endif ()
endif()
# Note: libpaddle_inference_api.so/a must put before libpaddle_inference.so/a
if(WITH_STATIC_LIB)
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
else()
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endif(WITH_STATIC_LIB)
if (NOT WIN32)
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags protobuf z xxhash
)
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
else()
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags_static libprotobuf xxhash)
set(DEPS ${DEPS} libcmt shlwapi)
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
endif(NOT WIN32)
if(WITH_GPU)
if(NOT WIN32)
if (WITH_TENSORRT)
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${CUDNN_LIB}/libcudnn${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDNN_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
endif()
if (NOT WIN32)
set(EXTERNAL_LIB "-ldl -lrt -lgomp -lz -lm -lpthread")
set(DEPS ${DEPS} ${EXTERNAL_LIB})
endif()
set(DEPS ${DEPS} ${OpenCV_LIBS})
AUX_SOURCE_DIRECTORY(./src_det SRCS)
add_executable(${DEMO_NAME} ${SRCS})
target_link_libraries(${DEMO_NAME} ${DEPS})
if (WIN32 AND WITH_MKL)
add_custom_command(TARGET ${DEMO_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./release/mkldnn.dll
)
endif()
project(ocr_rec CXX C)
option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON)
option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF)
option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON)
option(WITH_TENSORRT "Compile demo with TensorRT." OFF)
SET(PADDLE_LIB "" CACHE PATH "Location of libraries")
SET(OPENCV_DIR "" CACHE PATH "Location of libraries")
SET(CUDA_LIB "" CACHE PATH "Location of libraries")
SET(CUDNN_LIB "" CACHE PATH "Location of libraries")
SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT")
set(DEMO_NAME "ocr_rec")
macro(safe_set_static_flag)
foreach(flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif(${flag_var} MATCHES "/MD")
endforeach(flag_var)
endmacro()
if (WITH_MKL)
ADD_DEFINITIONS(-DUSE_MKL)
endif()
if(NOT DEFINED PADDLE_LIB)
message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib")
endif()
if(NOT DEFINED OPENCV_DIR)
message(FATAL_ERROR "please set OPENCV_DIR with -DOPENCV_DIR=/path/opencv")
endif()
if (WIN32)
include_directories("${PADDLE_LIB}/paddle/fluid/inference")
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
link_directories("${PADDLE_LIB}/paddle/fluid/inference")
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH)
else ()
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/share/OpenCV NO_DEFAULT_PATH)
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
endif ()
include_directories(${OpenCV_INCLUDE_DIRS})
if (WIN32)
add_definitions("/DGOOGLE_GLOG_DLL_DECL=")
if(WITH_MKL)
set(FLAG_OPENMP "/openmp")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
if (WITH_STATIC_LIB)
safe_set_static_flag()
add_definitions(-DSTATIC_LIB)
endif()
message("cmake c debug flags " ${CMAKE_C_FLAGS_DEBUG})
message("cmake c release flags " ${CMAKE_C_FLAGS_RELEASE})
message("cmake cxx debug flags " ${CMAKE_CXX_FLAGS_DEBUG})
message("cmake cxx release flags " ${CMAKE_CXX_FLAGS_RELEASE})
else()
if(WITH_MKL)
set(FLAG_OPENMP "-fopenmp")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -o3 ${FLAG_OPENMP} -std=c++11")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
message("cmake cxx flags" ${CMAKE_CXX_FLAGS})
endif()
if (WITH_GPU)
if (NOT DEFINED CUDA_LIB OR ${CUDA_LIB} STREQUAL "")
message(FATAL_ERROR "please set CUDA_LIB with -DCUDA_LIB=/path/cuda-8.0/lib64")
endif()
if (NOT WIN32)
if (NOT DEFINED CUDNN_LIB)
message(FATAL_ERROR "please set CUDNN_LIB with -DCUDNN_LIB=/path/cudnn_v7.4/cuda/lib64")
endif()
endif(NOT WIN32)
endif()
include_directories("${PADDLE_LIB}/third_party/install/protobuf/include")
include_directories("${PADDLE_LIB}/third_party/install/glog/include")
include_directories("${PADDLE_LIB}/third_party/install/gflags/include")
include_directories("${PADDLE_LIB}/third_party/install/xxhash/include")
include_directories("${PADDLE_LIB}/third_party/install/zlib/include")
include_directories("${PADDLE_LIB}/third_party/boost")
include_directories("${PADDLE_LIB}/third_party/eigen3")
include_directories("${CMAKE_SOURCE_DIR}/")
if (NOT WIN32)
if (WITH_TENSORRT AND WITH_GPU)
include_directories("${TENSORRT_DIR}/include")
link_directories("${TENSORRT_DIR}/lib")
endif()
endif(NOT WIN32)
link_directories("${PADDLE_LIB}/third_party/install/zlib/lib")
link_directories("${PADDLE_LIB}/third_party/install/protobuf/lib")
link_directories("${PADDLE_LIB}/third_party/install/glog/lib")
link_directories("${PADDLE_LIB}/third_party/install/gflags/lib")
link_directories("${PADDLE_LIB}/third_party/install/xxhash/lib")
link_directories("${PADDLE_LIB}/paddle/lib")
if(WITH_MKL)
include_directories("${PADDLE_LIB}/third_party/install/mklml/include")
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.lib
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.lib)
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX}
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX})
execute_process(COMMAND cp -r ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} /usr/lib)
endif ()
set(MKLDNN_PATH "${PADDLE_LIB}/third_party/install/mkldnn")
if(EXISTS ${MKLDNN_PATH})
include_directories("${MKLDNN_PATH}/include")
if (WIN32)
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib)
else ()
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0)
endif ()
endif()
else()
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX})
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX})
endif ()
endif()
# Note: libpaddle_inference_api.so/a must put before libpaddle_inference.so/a
if(WITH_STATIC_LIB)
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
else()
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endif(WITH_STATIC_LIB)
if (NOT WIN32)
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags protobuf z xxhash
)
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
else()
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags_static libprotobuf xxhash)
set(DEPS ${DEPS} libcmt shlwapi)
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
endif(NOT WIN32)
if(WITH_GPU)
if(NOT WIN32)
if (WITH_TENSORRT)
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${CUDNN_LIB}/libcudnn${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDNN_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
endif()
if (NOT WIN32)
set(EXTERNAL_LIB "-ldl -lrt -lgomp -lz -lm -lpthread")
set(DEPS ${DEPS} ${EXTERNAL_LIB})
endif()
set(DEPS ${DEPS} ${OpenCV_LIBS})
AUX_SOURCE_DIRECTORY(./src_rec SRCS)
add_executable(${DEMO_NAME} ${SRCS})
target_link_libraries(${DEMO_NAME} ${DEPS})
if (WIN32 AND WITH_MKL)
add_custom_command(TARGET ${DEMO_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./release/mkldnn.dll
)
endif()
project(ocr_system CXX C)
option(WITH_MKL "Compile demo with MKL/OpenBlas support, default use MKL." ON)
option(WITH_GPU "Compile demo with GPU/CPU, default use CPU." OFF)
option(WITH_STATIC_LIB "Compile demo with static/shared library, default use static." ON)
option(WITH_TENSORRT "Compile demo with TensorRT." OFF)
SET(PADDLE_LIB "" CACHE PATH "Location of libraries")
SET(OPENCV_DIR "" CACHE PATH "Location of libraries")
SET(CUDA_LIB "" CACHE PATH "Location of libraries")
SET(CUDNN_LIB "" CACHE PATH "Location of libraries")
SET(TENSORRT_DIR "" CACHE PATH "Compile demo with TensorRT")
set(DEMO_NAME "ocr_system")
macro(safe_set_static_flag)
foreach(flag_var
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
if(${flag_var} MATCHES "/MD")
string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
endif(${flag_var} MATCHES "/MD")
endforeach(flag_var)
endmacro()
if (WITH_MKL)
ADD_DEFINITIONS(-DUSE_MKL)
endif()
if(NOT DEFINED PADDLE_LIB)
message(FATAL_ERROR "please set PADDLE_LIB with -DPADDLE_LIB=/path/paddle/lib")
endif()
if(NOT DEFINED OPENCV_DIR)
message(FATAL_ERROR "please set OPENCV_DIR with -DOPENCV_DIR=/path/opencv")
endif()
if (WIN32)
include_directories("${PADDLE_LIB}/paddle/fluid/inference")
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
link_directories("${PADDLE_LIB}/paddle/fluid/inference")
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/build/ NO_DEFAULT_PATH)
else ()
find_package(OpenCV REQUIRED PATHS ${OPENCV_DIR}/share/OpenCV NO_DEFAULT_PATH)
include_directories("${PADDLE_LIB}/paddle/include")
link_directories("${PADDLE_LIB}/paddle/lib")
endif ()
include_directories(${OpenCV_INCLUDE_DIRS})
if (WIN32)
add_definitions("/DGOOGLE_GLOG_DLL_DECL=")
if(WITH_MKL)
set(FLAG_OPENMP "/openmp")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /bigobj /MTd ${FLAG_OPENMP}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /bigobj /MT ${FLAG_OPENMP}")
if (WITH_STATIC_LIB)
safe_set_static_flag()
add_definitions(-DSTATIC_LIB)
endif()
message("cmake c debug flags " ${CMAKE_C_FLAGS_DEBUG})
message("cmake c release flags " ${CMAKE_C_FLAGS_RELEASE})
message("cmake cxx debug flags " ${CMAKE_CXX_FLAGS_DEBUG})
message("cmake cxx release flags " ${CMAKE_CXX_FLAGS_RELEASE})
else()
if(WITH_MKL)
set(FLAG_OPENMP "-fopenmp")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -o3 ${FLAG_OPENMP} -std=c++11")
set(CMAKE_STATIC_LIBRARY_PREFIX "")
message("cmake cxx flags" ${CMAKE_CXX_FLAGS})
endif()
if (WITH_GPU)
if (NOT DEFINED CUDA_LIB OR ${CUDA_LIB} STREQUAL "")
message(FATAL_ERROR "please set CUDA_LIB with -DCUDA_LIB=/path/cuda-8.0/lib64")
endif()
if (NOT WIN32)
if (NOT DEFINED CUDNN_LIB)
message(FATAL_ERROR "please set CUDNN_LIB with -DCUDNN_LIB=/path/cudnn_v7.4/cuda/lib64")
endif()
endif(NOT WIN32)
endif()
include_directories("${PADDLE_LIB}/third_party/install/protobuf/include")
include_directories("${PADDLE_LIB}/third_party/install/glog/include")
include_directories("${PADDLE_LIB}/third_party/install/gflags/include")
include_directories("${PADDLE_LIB}/third_party/install/xxhash/include")
include_directories("${PADDLE_LIB}/third_party/install/zlib/include")
include_directories("${PADDLE_LIB}/third_party/boost")
include_directories("${PADDLE_LIB}/third_party/eigen3")
include_directories("${CMAKE_SOURCE_DIR}/")
if (NOT WIN32)
if (WITH_TENSORRT AND WITH_GPU)
include_directories("${TENSORRT_DIR}/include")
link_directories("${TENSORRT_DIR}/lib")
endif()
endif(NOT WIN32)
link_directories("${PADDLE_LIB}/third_party/install/zlib/lib")
link_directories("${PADDLE_LIB}/third_party/install/protobuf/lib")
link_directories("${PADDLE_LIB}/third_party/install/glog/lib")
link_directories("${PADDLE_LIB}/third_party/install/gflags/lib")
link_directories("${PADDLE_LIB}/third_party/install/xxhash/lib")
link_directories("${PADDLE_LIB}/paddle/lib")
if(WITH_MKL)
include_directories("${PADDLE_LIB}/third_party/install/mklml/include")
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.lib
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.lib)
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX}
${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5${CMAKE_SHARED_LIBRARY_SUFFIX})
execute_process(COMMAND cp -r ${PADDLE_LIB}/third_party/install/mklml/lib/libmklml_intel${CMAKE_SHARED_LIBRARY_SUFFIX} /usr/lib)
endif ()
set(MKLDNN_PATH "${PADDLE_LIB}/third_party/install/mkldnn")
if(EXISTS ${MKLDNN_PATH})
include_directories("${MKLDNN_PATH}/include")
if (WIN32)
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/mkldnn.lib)
else ()
set(MKLDNN_LIB ${MKLDNN_PATH}/lib/libmkldnn.so.0)
endif ()
endif()
else()
if (WIN32)
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX})
else ()
set(MATH_LIB ${PADDLE_LIB}/third_party/install/openblas/lib/libopenblas${CMAKE_STATIC_LIBRARY_SUFFIX})
endif ()
endif()
# Note: libpaddle_inference_api.so/a must put before libpaddle_inference.so/a
if(WITH_STATIC_LIB)
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
else()
if(WIN32)
set(DEPS
${PADDLE_LIB}/paddle/lib/paddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS
${PADDLE_LIB}/paddle/lib/libpaddle_inference${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
endif(WITH_STATIC_LIB)
if (NOT WIN32)
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags protobuf z xxhash
)
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
else()
set(DEPS ${DEPS}
${MATH_LIB} ${MKLDNN_LIB}
glog gflags_static libprotobuf xxhash)
set(DEPS ${DEPS} libcmt shlwapi)
if (EXISTS "${PADDLE_LIB}/third_party/install/snappy/lib")
set(DEPS ${DEPS} snappy)
endif()
if(EXISTS "${PADDLE_LIB}/third_party/install/snappystream/lib")
set(DEPS ${DEPS} snappystream)
endif()
endif(NOT WIN32)
if(WITH_GPU)
if(NOT WIN32)
if (WITH_TENSORRT)
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${TENSORRT_DIR}/lib/libnvinfer_plugin${CMAKE_SHARED_LIBRARY_SUFFIX})
endif()
set(DEPS ${DEPS} ${CUDA_LIB}/libcudart${CMAKE_SHARED_LIBRARY_SUFFIX})
set(DEPS ${DEPS} ${CUDNN_LIB}/libcudnn${CMAKE_SHARED_LIBRARY_SUFFIX})
else()
set(DEPS ${DEPS} ${CUDA_LIB}/cudart${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDA_LIB}/cublas${CMAKE_STATIC_LIBRARY_SUFFIX} )
set(DEPS ${DEPS} ${CUDNN_LIB}/cudnn${CMAKE_STATIC_LIBRARY_SUFFIX})
endif()
endif()
if (NOT WIN32)
set(EXTERNAL_LIB "-ldl -lrt -lgomp -lz -lm -lpthread")
set(DEPS ${DEPS} ${EXTERNAL_LIB})
endif()
set(DEPS ${DEPS} ${OpenCV_LIBS})
AUX_SOURCE_DIRECTORY(./src_system SRCS)
add_executable(${DEMO_NAME} ${SRCS})
target_link_libraries(${DEMO_NAME} ${DEPS})
if (WIN32 AND WITH_MKL)
add_custom_command(TARGET ${DEMO_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./mkldnn.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/mklml.dll ./release/mklml.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mklml/lib/libiomp5md.dll ./release/libiomp5md.dll
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PADDLE_LIB}/third_party/install/mkldnn/lib/mkldnn.dll ./release/mkldnn.dll
)
endif()
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include <iomanip>
#include <iostream>
#include <map>
#include <ostream>
#include <string>
#include <vector>
#include "include/utility.h"
namespace PaddleOCR {
class OCRConfig {
public:
explicit OCRConfig(const std::string &config_file) {
config_map_ = LoadConfig(config_file);
this->use_gpu = bool(stoi(config_map_["use_gpu"]));
this->gpu_id = stoi(config_map_["gpu_id"]);
this->gpu_mem = stoi(config_map_["gpu_mem"]);
this->cpu_math_library_num_threads =
stoi(config_map_["cpu_math_library_num_threads"]);
this->use_mkldnn = bool(stoi(config_map_["use_mkldnn"]));
this->max_side_len = stoi(config_map_["max_side_len"]);
this->det_db_thresh = stod(config_map_["det_db_thresh"]);
this->det_db_box_thresh = stod(config_map_["det_db_box_thresh"]);
this->det_db_unclip_ratio = stod(config_map_["det_db_unclip_ratio"]);
this->use_polygon_score = bool(stoi(config_map_["use_polygon_score"]));
this->det_model_dir.assign(config_map_["det_model_dir"]);
this->rec_model_dir.assign(config_map_["rec_model_dir"]);
this->char_list_file.assign(config_map_["char_list_file"]);
this->use_angle_cls = bool(stoi(config_map_["use_angle_cls"]));
this->cls_model_dir.assign(config_map_["cls_model_dir"]);
this->cls_thresh = stod(config_map_["cls_thresh"]);
this->visualize = bool(stoi(config_map_["visualize"]));
this->use_tensorrt = bool(stoi(config_map_["use_tensorrt"]));
this->use_fp16 = bool(stod(config_map_["use_fp16"]));
}
bool use_gpu = false;
int gpu_id = 0;
int gpu_mem = 4000;
int cpu_math_library_num_threads = 1;
bool use_mkldnn = false;
int max_side_len = 960;
double det_db_thresh = 0.3;
double det_db_box_thresh = 0.5;
double det_db_unclip_ratio = 2.0;
bool use_polygon_score = false;
std::string det_model_dir;
std::string rec_model_dir;
bool use_angle_cls;
std::string char_list_file;
std::string cls_model_dir;
double cls_thresh;
bool visualize = true;
bool use_tensorrt = false;
bool use_fp16 = false;
void PrintConfigInfo();
private:
// Load configuration
std::map<std::string, std::string> LoadConfig(const std::string &config_file);
std::vector<std::string> split(const std::string &str,
const std::string &delim);
std::map<std::string, std::string> config_map_;
};
} // namespace PaddleOCR
...@@ -38,15 +38,20 @@ namespace PaddleOCR { ...@@ -38,15 +38,20 @@ namespace PaddleOCR {
class DBDetector { class DBDetector {
public: public:
explicit DBDetector(const std::string &model_dir, const bool &use_gpu, explicit DBDetector(const std::string &model_dir,
const int &gpu_id, const int &gpu_mem, const bool &use_gpu=false,
const int &cpu_math_library_num_threads, const int &gpu_id=0,
const bool &use_mkldnn, const int &max_side_len, const int &gpu_mem=4000,
const double &det_db_thresh, const int &cpu_math_library_num_threads=1,
const double &det_db_box_thresh, const bool &use_mkldnn=false,
const double &det_db_unclip_ratio, const int &max_side_len=960,
const bool &use_polygon_score, const bool &visualize, const double &det_db_thresh=0.3,
const bool &use_tensorrt, const bool &use_fp16) { const double &det_db_box_thresh=0.5,
const double &det_db_unclip_ratio=2.0,
const bool &use_polygon_score=false,
const bool &visualize=false,
const bool &use_tensorrt=false,
const bool &use_fp16=false) {
this->use_gpu_ = use_gpu; this->use_gpu_ = use_gpu;
this->gpu_id_ = gpu_id; this->gpu_id_ = gpu_id;
this->gpu_mem_ = gpu_mem; this->gpu_mem_ = gpu_mem;
......
...@@ -62,8 +62,7 @@ public: ...@@ -62,8 +62,7 @@ public:
// Load Paddle inference model // Load Paddle inference model
void LoadModel(const std::string &model_dir); void LoadModel(const std::string &model_dir);
void Run(std::vector<std::vector<std::vector<int>>> boxes, cv::Mat &img, void Run(cv::Mat &img);
Classifier *cls);
private: private:
std::shared_ptr<Predictor> predictor_; std::shared_ptr<Predictor> predictor_;
......
...@@ -156,80 +156,87 @@ inference/ ...@@ -156,80 +156,87 @@ inference/
```shell ```shell
sh tools/build.sh sh tools/build.sh MODE(['det', 'rec', 'system'])
```
其中`MODE`表示demo功能,支持3种参数,**按需选择一种参数即可**,相应解释如下:
```shell
sh tools/build.sh det # 编译检测demo
sh tools/build.sh rec # 编译识别demo
sh tools/build.sh system # 编译串联demo(包括方向分类器)
``` ```
具体地,`tools/build.sh`中内容如下。 此外,需要修改`tools/build.sh`中环境路径,相关内容如下:
```shell ```shell
OPENCV_DIR=your_opencv_dir OPENCV_DIR=your_opencv_dir
LIB_DIR=your_paddle_inference_dir LIB_DIR=your_paddle_inference_dir
CUDA_LIB_DIR=your_cuda_lib_dir CUDA_LIB_DIR=your_cuda_lib_dir
CUDNN_LIB_DIR=/your_cudnn_lib_dir CUDNN_LIB_DIR=/your_cudnn_lib_dir
BUILD_DIR=build
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
cmake .. \
-DPADDLE_LIB=${LIB_DIR} \
-DWITH_MKL=ON \
-DDEMO_NAME=ocr_system \
-DWITH_GPU=OFF \
-DWITH_STATIC_LIB=OFF \
-DUSE_TENSORRT=OFF \
-DOPENCV_DIR=${OPENCV_DIR} \
-DCUDNN_LIB=${CUDNN_LIB_DIR} \
-DCUDA_LIB=${CUDA_LIB_DIR} \
make -j
``` ```
`OPENCV_DIR`为opencv编译安装的地址;`LIB_DIR`为下载(`paddle_inference`文件夹)或者编译生成的Paddle预测库地址(`build/paddle_inference_install_dir`文件夹);`CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64``CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`**注意**:以上路径都写绝对路径,不要写相对路径。 其中,`OPENCV_DIR`为opencv编译安装的地址;`LIB_DIR`为下载(`paddle_inference`文件夹)或者编译生成的Paddle预测库地址(`build/paddle_inference_install_dir`文件夹);`CUDA_LIB_DIR`为cuda库文件地址,在docker中为`/usr/local/cuda/lib64``CUDNN_LIB_DIR`为cudnn库文件地址,在docker中为`/usr/lib/x86_64-linux-gnu/`**注意:以上路径都写绝对路径,不要写相对路径。**
* 编译完成之后,会在`build`文件夹下生成一个名为`ocr_system`的可执行文件。 * 编译完成之后,会在`build`文件夹下生成一个名为`ocr_det``ocr_rec``ocr_system`的可执行文件。
### 运行demo ### 运行demo
* 执行以下命令,完成对一幅图像的OCR识别与检测 直接运行编译好的可执行文件:```./build/ocr_***```,可获得参数信息提示
##### 1. 检测demo运行方式:
```shell ```shell
sh tools/run.sh ./build/ocr_det
``` --det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--image_dir=../../doc/imgs/12.jpg
* 若需要使用方向分类器,则需要将`tools/config.txt`中的`use_angle_cls`参数修改为1,表示开启方向分类器的预测。
* 更多地,tools/config.txt中的参数及解释如下。
``` ```
use_gpu 0 # 是否使用GPU,1表示使用,0表示不使用 ##### 2. 识别demo运行方式:
gpu_id 0 # GPU id,使用GPU时有效 ```shell
gpu_mem 4000 # 申请的GPU内存 ./build/ocr_rec
cpu_math_library_num_threads 10 # CPU预测时的线程数,在机器核数充足的情况下,该值越大,预测速度越快 --rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
use_mkldnn 1 # 是否使用mkldnn库 --image_dir=../../doc/imgs_words/ch/
# det config
max_side_len 960 # 输入图像长宽大于960时,等比例缩放图像,使得图像最长边为960
det_db_thresh 0.3 # 用于过滤DB预测的二值化图像,设置为0.-0.3对结果影响不明显
det_db_box_thresh 0.5 # DB后处理过滤box的阈值,如果检测存在漏框情况,可酌情减小
det_db_unclip_ratio 1.6 # 表示文本框的紧致程度,越小则文本框更靠近文本
use_polygon_score 1 # 是否使用多边形框计算bbox score,0表示使用矩形框计算。矩形框计算速度更快,多边形框对弯曲文本区域计算更准确。
det_model_dir ./inference/det_db # 检测模型inference model地址
# cls config
use_angle_cls 0 # 是否使用方向分类器,0表示不使用,1表示使用
cls_model_dir ./inference/cls # 方向分类器inference model地址
cls_thresh 0.9 # 方向分类器的得分阈值
# rec config
rec_model_dir ./inference/rec_crnn # 识别模型inference model地址
char_list_file ../../ppocr/utils/ppocr_keys_v1.txt # 字典文件
# show the detection results
visualize 1 # 是否对结果进行可视化,为1时,会在当前文件夹下保存文件名为`ocr_vis.png`的预测结果。
``` ```
##### 3. 串联demo运行方式:
* PaddleOCR也支持多语言的预测,更多支持的语言和模型可以参考[识别文档](../../doc/doc_ch/recognition.md)中的多语言字典与模型部分,如果希望进行多语言预测,只需将修改`tools/config.txt`中的`char_list_file`(字典文件路径)以及`rec_model_dir`(inference模型路径)字段即可。 ```shell
# 不使用方向分类器
./build/ocr_rec
--det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
--image_dir=../../doc/imgs/12.jpg
# 使用方向分类器
./build/ocr_rec
--det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--use_angle_cls=true
--cls_model_dir=inference/ch_ppocr_mobile_v2.0_cls_infer
--rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
--image_dir=../../doc/imgs/12.jpg
```
更多参数如下:
|参数名称|类型|默认参数|意义|
| --- | --- | --- | --- |
|use_gpu|bool|false|是否使用GPU|
|gpu_id|int|0|GPU id,使用GPU时有效|
|gpu_mem|int|4000|申请的GPU内存|
|cpu_math_library_num_threads|int|10|CPU预测时的线程数,在机器核数充足的情况下,该值越大,预测速度越快|
|use_mkldnn|bool|true|是否使用mkldnn库|
|**检测模型相关**|
|det_model_dir|string|-|检测模型inference model地址|
|max_side_len|int|960|输入图像长宽大于960时,等比例缩放图像,使得图像最长边为960|
|det_db_thresh|float|0.3|用于过滤DB预测的二值化图像,设置为0.-0.3对结果影响不明显|
|det_db_box_thresh|float|0.5|DB后处理过滤box的阈值,如果检测存在漏框情况,可酌情减小|
|det_db_unclip_ratio|float|1.6|表示文本框的紧致程度,越小则文本框更靠近文本|
|use_polygon_score|bool|false|是否使用多边形框计算bbox score,false表示使用矩形框计算。矩形框计算速度更快,多边形框对弯曲文本区域计算更准确。|
|visualize|bool|true|是否对结果进行可视化,为1时,会在当前文件夹下保存文件名为`ocr_vis.png`的预测结果。|
|**方向分类器相关**|
|use_angle_cls|bool|false|是否使用方向分类器|
|cls_model_dir|string|-|方向分类器inference model地址|
|cls_thresh|float|0.9|方向分类器的得分阈值|
|**识别模型相关**|
|rec_model_dir|string|-|识别模型inference model地址|
|char_list_file|string|../../ppocr/utils/ppocr_keys_v1.txt|字典文件|
* PaddleOCR也支持多语言的预测,更多支持的语言和模型可以参考[识别文档](../../doc/doc_ch/recognition.md)中的多语言字典与模型部分,如果希望进行多语言预测,只需将修改`char_list_file`(字典文件路径)以及`rec_model_dir`(inference模型路径)字段即可。
最终屏幕上会输出检测结果如下。 最终屏幕上会输出检测结果如下。
......
...@@ -159,33 +159,22 @@ inference/ ...@@ -159,33 +159,22 @@ inference/
* The compilation commands are as follows. The addresses of Paddle C++ inference library, opencv and other Dependencies need to be replaced with the actual addresses on your own machines. * The compilation commands are as follows. The addresses of Paddle C++ inference library, opencv and other Dependencies need to be replaced with the actual addresses on your own machines.
```shell ```shell
sh tools/build.sh sh tools/build.sh MODE(['det', 'rec', 'system'])
```
Here, `MODE` should be one of ['det', 'rec', 'system']. Choose one as your need. The explanation is as follows:
```shell
sh tools/build.sh det # build demo for detection only
sh tools/build.sh rec # build demo for recogniton only
sh tools/build.sh system # build demo for a system (including the text direction classifier)
``` ```
Specifically, the content in `tools/build.sh` is as follows. Specifically, you should modify the paths in `tools/build.sh`. The related content is as follows.
```shell ```shell
OPENCV_DIR=your_opencv_dir OPENCV_DIR=your_opencv_dir
LIB_DIR=your_paddle_inference_dir LIB_DIR=your_paddle_inference_dir
CUDA_LIB_DIR=your_cuda_lib_dir CUDA_LIB_DIR=your_cuda_lib_dir
CUDNN_LIB_DIR=your_cudnn_lib_dir CUDNN_LIB_DIR=your_cudnn_lib_dir
BUILD_DIR=build
rm -rf ${BUILD_DIR}
mkdir ${BUILD_DIR}
cd ${BUILD_DIR}
cmake .. \
-DPADDLE_LIB=${LIB_DIR} \
-DWITH_MKL=ON \
-DDEMO_NAME=ocr_system \
-DWITH_GPU=OFF \
-DWITH_STATIC_LIB=OFF \
-DUSE_TENSORRT=OFF \
-DOPENCV_DIR=${OPENCV_DIR} \
-DCUDNN_LIB=${CUDNN_LIB_DIR} \
-DCUDA_LIB=${CUDA_LIB_DIR} \
make -j
``` ```
`OPENCV_DIR` is the opencv installation path; `LIB_DIR` is the download (`paddle_inference` folder) `OPENCV_DIR` is the opencv installation path; `LIB_DIR` is the download (`paddle_inference` folder)
...@@ -193,48 +182,65 @@ or the generated Paddle inference library path (`build/paddle_inference_install_ ...@@ -193,48 +182,65 @@ or the generated Paddle inference library path (`build/paddle_inference_install_
`CUDA_LIB_DIR` is the cuda library file path, in docker; it is `/usr/local/cuda/lib64`; `CUDNN_LIB_DIR` is the cudnn library file path, in docker it is `/usr/lib/x86_64-linux-gnu/`. `CUDA_LIB_DIR` is the cuda library file path, in docker; it is `/usr/local/cuda/lib64`; `CUDNN_LIB_DIR` is the cudnn library file path, in docker it is `/usr/lib/x86_64-linux-gnu/`.
* After the compilation is completed, an executable file named `ocr_system` will be generated in the `build` folder. * After the compilation is completed, an executable file named `ocr_det` or `ocr_rec` or `ocr_system` will be generated in the `build` folder.
### Run the demo ### Run the demo
* Execute the following command to complete the OCR recognition and detection of an image. * Execute the built executable file by ```./build/ocr_***```. You'll get some command line parameter information.
##### 1. run ocr_det demo:
```shell ```shell
sh tools/run.sh ./build/ocr_det
--det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--image_dir=../../doc/imgs/12.jpg
``` ```
##### 2. run ocr_rec demo:
* If you want to orientation classifier to correct the detected boxes, you can set `use_angle_cls` in the file `tools/config.txt` as 1 to enable the function. ```shell
* What's more, Parameters and their meanings in `tools/config.txt` are as follows. ./build/ocr_rec
--rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
--image_dir=../../doc/imgs_words/ch/
```
use_gpu 0 # Whether to use GPU, 0 means not to use, 1 means to use
gpu_id 0 # GPU id when use_gpu is 1
gpu_mem 4000 # GPU memory requested
cpu_math_library_num_threads 10 # Number of threads when using CPU inference. When machine cores is enough, the large the value, the faster the inference speed
use_mkldnn 1 # Whether to use mkdlnn library
max_side_len 960 # Limit the maximum image height and width to 960
det_db_thresh 0.3 # Used to filter the binarized image of DB prediction, setting 0.-0.3 has no obvious effect on the result
det_db_box_thresh 0.5 # DDB post-processing filter box threshold, if there is a missing box detected, it can be reduced as appropriate
det_db_unclip_ratio 1.6 # Indicates the compactness of the text box, the smaller the value, the closer the text box to the text
use_polygon_score 1 # Whether to use polygon box to calculate bbox score, 0 means to use rectangle box to calculate. Use rectangular box to calculate faster, and polygonal box more accurate for curved text area.
det_model_dir ./inference/det_db # Address of detection inference model
# cls config
use_angle_cls 0 # Whether to use the direction classifier, 0 means not to use, 1 means to use
cls_model_dir ./inference/cls # Address of direction classifier inference model
cls_thresh 0.9 # Score threshold of the direction classifier
# rec config
rec_model_dir ./inference/rec_crnn # Address of recognition inference model
char_list_file ../../ppocr/utils/ppocr_keys_v1.txt # dictionary file
# show the detection results
visualize 1 # Whether to visualize the results,when it is set as 1, The prediction result will be save in the image file `./ocr_vis.png`.
``` ```
##### 3. run ocr_system demo:
* Multi-language inference is also supported in PaddleOCR, you can refer to [recognition tutorial](../../doc/doc_en/recognition_en.md) for more supported languages and models in PaddleOCR. Specifically, if you want to infer using multi-language models, you just need to modify values of `char_list_file` and `rec_model_dir` in file `tools/config.txt`. ```shell
# without text direction classifier
./build/ocr_rec
--det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
--image_dir=../../doc/imgs/12.jpg
# with text direction classifier
./build/ocr_rec
--det_model_dir=inference/ch_ppocr_mobile_v2.0_det_infer
--use_angle_cls=true
--cls_model_dir=inference/ch_ppocr_mobile_v2.0_cls_infer
--rec_model_dir=inference/ch_ppocr_mobile_v2.0_rec_infer
--image_dir=../../doc/imgs/12.jpg
```
More parameters are as follows,
|parameter|data type|default|meaning|
| --- | --- | --- | --- |
|use_gpu|bool|false|Whether to use GPU|
|gpu_id|int|0|GPU id when use_gpu is true|
|gpu_mem|int|4000|GPU memory requested|
|cpu_math_library_num_threads|int|10|Number of threads when using CPU inference. When machine cores is enough, the large the value, the faster the inference speed|
|use_mkldnn|bool|true|Whether to use mkdlnn library|
|**detection related**|
|det_model_dir|string|-|Address of detection inference model|
|max_side_len|int|960|Limit the maximum image height and width to 960|
|det_db_thresh|float|0.3|Used to filter the binarized image of DB prediction, setting 0.-0.3 has no obvious effect on the result|
|det_db_box_thresh|float|0.5|DB post-processing filter box threshold, if there is a missing box detected, it can be reduced as appropriate|
|det_db_unclip_ratio|float|1.6|Indicates the compactness of the text box, the smaller the value, the closer the text box to the text|
|use_polygon_score|bool|false|Whether to use polygon box to calculate bbox score, false means to use rectangle box to calculate. Use rectangular box to calculate faster, and polygonal box more accurate for curved text area.|
|visualize|bool|true|Whether to visualize the results,when it is set as true, The prediction result will be save in the image file `./ocr_vis.png`.|
|**recogniton related**|
|use_angle_cls|bool|false|Whether to use the direction classifier|
|cls_model_dir|string|-|Address of direction classifier inference model|
|cls_thresh|float|0.9|Score threshold of the direction classifier|
|**classification related**|
|rec_model_dir|string|-|Address of recognition inference model|
|char_list_file|string|../../ppocr/utils/ppocr_keys_v1.txt|dictionary file|
* Multi-language inference is also supported in PaddleOCR, you can refer to [recognition tutorial](../../doc/doc_en/recognition_en.md) for more supported languages and models in PaddleOCR. Specifically, if you want to infer using multi-language models, you just need to modify values of `char_list_file` and `rec_model_dir`.
The detection results will be shown on the screen, which is as follows. The detection results will be shown on the screen, which is as follows.
......
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <include/config.h>
namespace PaddleOCR {
std::vector<std::string> OCRConfig::split(const std::string &str,
const std::string &delim) {
std::vector<std::string> res;
if ("" == str)
return res;
int strlen = str.length() + 1;
char *strs = new char[strlen];
std::strcpy(strs, str.c_str());
int delimlen = delim.length() + 1;
char *d = new char[delimlen];
std::strcpy(d, delim.c_str());
char *p = std::strtok(strs, d);
while (p) {
std::string s = p;
res.push_back(s);
p = std::strtok(NULL, d);
}
delete[] strs;
delete[] d;
return res;
}
std::map<std::string, std::string>
OCRConfig::LoadConfig(const std::string &config_path) {
auto config = Utility::ReadDict(config_path);
std::map<std::string, std::string> dict;
for (int i = 0; i < config.size(); i++) {
// pass for empty line or comment
if (config[i].size() <= 1 || config[i][0] == '#') {
continue;
}
std::vector<std::string> res = split(config[i], " ");
dict[res[0]] = res[1];
}
return dict;
}
void OCRConfig::PrintConfigInfo() {
std::cout << "=======Paddle OCR inference config======" << std::endl;
for (auto iter = config_map_.begin(); iter != config_map_.end(); iter++) {
std::cout << iter->first << " : " << iter->second << std::endl;
}
std::cout << "=======End of Paddle OCR inference config======" << std::endl;
}
} // namespace PaddleOCR
/*******************************************************************************
* *
* 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 <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <ostream>
#include <stdexcept>
#include <vector>
#include "include/clipper.h"
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<cInt>(val - 0.5);
else
return static_cast<cInt>(val + 0.5);
}
//------------------------------------------------------------------------------
inline cInt Abs(cInt val) { return val < 0 ? -val : val; }
//------------------------------------------------------------------------------
// PolyTree methods ...
//------------------------------------------------------------------------------
void PolyTree::Clear() {
for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
delete AllNodes[i];
AllNodes.resize(0);
Childs.resize(0);
}
//------------------------------------------------------------------------------
PolyNode *PolyTree::GetFirst() const {
if (!Childs.empty())
return Childs[0];
else
return 0;
}
//------------------------------------------------------------------------------
int PolyTree::Total() const {
int result = (int)AllNodes.size();
// with negative offsets, ignore the hidden outer polygon ...
if (result > 0 && Childs[0] != AllNodes[0])
result--;
return result;
}
//------------------------------------------------------------------------------
// PolyNode methods ...
//------------------------------------------------------------------------------
PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
//------------------------------------------------------------------------------
int PolyNode::ChildCount() const { return (int)Childs.size(); }
//------------------------------------------------------------------------------
void PolyNode::AddChild(PolyNode &child) {
unsigned cnt = (unsigned)Childs.size();
Childs.push_back(&child);
child.Parent = this;
child.Index = cnt;
}
//------------------------------------------------------------------------------
PolyNode *PolyNode::GetNext() const {
if (!Childs.empty())
return Childs[0];
else
return GetNextSiblingUp();
}
//------------------------------------------------------------------------------
PolyNode *PolyNode::GetNextSiblingUp() const {
if (!Parent) // protects against PolyTree.GetNextSiblingUp()
return 0;
else if (Index == Parent->Childs.size() - 1)
return Parent->GetNextSiblingUp();
else
return Parent->Childs[Index + 1];
}
//------------------------------------------------------------------------------
bool PolyNode::IsHole() const {
bool result = true;
PolyNode *node = Parent;
while (node) {
result = !result;
node = node->Parent;
}
return result;
}
//------------------------------------------------------------------------------
bool PolyNode::IsOpen() const { return m_IsOpen; }
//------------------------------------------------------------------------------
#ifndef use_int32
//------------------------------------------------------------------------------
// Int128 class (enables safe math on signed 64bit integers)
// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
// Int128 val2((long64)9223372036854775807);
// Int128 val3 = val1 * val2;
// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
//------------------------------------------------------------------------------
class Int128 {
public:
ulong64 lo;
long64 hi;
Int128(long64 _lo = 0) {
lo = (ulong64)_lo;
if (_lo < 0)
hi = -1;
else
hi = 0;
}
Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
Int128 &operator=(const long64 &val) {
lo = (ulong64)val;
if (val < 0)
hi = -1;
else
hi = 0;
return *this;
}
bool operator==(const Int128 &val) const {
return (hi == val.hi && lo == val.lo);
}
bool operator!=(const Int128 &val) const { return !(*this == val); }
bool operator>(const Int128 &val) const {
if (hi != val.hi)
return hi > val.hi;
else
return lo > val.lo;
}
bool operator<(const Int128 &val) const {
if (hi != val.hi)
return hi < val.hi;
else
return lo < val.lo;
}
bool operator>=(const Int128 &val) const { return !(*this < val); }
bool operator<=(const Int128 &val) const { return !(*this > val); }
Int128 &operator+=(const Int128 &rhs) {
hi += rhs.hi;
lo += rhs.lo;
if (lo < rhs.lo)
hi++;
return *this;
}
Int128 operator+(const Int128 &rhs) const {
Int128 result(*this);
result += rhs;
return result;
}
Int128 &operator-=(const Int128 &rhs) {
*this += -rhs;
return *this;
}
Int128 operator-(const Int128 &rhs) const {
Int128 result(*this);
result -= rhs;
return result;
}
Int128 operator-() const // unary negation
{
if (lo == 0)
return Int128(-hi, 0);
else
return Int128(~hi, ~lo + 1);
}
operator double() const {
const double shift64 = 18446744073709551616.0; // 2^64
if (hi < 0) {
if (lo == 0)
return (double)hi * shift64;
else
return -(double)(~lo + ~hi * shift64);
} else
return (double)(lo + hi * shift64);
}
};
//------------------------------------------------------------------------------
Int128 Int128Mul(long64 lhs, long64 rhs) {
bool negate = (lhs < 0) != (rhs < 0);
if (lhs < 0)
lhs = -lhs;
ulong64 int1Hi = ulong64(lhs) >> 32;
ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
if (rhs < 0)
rhs = -rhs;
ulong64 int2Hi = ulong64(rhs) >> 32;
ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
// nb: see comments in clipper.pas
ulong64 a = int1Hi * int2Hi;
ulong64 b = int1Lo * int2Lo;
ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
Int128 tmp;
tmp.hi = long64(a + (c >> 32));
tmp.lo = long64(c << 32);
tmp.lo += long64(b);
if (tmp.lo < b)
tmp.hi++;
if (negate)
tmp = -tmp;
return tmp;
};
#endif
//------------------------------------------------------------------------------
// Miscellaneous global functions
//------------------------------------------------------------------------------
bool Orientation(const Path &poly) { return Area(poly) >= 0; }
//------------------------------------------------------------------------------
double Area(const Path &poly) {
int size = (int)poly.size();
if (size < 3)
return 0;
double a = 0;
for (int i = 0, j = size - 1; i < size; ++i) {
a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
j = i;
}
return -a * 0.5;
}
//------------------------------------------------------------------------------
double Area(const OutPt *op) {
const OutPt *startOp = op;
if (!op)
return 0;
double a = 0;
do {
a += (double)(op->Prev->Pt.X + op->Pt.X) *
(double)(op->Prev->Pt.Y - op->Pt.Y);
op = op->Next;
} while (op != startOp);
return a * 0.5;
}
//------------------------------------------------------------------------------
double Area(const OutRec &outRec) { return Area(outRec.Pts); }
//------------------------------------------------------------------------------
bool PointIsVertex(const IntPoint &Pt, OutPt *pp) {
OutPt *pp2 = pp;
do {
if (pp2->Pt == Pt)
return true;
pp2 = pp2->Next;
} while (pp2 != pp);
return false;
}
//------------------------------------------------------------------------------
// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann &
// Agathos
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
int PointInPolygon(const IntPoint &pt, const Path &path) {
// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
int result = 0;
size_t cnt = path.size();
if (cnt < 3)
return 0;
IntPoint ip = path[0];
for (size_t i = 1; i <= cnt; ++i) {
IntPoint ipNext = (i == cnt ? path[0] : path[i]);
if (ipNext.Y == pt.Y) {
if ((ipNext.X == pt.X) ||
(ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X))))
return -1;
}
if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
if (ip.X >= pt.X) {
if (ipNext.X > pt.X)
result = 1 - result;
else {
double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
(double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (ipNext.Y > ip.Y))
result = 1 - result;
}
} else {
if (ipNext.X > pt.X) {
double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
(double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (ipNext.Y > ip.Y))
result = 1 - result;
}
}
}
ip = ipNext;
}
return result;
}
//------------------------------------------------------------------------------
int PointInPolygon(const IntPoint &pt, OutPt *op) {
// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
int result = 0;
OutPt *startOp = op;
for (;;) {
if (op->Next->Pt.Y == pt.Y) {
if ((op->Next->Pt.X == pt.X) ||
(op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
return -1;
}
if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
if (op->Pt.X >= pt.X) {
if (op->Next->Pt.X > pt.X)
result = 1 - result;
else {
double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
(double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
result = 1 - result;
}
} else {
if (op->Next->Pt.X > pt.X) {
double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
(double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
result = 1 - result;
}
}
}
op = op->Next;
if (startOp == op)
break;
}
return result;
}
//------------------------------------------------------------------------------
bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) {
OutPt *op = OutPt1;
do {
// nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
int res = PointInPolygon(op->Pt, OutPt2);
if (res >= 0)
return res > 0;
op = op->Next;
} while (op != OutPt1);
return true;
}
//----------------------------------------------------------------------
bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
else
#endif
return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
(e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
}
//------------------------------------------------------------------------------
bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) ==
Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
else
#endif
return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) ==
(pt1.X - pt2.X) * (pt2.Y - pt3.Y);
}
//------------------------------------------------------------------------------
bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
const IntPoint pt4, bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) ==
Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
else
#endif
return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) ==
(pt1.X - pt2.X) * (pt3.Y - pt4.Y);
}
//------------------------------------------------------------------------------
inline bool IsHorizontal(TEdge &e) { return e.Dx == HORIZONTAL; }
//------------------------------------------------------------------------------
inline double GetDx(const IntPoint pt1, const IntPoint pt2) {
return (pt1.Y == pt2.Y) ? HORIZONTAL
: (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
}
//---------------------------------------------------------------------------
inline void SetDx(TEdge &e) {
cInt dy = (e.Top.Y - e.Bot.Y);
if (dy == 0)
e.Dx = HORIZONTAL;
else
e.Dx = (double)(e.Top.X - e.Bot.X) / dy;
}
//---------------------------------------------------------------------------
inline void SwapSides(TEdge &Edge1, TEdge &Edge2) {
EdgeSide Side = Edge1.Side;
Edge1.Side = Edge2.Side;
Edge2.Side = Side;
}
//------------------------------------------------------------------------------
inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) {
int OutIdx = Edge1.OutIdx;
Edge1.OutIdx = Edge2.OutIdx;
Edge2.OutIdx = OutIdx;
}
//------------------------------------------------------------------------------
inline cInt TopX(TEdge &edge, const cInt currentY) {
return (currentY == edge.Top.Y)
? edge.Top.X
: edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
}
//------------------------------------------------------------------------------
void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) {
#ifdef use_xyz
ip.Z = 0;
#endif
double b1, b2;
if (Edge1.Dx == Edge2.Dx) {
ip.Y = Edge1.Curr.Y;
ip.X = TopX(Edge1, ip.Y);
return;
} else if (Edge1.Dx == 0) {
ip.X = Edge1.Bot.X;
if (IsHorizontal(Edge2))
ip.Y = Edge2.Bot.Y;
else {
b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
ip.Y = Round(ip.X / Edge2.Dx + b2);
}
} else if (Edge2.Dx == 0) {
ip.X = Edge2.Bot.X;
if (IsHorizontal(Edge1))
ip.Y = Edge1.Bot.Y;
else {
b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
ip.Y = Round(ip.X / Edge1.Dx + b1);
}
} else {
b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
ip.Y = Round(q);
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.X = Round(Edge1.Dx * q + b1);
else
ip.X = Round(Edge2.Dx * q + b2);
}
if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
if (Edge1.Top.Y > Edge2.Top.Y)
ip.Y = Edge1.Top.Y;
else
ip.Y = Edge2.Top.Y;
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.X = TopX(Edge1, ip.Y);
else
ip.X = TopX(Edge2, ip.Y);
}
// finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
if (ip.Y > Edge1.Curr.Y) {
ip.Y = Edge1.Curr.Y;
// use the more vertical edge to derive X ...
if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
ip.X = TopX(Edge2, ip.Y);
else
ip.X = TopX(Edge1, ip.Y);
}
}
//------------------------------------------------------------------------------
void ReversePolyPtLinks(OutPt *pp) {
if (!pp)
return;
OutPt *pp1, *pp2;
pp1 = pp;
do {
pp2 = pp1->Next;
pp1->Next = pp1->Prev;
pp1->Prev = pp2;
pp1 = pp2;
} while (pp1 != pp);
}
//------------------------------------------------------------------------------
void DisposeOutPts(OutPt *&pp) {
if (pp == 0)
return;
pp->Prev->Next = 0;
while (pp) {
OutPt *tmpPp = pp;
pp = pp->Next;
delete tmpPp;
}
}
//------------------------------------------------------------------------------
inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) {
std::memset(e, 0, sizeof(TEdge));
e->Next = eNext;
e->Prev = ePrev;
e->Curr = Pt;
e->OutIdx = Unassigned;
}
//------------------------------------------------------------------------------
void InitEdge2(TEdge &e, PolyType Pt) {
if (e.Curr.Y >= e.Next->Curr.Y) {
e.Bot = e.Curr;
e.Top = e.Next->Curr;
} else {
e.Top = e.Curr;
e.Bot = e.Next->Curr;
}
SetDx(e);
e.PolyTyp = Pt;
}
//------------------------------------------------------------------------------
TEdge *RemoveEdge(TEdge *e) {
// removes e from double_linked_list (but without removing from memory)
e->Prev->Next = e->Next;
e->Next->Prev = e->Prev;
TEdge *result = e->Next;
e->Prev = 0; // flag as removed (see ClipperBase.Clear)
return result;
}
//------------------------------------------------------------------------------
inline void ReverseHorizontal(TEdge &e) {
// swap horizontal edges' Top and Bottom x's so they follow the natural
// progression of the bounds - ie so their xbots will align with the
// adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
std::swap(e.Top.X, e.Bot.X);
#ifdef use_xyz
std::swap(e.Top.Z, e.Bot.Z);
#endif
}
//------------------------------------------------------------------------------
void SwapPoints(IntPoint &pt1, IntPoint &pt2) {
IntPoint tmp = pt1;
pt1 = pt2;
pt2 = tmp;
}
//------------------------------------------------------------------------------
bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
// precondition: segments are Collinear.
if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
if (pt1a.X > pt1b.X)
SwapPoints(pt1a, pt1b);
if (pt2a.X > pt2b.X)
SwapPoints(pt2a, pt2b);
if (pt1a.X > pt2a.X)
pt1 = pt1a;
else
pt1 = pt2a;
if (pt1b.X < pt2b.X)
pt2 = pt1b;
else
pt2 = pt2b;
return pt1.X < pt2.X;
} else {
if (pt1a.Y < pt1b.Y)
SwapPoints(pt1a, pt1b);
if (pt2a.Y < pt2b.Y)
SwapPoints(pt2a, pt2b);
if (pt1a.Y < pt2a.Y)
pt1 = pt1a;
else
pt1 = pt2a;
if (pt1b.Y > pt2b.Y)
pt2 = pt1b;
else
pt2 = pt2b;
return pt1.Y > pt2.Y;
}
}
//------------------------------------------------------------------------------
bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) {
OutPt *p = btmPt1->Prev;
while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
p = p->Prev;
double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
p = btmPt1->Next;
while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
p = p->Next;
double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
p = btmPt2->Prev;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
p = p->Prev;
double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
p = btmPt2->Next;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
p = p->Next;
double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
return Area(btmPt1) > 0; // if otherwise identical use orientation
else
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
}
//------------------------------------------------------------------------------
OutPt *GetBottomPt(OutPt *pp) {
OutPt *dups = 0;
OutPt *p = pp->Next;
while (p != pp) {
if (p->Pt.Y > pp->Pt.Y) {
pp = p;
dups = 0;
} else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
if (p->Pt.X < pp->Pt.X) {
dups = 0;
pp = p;
} else {
if (p->Next != pp && p->Prev != pp)
dups = p;
}
}
p = p->Next;
}
if (dups) {
// there appears to be at least 2 vertices at BottomPt so ...
while (dups != p) {
if (!FirstIsBottomPt(p, dups))
pp = dups;
dups = dups->Next;
while (dups->Pt != pp->Pt)
dups = dups->Next;
}
}
return pp;
}
//------------------------------------------------------------------------------
bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2,
const IntPoint pt3) {
if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
return false;
else if (pt1.X != pt3.X)
return (pt2.X > pt1.X) == (pt2.X < pt3.X);
else
return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
}
//------------------------------------------------------------------------------
bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) {
if (seg1a > seg1b)
std::swap(seg1a, seg1b);
if (seg2a > seg2b)
std::swap(seg2a, seg2b);
return (seg1a < seg2b) && (seg2a < seg1b);
}
//------------------------------------------------------------------------------
// ClipperBase class methods ...
//------------------------------------------------------------------------------
ClipperBase::ClipperBase() // constructor
{
m_CurrentLM = m_MinimaList.begin(); // begin() == end() here
m_UseFullRange = false;
}
//------------------------------------------------------------------------------
ClipperBase::~ClipperBase() // destructor
{
Clear();
}
//------------------------------------------------------------------------------
void RangeTest(const IntPoint &Pt, bool &useFullRange) {
if (useFullRange) {
if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
throw clipperException("Coordinate outside allowed range");
} else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange ||
-Pt.Y > loRange) {
useFullRange = true;
RangeTest(Pt, useFullRange);
}
}
//------------------------------------------------------------------------------
TEdge *FindNextLocMin(TEdge *E) {
for (;;) {
while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
E = E->Next;
if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
break;
while (IsHorizontal(*E->Prev))
E = E->Prev;
TEdge *E2 = E;
while (IsHorizontal(*E))
E = E->Next;
if (E->Top.Y == E->Prev->Bot.Y)
continue; // ie just an intermediate horz.
if (E2->Prev->Bot.X < E->Bot.X)
E = E2;
break;
}
return E;
}
//------------------------------------------------------------------------------
TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) {
TEdge *Result = E;
TEdge *Horz = 0;
if (E->OutIdx == Skip) {
// if edges still remain in the current bound beyond the skip edge then
// create another LocMin and call ProcessBound once more
if (NextIsForward) {
while (E->Top.Y == E->Next->Bot.Y)
E = E->Next;
// don't include top horizontals when parsing a bound a second time,
// they will be contained in the opposite bound ...
while (E != Result && IsHorizontal(*E))
E = E->Prev;
} else {
while (E->Top.Y == E->Prev->Bot.Y)
E = E->Prev;
while (E != Result && IsHorizontal(*E))
E = E->Next;
}
if (E == Result) {
if (NextIsForward)
Result = E->Next;
else
Result = E->Prev;
} else {
// there are more edges in the bound beyond result starting with E
if (NextIsForward)
E = Result->Next;
else
E = Result->Prev;
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
locMin.LeftBound = 0;
locMin.RightBound = E;
E->WindDelta = 0;
Result = ProcessBound(E, NextIsForward);
m_MinimaList.push_back(locMin);
}
return Result;
}
TEdge *EStart;
if (IsHorizontal(*E)) {
// We need to be careful with open paths because this may not be a
// true local minima (ie E may be following a skip edge).
// Also, consecutive horz. edges may start heading left before going right.
if (NextIsForward)
EStart = E->Prev;
else
EStart = E->Next;
if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge
{
if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
ReverseHorizontal(*E);
} else if (EStart->Bot.X != E->Bot.X)
ReverseHorizontal(*E);
}
EStart = E;
if (NextIsForward) {
while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
Result = Result->Next;
if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
// nb: at the top of a bound, horizontals are added to the bound
// only when the preceding edge attaches to the horizontal's left vertex
// unless a Skip edge is encountered when that becomes the top divide
Horz = Result;
while (IsHorizontal(*Horz->Prev))
Horz = Horz->Prev;
if (Horz->Prev->Top.X > Result->Next->Top.X)
Result = Horz->Prev;
}
while (E != Result) {
E->NextInLML = E->Next;
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
E = E->Next;
}
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
Result = Result->Next; // move to the edge just beyond current bound
} else {
while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
Result = Result->Prev;
if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
Horz = Result;
while (IsHorizontal(*Horz->Next))
Horz = Horz->Next;
if (Horz->Next->Top.X == Result->Prev->Top.X ||
Horz->Next->Top.X > Result->Prev->Top.X)
Result = Horz->Next;
}
while (E != Result) {
E->NextInLML = E->Prev;
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
ReverseHorizontal(*E);
E = E->Prev;
}
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
ReverseHorizontal(*E);
Result = Result->Prev; // move to the edge just beyond current bound
}
return Result;
}
//------------------------------------------------------------------------------
bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) {
#ifdef use_lines
if (!Closed && PolyTyp == ptClip)
throw clipperException("AddPath: Open paths must be subject.");
#else
if (!Closed)
throw clipperException("AddPath: Open paths have been disabled.");
#endif
int highI = (int)pg.size() - 1;
if (Closed)
while (highI > 0 && (pg[highI] == pg[0]))
--highI;
while (highI > 0 && (pg[highI] == pg[highI - 1]))
--highI;
if ((Closed && highI < 2) || (!Closed && highI < 1))
return false;
// create a new edge array ...
TEdge *edges = new TEdge[highI + 1];
bool IsFlat = true;
// 1. Basic (first) edge initialization ...
try {
edges[1].Curr = pg[1];
RangeTest(pg[0], m_UseFullRange);
RangeTest(pg[highI], m_UseFullRange);
InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
for (int i = highI - 1; i >= 1; --i) {
RangeTest(pg[i], m_UseFullRange);
InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
}
} catch (...) {
delete[] edges;
throw; // range test fails
}
TEdge *eStart = &edges[0];
// 2. Remove duplicate vertices, and (when closed) collinear edges ...
TEdge *E = eStart, *eLoopStop = eStart;
for (;;) {
// nb: allows matching start and end points when not Closed ...
if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
if (E == E->Next)
break;
if (E == eStart)
eStart = E->Next;
E = RemoveEdge(E);
eLoopStop = E;
continue;
}
if (E->Prev == E->Next)
break; // only two vertices
else if (Closed && SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr,
m_UseFullRange) &&
(!m_PreserveCollinear ||
!Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
// Collinear edges are allowed for open paths but in closed paths
// the default is to merge adjacent collinear edges into a single edge.
// However, if the PreserveCollinear property is enabled, only overlapping
// collinear edges (ie spikes) will be removed from closed paths.
if (E == eStart)
eStart = E->Next;
E = RemoveEdge(E);
E = E->Prev;
eLoopStop = E;
continue;
}
E = E->Next;
if ((E == eLoopStop) || (!Closed && E->Next == eStart))
break;
}
if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
delete[] edges;
return false;
}
if (!Closed) {
m_HasOpenPaths = true;
eStart->Prev->OutIdx = Skip;
}
// 3. Do second stage of edge initialization ...
E = eStart;
do {
InitEdge2(*E, PolyTyp);
E = E->Next;
if (IsFlat && E->Curr.Y != eStart->Curr.Y)
IsFlat = false;
} while (E != eStart);
// 4. Finally, add edge bounds to LocalMinima list ...
// Totally flat paths must be handled differently when adding them
// to LocalMinima list to avoid endless loops etc ...
if (IsFlat) {
if (Closed) {
delete[] edges;
return false;
}
E->Prev->OutIdx = Skip;
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
locMin.LeftBound = 0;
locMin.RightBound = E;
locMin.RightBound->Side = esRight;
locMin.RightBound->WindDelta = 0;
for (;;) {
if (E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
if (E->Next->OutIdx == Skip)
break;
E->NextInLML = E->Next;
E = E->Next;
}
m_MinimaList.push_back(locMin);
m_edges.push_back(edges);
return true;
}
m_edges.push_back(edges);
bool leftBoundIsForward;
TEdge *EMin = 0;
// workaround to avoid an endless loop in the while loop below when
// open paths have matching start and end points ...
if (E->Prev->Bot == E->Prev->Top)
E = E->Next;
for (;;) {
E = FindNextLocMin(E);
if (E == EMin)
break;
else if (!EMin)
EMin = E;
// E and E.Prev now share a local minima (left aligned if horizontal).
// Compare their slopes to find which starts which bound ...
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
if (E->Dx < E->Prev->Dx) {
locMin.LeftBound = E->Prev;
locMin.RightBound = E;
leftBoundIsForward = false; // Q.nextInLML = Q.prev
} else {
locMin.LeftBound = E;
locMin.RightBound = E->Prev;
leftBoundIsForward = true; // Q.nextInLML = Q.next
}
if (!Closed)
locMin.LeftBound->WindDelta = 0;
else if (locMin.LeftBound->Next == locMin.RightBound)
locMin.LeftBound->WindDelta = -1;
else
locMin.LeftBound->WindDelta = 1;
locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
if (E->OutIdx == Skip)
E = ProcessBound(E, leftBoundIsForward);
TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
if (E2->OutIdx == Skip)
E2 = ProcessBound(E2, !leftBoundIsForward);
if (locMin.LeftBound->OutIdx == Skip)
locMin.LeftBound = 0;
else if (locMin.RightBound->OutIdx == Skip)
locMin.RightBound = 0;
m_MinimaList.push_back(locMin);
if (!leftBoundIsForward)
E = E2;
}
return true;
}
//------------------------------------------------------------------------------
bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) {
bool result = false;
for (Paths::size_type i = 0; i < ppg.size(); ++i)
if (AddPath(ppg[i], PolyTyp, Closed))
result = true;
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::Clear() {
DisposeLocalMinimaList();
for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
TEdge *edges = m_edges[i];
delete[] edges;
}
m_edges.clear();
m_UseFullRange = false;
m_HasOpenPaths = false;
}
//------------------------------------------------------------------------------
void ClipperBase::Reset() {
m_CurrentLM = m_MinimaList.begin();
if (m_CurrentLM == m_MinimaList.end())
return; // ie nothing to process
std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
m_Scanbeam = ScanbeamList(); // clears/resets priority_queue
// reset all edges ...
for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end();
++lm) {
InsertScanbeam(lm->Y);
TEdge *e = lm->LeftBound;
if (e) {
e->Curr = e->Bot;
e->Side = esLeft;
e->OutIdx = Unassigned;
}
e = lm->RightBound;
if (e) {
e->Curr = e->Bot;
e->Side = esRight;
e->OutIdx = Unassigned;
}
}
m_ActiveEdges = 0;
m_CurrentLM = m_MinimaList.begin();
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeLocalMinimaList() {
m_MinimaList.clear();
m_CurrentLM = m_MinimaList.begin();
}
//------------------------------------------------------------------------------
bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) {
if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
return false;
locMin = &(*m_CurrentLM);
++m_CurrentLM;
return true;
}
//------------------------------------------------------------------------------
IntRect ClipperBase::GetBounds() {
IntRect result;
MinimaList::iterator lm = m_MinimaList.begin();
if (lm == m_MinimaList.end()) {
result.left = result.top = result.right = result.bottom = 0;
return result;
}
result.left = lm->LeftBound->Bot.X;
result.top = lm->LeftBound->Bot.Y;
result.right = lm->LeftBound->Bot.X;
result.bottom = lm->LeftBound->Bot.Y;
while (lm != m_MinimaList.end()) {
// todo - needs fixing for open paths
result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
TEdge *e = lm->LeftBound;
for (;;) {
TEdge *bottomE = e;
while (e->NextInLML) {
if (e->Bot.X < result.left)
result.left = e->Bot.X;
if (e->Bot.X > result.right)
result.right = e->Bot.X;
e = e->NextInLML;
}
result.left = std::min(result.left, e->Bot.X);
result.right = std::max(result.right, e->Bot.X);
result.left = std::min(result.left, e->Top.X);
result.right = std::max(result.right, e->Top.X);
result.top = std::min(result.top, e->Top.Y);
if (bottomE == lm->LeftBound)
e = lm->RightBound;
else
break;
}
++lm;
}
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); }
//------------------------------------------------------------------------------
bool ClipperBase::PopScanbeam(cInt &Y) {
if (m_Scanbeam.empty())
return false;
Y = m_Scanbeam.top();
m_Scanbeam.pop();
while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
m_Scanbeam.pop();
} // Pop duplicates.
return true;
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeAllOutRecs() {
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
DisposeOutRec(i);
m_PolyOuts.clear();
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeOutRec(PolyOutList::size_type index) {
OutRec *outRec = m_PolyOuts[index];
if (outRec->Pts)
DisposeOutPts(outRec->Pts);
delete outRec;
m_PolyOuts[index] = 0;
}
//------------------------------------------------------------------------------
void ClipperBase::DeleteFromAEL(TEdge *e) {
TEdge *AelPrev = e->PrevInAEL;
TEdge *AelNext = e->NextInAEL;
if (!AelPrev && !AelNext && (e != m_ActiveEdges))
return; // already deleted
if (AelPrev)
AelPrev->NextInAEL = AelNext;
else
m_ActiveEdges = AelNext;
if (AelNext)
AelNext->PrevInAEL = AelPrev;
e->NextInAEL = 0;
e->PrevInAEL = 0;
}
//------------------------------------------------------------------------------
OutRec *ClipperBase::CreateOutRec() {
OutRec *result = new OutRec;
result->IsHole = false;
result->IsOpen = false;
result->FirstLeft = 0;
result->Pts = 0;
result->BottomPt = 0;
result->PolyNd = 0;
m_PolyOuts.push_back(result);
result->Idx = (int)m_PolyOuts.size() - 1;
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) {
// check that one or other edge hasn't already been removed from AEL ...
if (Edge1->NextInAEL == Edge1->PrevInAEL ||
Edge2->NextInAEL == Edge2->PrevInAEL)
return;
if (Edge1->NextInAEL == Edge2) {
TEdge *Next = Edge2->NextInAEL;
if (Next)
Next->PrevInAEL = Edge1;
TEdge *Prev = Edge1->PrevInAEL;
if (Prev)
Prev->NextInAEL = Edge2;
Edge2->PrevInAEL = Prev;
Edge2->NextInAEL = Edge1;
Edge1->PrevInAEL = Edge2;
Edge1->NextInAEL = Next;
} else if (Edge2->NextInAEL == Edge1) {
TEdge *Next = Edge1->NextInAEL;
if (Next)
Next->PrevInAEL = Edge2;
TEdge *Prev = Edge2->PrevInAEL;
if (Prev)
Prev->NextInAEL = Edge1;
Edge1->PrevInAEL = Prev;
Edge1->NextInAEL = Edge2;
Edge2->PrevInAEL = Edge1;
Edge2->NextInAEL = Next;
} else {
TEdge *Next = Edge1->NextInAEL;
TEdge *Prev = Edge1->PrevInAEL;
Edge1->NextInAEL = Edge2->NextInAEL;
if (Edge1->NextInAEL)
Edge1->NextInAEL->PrevInAEL = Edge1;
Edge1->PrevInAEL = Edge2->PrevInAEL;
if (Edge1->PrevInAEL)
Edge1->PrevInAEL->NextInAEL = Edge1;
Edge2->NextInAEL = Next;
if (Edge2->NextInAEL)
Edge2->NextInAEL->PrevInAEL = Edge2;
Edge2->PrevInAEL = Prev;
if (Edge2->PrevInAEL)
Edge2->PrevInAEL->NextInAEL = Edge2;
}
if (!Edge1->PrevInAEL)
m_ActiveEdges = Edge1;
else if (!Edge2->PrevInAEL)
m_ActiveEdges = Edge2;
}
//------------------------------------------------------------------------------
void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) {
if (!e->NextInLML)
throw clipperException("UpdateEdgeIntoAEL: invalid call");
e->NextInLML->OutIdx = e->OutIdx;
TEdge *AelPrev = e->PrevInAEL;
TEdge *AelNext = e->NextInAEL;
if (AelPrev)
AelPrev->NextInAEL = e->NextInLML;
else
m_ActiveEdges = e->NextInLML;
if (AelNext)
AelNext->PrevInAEL = e->NextInLML;
e->NextInLML->Side = e->Side;
e->NextInLML->WindDelta = e->WindDelta;
e->NextInLML->WindCnt = e->WindCnt;
e->NextInLML->WindCnt2 = e->WindCnt2;
e = e->NextInLML;
e->Curr = e->Bot;
e->PrevInAEL = AelPrev;
e->NextInAEL = AelNext;
if (!IsHorizontal(*e))
InsertScanbeam(e->Top.Y);
}
//------------------------------------------------------------------------------
bool ClipperBase::LocalMinimaPending() {
return (m_CurrentLM != m_MinimaList.end());
}
//------------------------------------------------------------------------------
// TClipper methods ...
//------------------------------------------------------------------------------
Clipper::Clipper(int initOptions)
: ClipperBase() // constructor
{
m_ExecuteLocked = false;
m_UseFullRange = false;
m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
m_HasOpenPaths = false;
#ifdef use_xyz
m_ZFill = 0;
#endif
}
//------------------------------------------------------------------------------
#ifdef use_xyz
void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; }
//------------------------------------------------------------------------------
#endif
bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType fillType) {
return Execute(clipType, solution, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
PolyFillType fillType) {
return Execute(clipType, polytree, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType subjFillType, PolyFillType clipFillType) {
if (m_ExecuteLocked)
return false;
if (m_HasOpenPaths)
throw clipperException(
"Error: PolyTree struct is needed for open path clipping.");
m_ExecuteLocked = true;
solution.resize(0);
m_SubjFillType = subjFillType;
m_ClipFillType = clipFillType;
m_ClipType = clipType;
m_UsingPolyTree = false;
bool succeeded = ExecuteInternal();
if (succeeded)
BuildResult(solution);
DisposeAllOutRecs();
m_ExecuteLocked = false;
return succeeded;
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
PolyFillType subjFillType, PolyFillType clipFillType) {
if (m_ExecuteLocked)
return false;
m_ExecuteLocked = true;
m_SubjFillType = subjFillType;
m_ClipFillType = clipFillType;
m_ClipType = clipType;
m_UsingPolyTree = true;
bool succeeded = ExecuteInternal();
if (succeeded)
BuildResult2(polytree);
DisposeAllOutRecs();
m_ExecuteLocked = false;
return succeeded;
}
//------------------------------------------------------------------------------
void Clipper::FixHoleLinkage(OutRec &outrec) {
// skip OutRecs that (a) contain outermost polygons or
//(b) already have the correct owner/child linkage ...
if (!outrec.FirstLeft ||
(outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts))
return;
OutRec *orfl = outrec.FirstLeft;
while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
orfl = orfl->FirstLeft;
outrec.FirstLeft = orfl;
}
//------------------------------------------------------------------------------
bool Clipper::ExecuteInternal() {
bool succeeded = true;
try {
Reset();
m_Maxima = MaximaList();
m_SortedEdges = 0;
succeeded = true;
cInt botY, topY;
if (!PopScanbeam(botY))
return false;
InsertLocalMinimaIntoAEL(botY);
while (PopScanbeam(topY) || LocalMinimaPending()) {
ProcessHorizontals();
ClearGhostJoins();
if (!ProcessIntersections(topY)) {
succeeded = false;
break;
}
ProcessEdgesAtTopOfScanbeam(topY);
botY = topY;
InsertLocalMinimaIntoAEL(botY);
}
} catch (...) {
succeeded = false;
}
if (succeeded) {
// fix orientations ...
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts || outRec->IsOpen)
continue;
if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
ReversePolyPtLinks(outRec->Pts);
}
if (!m_Joins.empty())
JoinCommonEdges();
// unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts)
continue;
if (outRec->IsOpen)
FixupOutPolyline(*outRec);
else
FixupOutPolygon(*outRec);
}
if (m_StrictSimple)
DoSimplePolygons();
}
ClearJoins();
ClearGhostJoins();
return succeeded;
}
//------------------------------------------------------------------------------
void Clipper::SetWindingCount(TEdge &edge) {
TEdge *e = edge.PrevInAEL;
// find the edge of the same polytype that immediately preceeds 'edge' in AEL
while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
e = e->PrevInAEL;
if (!e) {
if (edge.WindDelta == 0) {
PolyFillType pft =
(edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
edge.WindCnt = (pft == pftNegative ? -1 : 1);
} else
edge.WindCnt = edge.WindDelta;
edge.WindCnt2 = 0;
e = m_ActiveEdges; // ie get ready to calc WindCnt2
} else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
edge.WindCnt = 1;
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
} else if (IsEvenOddFillType(edge)) {
// EvenOdd filling ...
if (edge.WindDelta == 0) {
// are we inside a subj polygon ...
bool Inside = true;
TEdge *e2 = e->PrevInAEL;
while (e2) {
if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
Inside = !Inside;
e2 = e2->PrevInAEL;
}
edge.WindCnt = (Inside ? 0 : 1);
} else {
edge.WindCnt = edge.WindDelta;
}
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
} else {
// nonZero, Positive or Negative filling ...
if (e->WindCnt * e->WindDelta < 0) {
// prev edge is 'decreasing' WindCount (WC) toward zero
// so we're outside the previous polygon ...
if (Abs(e->WindCnt) > 1) {
// outside prev poly but still inside another.
// when reversing direction of prev poly use the same WC
if (e->WindDelta * edge.WindDelta < 0)
edge.WindCnt = e->WindCnt;
// otherwise continue to 'decrease' WC ...
else
edge.WindCnt = e->WindCnt + edge.WindDelta;
} else
// now outside all polys of same polytype so set own WC ...
edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
} else {
// prev edge is 'increasing' WindCount (WC) away from zero
// so we're inside the previous polygon ...
if (edge.WindDelta == 0)
edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
// if wind direction is reversing prev then use same WC
else if (e->WindDelta * edge.WindDelta < 0)
edge.WindCnt = e->WindCnt;
// otherwise add to WC ...
else
edge.WindCnt = e->WindCnt + edge.WindDelta;
}
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
}
// update WindCnt2 ...
if (IsEvenOddAltFillType(edge)) {
// EvenOdd filling ...
while (e != &edge) {
if (e->WindDelta != 0)
edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
e = e->NextInAEL;
}
} else {
// nonZero, Positive or Negative filling ...
while (e != &edge) {
edge.WindCnt2 += e->WindDelta;
e = e->NextInAEL;
}
}
}
//------------------------------------------------------------------------------
bool Clipper::IsEvenOddFillType(const TEdge &edge) const {
if (edge.PolyTyp == ptSubject)
return m_SubjFillType == pftEvenOdd;
else
return m_ClipFillType == pftEvenOdd;
}
//------------------------------------------------------------------------------
bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const {
if (edge.PolyTyp == ptSubject)
return m_ClipFillType == pftEvenOdd;
else
return m_SubjFillType == pftEvenOdd;
}
//------------------------------------------------------------------------------
bool Clipper::IsContributing(const TEdge &edge) const {
PolyFillType pft, pft2;
if (edge.PolyTyp == ptSubject) {
pft = m_SubjFillType;
pft2 = m_ClipFillType;
} else {
pft = m_ClipFillType;
pft2 = m_SubjFillType;
}
switch (pft) {
case pftEvenOdd:
// return false if a subj line has been flagged as inside a subj polygon
if (edge.WindDelta == 0 && edge.WindCnt != 1)
return false;
break;
case pftNonZero:
if (Abs(edge.WindCnt) != 1)
return false;
break;
case pftPositive:
if (edge.WindCnt != 1)
return false;
break;
default: // pftNegative
if (edge.WindCnt != -1)
return false;
}
switch (m_ClipType) {
case ctIntersection:
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 != 0);
case pftPositive:
return (edge.WindCnt2 > 0);
default:
return (edge.WindCnt2 < 0);
}
break;
case ctUnion:
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
break;
case ctDifference:
if (edge.PolyTyp == ptSubject)
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
else
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 != 0);
case pftPositive:
return (edge.WindCnt2 > 0);
default:
return (edge.WindCnt2 < 0);
}
break;
case ctXor:
if (edge.WindDelta == 0) // XOr always contributing unless open
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
else
return true;
break;
default:
return true;
}
}
//------------------------------------------------------------------------------
OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
OutPt *result;
TEdge *e, *prevE;
if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
result = AddOutPt(e1, Pt);
e2->OutIdx = e1->OutIdx;
e1->Side = esLeft;
e2->Side = esRight;
e = e1;
if (e->PrevInAEL == e2)
prevE = e2->PrevInAEL;
else
prevE = e->PrevInAEL;
} else {
result = AddOutPt(e2, Pt);
e1->OutIdx = e2->OutIdx;
e1->Side = esRight;
e2->Side = esLeft;
e = e2;
if (e->PrevInAEL == e1)
prevE = e1->PrevInAEL;
else
prevE = e->PrevInAEL;
}
if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
cInt xPrev = TopX(*prevE, Pt.Y);
cInt xE = TopX(*e, Pt.Y);
if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y),
e->Top, m_UseFullRange)) {
OutPt *outPt = AddOutPt(prevE, Pt);
AddJoin(result, outPt, e->Top);
}
}
return result;
}
//------------------------------------------------------------------------------
void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
AddOutPt(e1, Pt);
if (e2->WindDelta == 0)
AddOutPt(e2, Pt);
if (e1->OutIdx == e2->OutIdx) {
e1->OutIdx = Unassigned;
e2->OutIdx = Unassigned;
} else if (e1->OutIdx < e2->OutIdx)
AppendPolygon(e1, e2);
else
AppendPolygon(e2, e1);
}
//------------------------------------------------------------------------------
void Clipper::AddEdgeToSEL(TEdge *edge) {
// SEL pointers in PEdge are reused to build a list of horizontal edges.
// However, we don't need to worry about order with horizontal edge
// processing.
if (!m_SortedEdges) {
m_SortedEdges = edge;
edge->PrevInSEL = 0;
edge->NextInSEL = 0;
} else {
edge->NextInSEL = m_SortedEdges;
edge->PrevInSEL = 0;
m_SortedEdges->PrevInSEL = edge;
m_SortedEdges = edge;
}
}
//------------------------------------------------------------------------------
bool Clipper::PopEdgeFromSEL(TEdge *&edge) {
if (!m_SortedEdges)
return false;
edge = m_SortedEdges;
DeleteFromSEL(m_SortedEdges);
return true;
}
//------------------------------------------------------------------------------
void Clipper::CopyAELToSEL() {
TEdge *e = m_ActiveEdges;
m_SortedEdges = e;
while (e) {
e->PrevInSEL = e->PrevInAEL;
e->NextInSEL = e->NextInAEL;
e = e->NextInAEL;
}
}
//------------------------------------------------------------------------------
void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) {
Join *j = new Join;
j->OutPt1 = op1;
j->OutPt2 = op2;
j->OffPt = OffPt;
m_Joins.push_back(j);
}
//------------------------------------------------------------------------------
void Clipper::ClearJoins() {
for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
delete m_Joins[i];
m_Joins.resize(0);
}
//------------------------------------------------------------------------------
void Clipper::ClearGhostJoins() {
for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
delete m_GhostJoins[i];
m_GhostJoins.resize(0);
}
//------------------------------------------------------------------------------
void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) {
Join *j = new Join;
j->OutPt1 = op;
j->OutPt2 = 0;
j->OffPt = OffPt;
m_GhostJoins.push_back(j);
}
//------------------------------------------------------------------------------
void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) {
const LocalMinimum *lm;
while (PopLocalMinima(botY, lm)) {
TEdge *lb = lm->LeftBound;
TEdge *rb = lm->RightBound;
OutPt *Op1 = 0;
if (!lb) {
// nb: don't insert LB into either AEL or SEL
InsertEdgeIntoAEL(rb, 0);
SetWindingCount(*rb);
if (IsContributing(*rb))
Op1 = AddOutPt(rb, rb->Bot);
} else if (!rb) {
InsertEdgeIntoAEL(lb, 0);
SetWindingCount(*lb);
if (IsContributing(*lb))
Op1 = AddOutPt(lb, lb->Bot);
InsertScanbeam(lb->Top.Y);
} else {
InsertEdgeIntoAEL(lb, 0);
InsertEdgeIntoAEL(rb, lb);
SetWindingCount(*lb);
rb->WindCnt = lb->WindCnt;
rb->WindCnt2 = lb->WindCnt2;
if (IsContributing(*lb))
Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
InsertScanbeam(lb->Top.Y);
}
if (rb) {
if (IsHorizontal(*rb)) {
AddEdgeToSEL(rb);
if (rb->NextInLML)
InsertScanbeam(rb->NextInLML->Top.Y);
} else
InsertScanbeam(rb->Top.Y);
}
if (!lb || !rb)
continue;
// if any output polygons share an edge, they'll need joining later ...
if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 &&
(rb->WindDelta != 0)) {
for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
Join *jr = m_GhostJoins[i];
// if the horizontal Rb and a 'ghost' horizontal overlap, then convert
// the 'ghost' join to a real join ready for later ...
if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X,
rb->Top.X))
AddJoin(jr->OutPt1, Op1, jr->OffPt);
}
}
if (lb->OutIdx >= 0 && lb->PrevInAEL &&
lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 &&
SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top,
m_UseFullRange) &&
(lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
AddJoin(Op1, Op2, lb->Top);
}
if (lb->NextInAEL != rb) {
if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr,
rb->Top, m_UseFullRange) &&
(rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
AddJoin(Op1, Op2, rb->Top);
}
TEdge *e = lb->NextInAEL;
if (e) {
while (e != rb) {
// nb: For calculating winding counts etc, IntersectEdges() assumes
// that param1 will be to the Right of param2 ABOVE the intersection
// ...
IntersectEdges(rb, e, lb->Curr); // order important here
e = e->NextInAEL;
}
}
}
}
}
//------------------------------------------------------------------------------
void Clipper::DeleteFromSEL(TEdge *e) {
TEdge *SelPrev = e->PrevInSEL;
TEdge *SelNext = e->NextInSEL;
if (!SelPrev && !SelNext && (e != m_SortedEdges))
return; // already deleted
if (SelPrev)
SelPrev->NextInSEL = SelNext;
else
m_SortedEdges = SelNext;
if (SelNext)
SelNext->PrevInSEL = SelPrev;
e->NextInSEL = 0;
e->PrevInSEL = 0;
}
//------------------------------------------------------------------------------
#ifdef use_xyz
void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) {
if (pt.Z != 0 || !m_ZFill)
return;
else if (pt == e1.Bot)
pt.Z = e1.Bot.Z;
else if (pt == e1.Top)
pt.Z = e1.Top.Z;
else if (pt == e2.Bot)
pt.Z = e2.Bot.Z;
else if (pt == e2.Top)
pt.Z = e2.Top.Z;
else
(*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
}
//------------------------------------------------------------------------------
#endif
void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) {
bool e1Contributing = (e1->OutIdx >= 0);
bool e2Contributing = (e2->OutIdx >= 0);
#ifdef use_xyz
SetZ(Pt, *e1, *e2);
#endif
#ifdef use_lines
// if either edge is on an OPEN path ...
if (e1->WindDelta == 0 || e2->WindDelta == 0) {
// ignore subject-subject open path intersections UNLESS they
// are both open paths, AND they are both 'contributing maximas' ...
if (e1->WindDelta == 0 && e2->WindDelta == 0)
return;
// if intersecting a subj line with a subj poly ...
else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta &&
m_ClipType == ctUnion) {
if (e1->WindDelta == 0) {
if (e2Contributing) {
AddOutPt(e1, Pt);
if (e1Contributing)
e1->OutIdx = Unassigned;
}
} else {
if (e1Contributing) {
AddOutPt(e2, Pt);
if (e2Contributing)
e2->OutIdx = Unassigned;
}
}
} else if (e1->PolyTyp != e2->PolyTyp) {
// toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
(m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
AddOutPt(e1, Pt);
if (e1Contributing)
e1->OutIdx = Unassigned;
} else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
(m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
AddOutPt(e2, Pt);
if (e2Contributing)
e2->OutIdx = Unassigned;
}
}
return;
}
#endif
// update winding counts...
// assumes that e1 will be to the Right of e2 ABOVE the intersection
if (e1->PolyTyp == e2->PolyTyp) {
if (IsEvenOddFillType(*e1)) {
int oldE1WindCnt = e1->WindCnt;
e1->WindCnt = e2->WindCnt;
e2->WindCnt = oldE1WindCnt;
} else {
if (e1->WindCnt + e2->WindDelta == 0)
e1->WindCnt = -e1->WindCnt;
else
e1->WindCnt += e2->WindDelta;
if (e2->WindCnt - e1->WindDelta == 0)
e2->WindCnt = -e2->WindCnt;
else
e2->WindCnt -= e1->WindDelta;
}
} else {
if (!IsEvenOddFillType(*e2))
e1->WindCnt2 += e2->WindDelta;
else
e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
if (!IsEvenOddFillType(*e1))
e2->WindCnt2 -= e1->WindDelta;
else
e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
}
PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
if (e1->PolyTyp == ptSubject) {
e1FillType = m_SubjFillType;
e1FillType2 = m_ClipFillType;
} else {
e1FillType = m_ClipFillType;
e1FillType2 = m_SubjFillType;
}
if (e2->PolyTyp == ptSubject) {
e2FillType = m_SubjFillType;
e2FillType2 = m_ClipFillType;
} else {
e2FillType = m_ClipFillType;
e2FillType2 = m_SubjFillType;
}
cInt e1Wc, e2Wc;
switch (e1FillType) {
case pftPositive:
e1Wc = e1->WindCnt;
break;
case pftNegative:
e1Wc = -e1->WindCnt;
break;
default:
e1Wc = Abs(e1->WindCnt);
}
switch (e2FillType) {
case pftPositive:
e2Wc = e2->WindCnt;
break;
case pftNegative:
e2Wc = -e2->WindCnt;
break;
default:
e2Wc = Abs(e2->WindCnt);
}
if (e1Contributing && e2Contributing) {
if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
(e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
AddLocalMaxPoly(e1, e2, Pt);
} else {
AddOutPt(e1, Pt);
AddOutPt(e2, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if (e1Contributing) {
if (e2Wc == 0 || e2Wc == 1) {
AddOutPt(e1, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if (e2Contributing) {
if (e1Wc == 0 || e1Wc == 1) {
AddOutPt(e2, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
// neither edge is currently contributing ...
cInt e1Wc2, e2Wc2;
switch (e1FillType2) {
case pftPositive:
e1Wc2 = e1->WindCnt2;
break;
case pftNegative:
e1Wc2 = -e1->WindCnt2;
break;
default:
e1Wc2 = Abs(e1->WindCnt2);
}
switch (e2FillType2) {
case pftPositive:
e2Wc2 = e2->WindCnt2;
break;
case pftNegative:
e2Wc2 = -e2->WindCnt2;
break;
default:
e2Wc2 = Abs(e2->WindCnt2);
}
if (e1->PolyTyp != e2->PolyTyp) {
AddLocalMinPoly(e1, e2, Pt);
} else if (e1Wc == 1 && e2Wc == 1)
switch (m_ClipType) {
case ctIntersection:
if (e1Wc2 > 0 && e2Wc2 > 0)
AddLocalMinPoly(e1, e2, Pt);
break;
case ctUnion:
if (e1Wc2 <= 0 && e2Wc2 <= 0)
AddLocalMinPoly(e1, e2, Pt);
break;
case ctDifference:
if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
AddLocalMinPoly(e1, e2, Pt);
break;
case ctXor:
AddLocalMinPoly(e1, e2, Pt);
}
else
SwapSides(*e1, *e2);
}
}
//------------------------------------------------------------------------------
void Clipper::SetHoleState(TEdge *e, OutRec *outrec) {
TEdge *e2 = e->PrevInAEL;
TEdge *eTmp = 0;
while (e2) {
if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
if (!eTmp)
eTmp = e2;
else if (eTmp->OutIdx == e2->OutIdx)
eTmp = 0;
}
e2 = e2->PrevInAEL;
}
if (!eTmp) {
outrec->FirstLeft = 0;
outrec->IsHole = false;
} else {
outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
outrec->IsHole = !outrec->FirstLeft->IsHole;
}
}
//------------------------------------------------------------------------------
OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) {
// work out which polygon fragment has the correct hole state ...
if (!outRec1->BottomPt)
outRec1->BottomPt = GetBottomPt(outRec1->Pts);
if (!outRec2->BottomPt)
outRec2->BottomPt = GetBottomPt(outRec2->Pts);
OutPt *OutPt1 = outRec1->BottomPt;
OutPt *OutPt2 = outRec2->BottomPt;
if (OutPt1->Pt.Y > OutPt2->Pt.Y)
return outRec1;
else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
return outRec2;
else if (OutPt1->Pt.X < OutPt2->Pt.X)
return outRec1;
else if (OutPt1->Pt.X > OutPt2->Pt.X)
return outRec2;
else if (OutPt1->Next == OutPt1)
return outRec2;
else if (OutPt2->Next == OutPt2)
return outRec1;
else if (FirstIsBottomPt(OutPt1, OutPt2))
return outRec1;
else
return outRec2;
}
//------------------------------------------------------------------------------
bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) {
do {
outRec1 = outRec1->FirstLeft;
if (outRec1 == outRec2)
return true;
} while (outRec1);
return false;
}
//------------------------------------------------------------------------------
OutRec *Clipper::GetOutRec(int Idx) {
OutRec *outrec = m_PolyOuts[Idx];
while (outrec != m_PolyOuts[outrec->Idx])
outrec = m_PolyOuts[outrec->Idx];
return outrec;
}
//------------------------------------------------------------------------------
void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) {
// get the start and ends of both output polygons ...
OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
OutRec *holeStateRec;
if (OutRec1RightOfOutRec2(outRec1, outRec2))
holeStateRec = outRec2;
else if (OutRec1RightOfOutRec2(outRec2, outRec1))
holeStateRec = outRec1;
else
holeStateRec = GetLowermostRec(outRec1, outRec2);
// get the start and ends of both output polygons and
// join e2 poly onto e1 poly and delete pointers to e2 ...
OutPt *p1_lft = outRec1->Pts;
OutPt *p1_rt = p1_lft->Prev;
OutPt *p2_lft = outRec2->Pts;
OutPt *p2_rt = p2_lft->Prev;
// join e2 poly onto e1 poly and delete pointers to e2 ...
if (e1->Side == esLeft) {
if (e2->Side == esLeft) {
// z y x a b c
ReversePolyPtLinks(p2_lft);
p2_lft->Next = p1_lft;
p1_lft->Prev = p2_lft;
p1_rt->Next = p2_rt;
p2_rt->Prev = p1_rt;
outRec1->Pts = p2_rt;
} else {
// x y z a b c
p2_rt->Next = p1_lft;
p1_lft->Prev = p2_rt;
p2_lft->Prev = p1_rt;
p1_rt->Next = p2_lft;
outRec1->Pts = p2_lft;
}
} else {
if (e2->Side == esRight) {
// a b c z y x
ReversePolyPtLinks(p2_lft);
p1_rt->Next = p2_rt;
p2_rt->Prev = p1_rt;
p2_lft->Next = p1_lft;
p1_lft->Prev = p2_lft;
} else {
// a b c x y z
p1_rt->Next = p2_lft;
p2_lft->Prev = p1_rt;
p1_lft->Prev = p2_rt;
p2_rt->Next = p1_lft;
}
}
outRec1->BottomPt = 0;
if (holeStateRec == outRec2) {
if (outRec2->FirstLeft != outRec1)
outRec1->FirstLeft = outRec2->FirstLeft;
outRec1->IsHole = outRec2->IsHole;
}
outRec2->Pts = 0;
outRec2->BottomPt = 0;
outRec2->FirstLeft = outRec1;
int OKIdx = e1->OutIdx;
int ObsoleteIdx = e2->OutIdx;
e1->OutIdx =
Unassigned; // nb: safe because we only get here via AddLocalMaxPoly
e2->OutIdx = Unassigned;
TEdge *e = m_ActiveEdges;
while (e) {
if (e->OutIdx == ObsoleteIdx) {
e->OutIdx = OKIdx;
e->Side = e1->Side;
break;
}
e = e->NextInAEL;
}
outRec2->Idx = outRec1->Idx;
}
//------------------------------------------------------------------------------
OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) {
if (e->OutIdx < 0) {
OutRec *outRec = CreateOutRec();
outRec->IsOpen = (e->WindDelta == 0);
OutPt *newOp = new OutPt;
outRec->Pts = newOp;
newOp->Idx = outRec->Idx;
newOp->Pt = pt;
newOp->Next = newOp;
newOp->Prev = newOp;
if (!outRec->IsOpen)
SetHoleState(e, outRec);
e->OutIdx = outRec->Idx;
return newOp;
} else {
OutRec *outRec = m_PolyOuts[e->OutIdx];
// OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
OutPt *op = outRec->Pts;
bool ToFront = (e->Side == esLeft);
if (ToFront && (pt == op->Pt))
return op;
else if (!ToFront && (pt == op->Prev->Pt))
return op->Prev;
OutPt *newOp = new OutPt;
newOp->Idx = outRec->Idx;
newOp->Pt = pt;
newOp->Next = op;
newOp->Prev = op->Prev;
newOp->Prev->Next = newOp;
op->Prev = newOp;
if (ToFront)
outRec->Pts = newOp;
return newOp;
}
}
//------------------------------------------------------------------------------
OutPt *Clipper::GetLastOutPt(TEdge *e) {
OutRec *outRec = m_PolyOuts[e->OutIdx];
if (e->Side == esLeft)
return outRec->Pts;
else
return outRec->Pts->Prev;
}
//------------------------------------------------------------------------------
void Clipper::ProcessHorizontals() {
TEdge *horzEdge;
while (PopEdgeFromSEL(horzEdge))
ProcessHorizontal(horzEdge);
}
//------------------------------------------------------------------------------
inline bool IsMinima(TEdge *e) {
return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
}
//------------------------------------------------------------------------------
inline bool IsMaxima(TEdge *e, const cInt Y) {
return e && e->Top.Y == Y && !e->NextInLML;
}
//------------------------------------------------------------------------------
inline bool IsIntermediate(TEdge *e, const cInt Y) {
return e->Top.Y == Y && e->NextInLML;
}
//------------------------------------------------------------------------------
TEdge *GetMaximaPair(TEdge *e) {
if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
return e->Next;
else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
return e->Prev;
else
return 0;
}
//------------------------------------------------------------------------------
TEdge *GetMaximaPairEx(TEdge *e) {
// as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's
// horizontal)
TEdge *result = GetMaximaPair(e);
if (result &&
(result->OutIdx == Skip ||
(result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
return 0;
return result;
}
//------------------------------------------------------------------------------
void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) {
if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
return;
if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
return;
if (Edge1->NextInSEL == Edge2) {
TEdge *Next = Edge2->NextInSEL;
if (Next)
Next->PrevInSEL = Edge1;
TEdge *Prev = Edge1->PrevInSEL;
if (Prev)
Prev->NextInSEL = Edge2;
Edge2->PrevInSEL = Prev;
Edge2->NextInSEL = Edge1;
Edge1->PrevInSEL = Edge2;
Edge1->NextInSEL = Next;
} else if (Edge2->NextInSEL == Edge1) {
TEdge *Next = Edge1->NextInSEL;
if (Next)
Next->PrevInSEL = Edge2;
TEdge *Prev = Edge2->PrevInSEL;
if (Prev)
Prev->NextInSEL = Edge1;
Edge1->PrevInSEL = Prev;
Edge1->NextInSEL = Edge2;
Edge2->PrevInSEL = Edge1;
Edge2->NextInSEL = Next;
} else {
TEdge *Next = Edge1->NextInSEL;
TEdge *Prev = Edge1->PrevInSEL;
Edge1->NextInSEL = Edge2->NextInSEL;
if (Edge1->NextInSEL)
Edge1->NextInSEL->PrevInSEL = Edge1;
Edge1->PrevInSEL = Edge2->PrevInSEL;
if (Edge1->PrevInSEL)
Edge1->PrevInSEL->NextInSEL = Edge1;
Edge2->NextInSEL = Next;
if (Edge2->NextInSEL)
Edge2->NextInSEL->PrevInSEL = Edge2;
Edge2->PrevInSEL = Prev;
if (Edge2->PrevInSEL)
Edge2->PrevInSEL->NextInSEL = Edge2;
}
if (!Edge1->PrevInSEL)
m_SortedEdges = Edge1;
else if (!Edge2->PrevInSEL)
m_SortedEdges = Edge2;
}
//------------------------------------------------------------------------------
TEdge *GetNextInAEL(TEdge *e, Direction dir) {
return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
}
//------------------------------------------------------------------------------
void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left,
cInt &Right) {
if (HorzEdge.Bot.X < HorzEdge.Top.X) {
Left = HorzEdge.Bot.X;
Right = HorzEdge.Top.X;
Dir = dLeftToRight;
} else {
Left = HorzEdge.Top.X;
Right = HorzEdge.Bot.X;
Dir = dRightToLeft;
}
}
//------------------------------------------------------------------------
/*******************************************************************************
* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or *
* Bottom of a scanbeam) are processed as if layered. The order in which HEs *
* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
* and with other non-horizontal edges [*]. Once these intersections are *
* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
*******************************************************************************/
void Clipper::ProcessHorizontal(TEdge *horzEdge) {
Direction dir;
cInt horzLeft, horzRight;
bool IsOpen = (horzEdge->WindDelta == 0);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
eLastHorz = eLastHorz->NextInLML;
if (!eLastHorz->NextInLML)
eMaxPair = GetMaximaPair(eLastHorz);
MaximaList::const_iterator maxIt;
MaximaList::const_reverse_iterator maxRit;
if (m_Maxima.size() > 0) {
// get the first maxima in range (X) ...
if (dir == dLeftToRight) {
maxIt = m_Maxima.begin();
while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
maxIt++;
if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
maxIt = m_Maxima.end();
} else {
maxRit = m_Maxima.rbegin();
while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X)
maxRit++;
if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
maxRit = m_Maxima.rend();
}
}
OutPt *op1 = 0;
for (;;) // loop through consec. horizontal edges
{
bool IsLastHorz = (horzEdge == eLastHorz);
TEdge *e = GetNextInAEL(horzEdge, dir);
while (e) {
// this code block inserts extra coords into horizontal edges (in output
// polygons) whereever maxima touch these horizontal edges. This helps
//'simplifying' polygons (ie if the Simplify property is set).
if (m_Maxima.size() > 0) {
if (dir == dLeftToRight) {
while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
maxIt++;
}
} else {
while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) {
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
maxRit++;
}
}
};
if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
(dir == dRightToLeft && e->Curr.X < horzLeft))
break;
// Also break if we've got to the end of an intermediate horizontal edge
// ...
// nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
e->Dx < horzEdge->NextInLML->Dx)
break;
if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times
{
#ifdef use_xyz
if (dir == dLeftToRight)
SetZ(e->Curr, *horzEdge, *e);
else
SetZ(e->Curr, *e, *horzEdge);
#endif
op1 = AddOutPt(horzEdge, e->Curr);
TEdge *eNextHorz = m_SortedEdges;
while (eNextHorz) {
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
eNextHorz->Bot.X, eNextHorz->Top.X)) {
OutPt *op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Bot);
}
// OK, so far we're still in range of the horizontal Edge but make sure
// we're at the last of consec. horizontals when matching with eMaxPair
if (e == eMaxPair && IsLastHorz) {
if (horzEdge->OutIdx >= 0)
AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
DeleteFromAEL(horzEdge);
DeleteFromAEL(eMaxPair);
return;
}
if (dir == dLeftToRight) {
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges(horzEdge, e, Pt);
} else {
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges(e, horzEdge, Pt);
}
TEdge *eNext = GetNextInAEL(e, dir);
SwapPositionsInAEL(horzEdge, e);
e = eNext;
} // end while(e)
// Break out of loop if HorzEdge.NextInLML is not also horizontal ...
if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
break;
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->OutIdx >= 0)
AddOutPt(horzEdge, horzEdge->Bot);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
} // end for (;;)
if (horzEdge->OutIdx >= 0 && !op1) {
op1 = GetLastOutPt(horzEdge);
TEdge *eNextHorz = m_SortedEdges;
while (eNextHorz) {
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
eNextHorz->Bot.X, eNextHorz->Top.X)) {
OutPt *op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Top);
}
if (horzEdge->NextInLML) {
if (horzEdge->OutIdx >= 0) {
op1 = AddOutPt(horzEdge, horzEdge->Top);
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->WindDelta == 0)
return;
// nb: HorzEdge is no longer horizontal here
TEdge *ePrev = horzEdge->PrevInAEL;
TEdge *eNext = horzEdge->NextInAEL;
if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
(ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
AddJoin(op1, op2, horzEdge->Top);
} else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
AddJoin(op1, op2, horzEdge->Top);
}
} else
UpdateEdgeIntoAEL(horzEdge);
} else {
if (horzEdge->OutIdx >= 0)
AddOutPt(horzEdge, horzEdge->Top);
DeleteFromAEL(horzEdge);
}
}
//------------------------------------------------------------------------------
bool Clipper::ProcessIntersections(const cInt topY) {
if (!m_ActiveEdges)
return true;
try {
BuildIntersectList(topY);
size_t IlSize = m_IntersectList.size();
if (IlSize == 0)
return true;
if (IlSize == 1 || FixupIntersectionOrder())
ProcessIntersectList();
else
return false;
} catch (...) {
m_SortedEdges = 0;
DisposeIntersectNodes();
throw clipperException("ProcessIntersections error");
}
m_SortedEdges = 0;
return true;
}
//------------------------------------------------------------------------------
void Clipper::DisposeIntersectNodes() {
for (size_t i = 0; i < m_IntersectList.size(); ++i)
delete m_IntersectList[i];
m_IntersectList.clear();
}
//------------------------------------------------------------------------------
void Clipper::BuildIntersectList(const cInt topY) {
if (!m_ActiveEdges)
return;
// prepare for sorting ...
TEdge *e = m_ActiveEdges;
m_SortedEdges = e;
while (e) {
e->PrevInSEL = e->PrevInAEL;
e->NextInSEL = e->NextInAEL;
e->Curr.X = TopX(*e, topY);
e = e->NextInAEL;
}
// bubblesort ...
bool isModified;
do {
isModified = false;
e = m_SortedEdges;
while (e->NextInSEL) {
TEdge *eNext = e->NextInSEL;
IntPoint Pt;
if (e->Curr.X > eNext->Curr.X) {
IntersectPoint(*e, *eNext, Pt);
if (Pt.Y < topY)
Pt = IntPoint(TopX(*e, topY), topY);
IntersectNode *newNode = new IntersectNode;
newNode->Edge1 = e;
newNode->Edge2 = eNext;
newNode->Pt = Pt;
m_IntersectList.push_back(newNode);
SwapPositionsInSEL(e, eNext);
isModified = true;
} else
e = eNext;
}
if (e->PrevInSEL)
e->PrevInSEL->NextInSEL = 0;
else
break;
} while (isModified);
m_SortedEdges = 0; // important
}
//------------------------------------------------------------------------------
void Clipper::ProcessIntersectList() {
for (size_t i = 0; i < m_IntersectList.size(); ++i) {
IntersectNode *iNode = m_IntersectList[i];
{
IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
}
delete iNode;
}
m_IntersectList.clear();
}
//------------------------------------------------------------------------------
bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) {
return node2->Pt.Y < node1->Pt.Y;
}
//------------------------------------------------------------------------------
inline bool EdgesAdjacent(const IntersectNode &inode) {
return (inode.Edge1->NextInSEL == inode.Edge2) ||
(inode.Edge1->PrevInSEL == inode.Edge2);
}
//------------------------------------------------------------------------------
bool Clipper::FixupIntersectionOrder() {
// pre-condition: intersections are sorted Bottom-most first.
// Now it's crucial that intersections are made only between adjacent edges,
// so to ensure this the order of intersections may need adjusting ...
CopyAELToSEL();
std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
size_t cnt = m_IntersectList.size();
for (size_t i = 0; i < cnt; ++i) {
if (!EdgesAdjacent(*m_IntersectList[i])) {
size_t j = i + 1;
while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
j++;
if (j == cnt)
return false;
std::swap(m_IntersectList[i], m_IntersectList[j]);
}
SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
}
return true;
}
//------------------------------------------------------------------------------
void Clipper::DoMaxima(TEdge *e) {
TEdge *eMaxPair = GetMaximaPairEx(e);
if (!eMaxPair) {
if (e->OutIdx >= 0)
AddOutPt(e, e->Top);
DeleteFromAEL(e);
return;
}
TEdge *eNext = e->NextInAEL;
while (eNext && eNext != eMaxPair) {
IntersectEdges(e, eNext, e->Top);
SwapPositionsInAEL(e, eNext);
eNext = e->NextInAEL;
}
if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
DeleteFromAEL(e);
DeleteFromAEL(eMaxPair);
} else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
if (e->OutIdx >= 0)
AddLocalMaxPoly(e, eMaxPair, e->Top);
DeleteFromAEL(e);
DeleteFromAEL(eMaxPair);
}
#ifdef use_lines
else if (e->WindDelta == 0) {
if (e->OutIdx >= 0) {
AddOutPt(e, e->Top);
e->OutIdx = Unassigned;
}
DeleteFromAEL(e);
if (eMaxPair->OutIdx >= 0) {
AddOutPt(eMaxPair, e->Top);
eMaxPair->OutIdx = Unassigned;
}
DeleteFromAEL(eMaxPair);
}
#endif
else
throw clipperException("DoMaxima error");
}
//------------------------------------------------------------------------------
void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) {
TEdge *e = m_ActiveEdges;
while (e) {
// 1. process maxima, treating them as if they're 'bent' horizontal edges,
// but exclude maxima with horizontal edges. nb: e can't be a horizontal.
bool IsMaximaEdge = IsMaxima(e, topY);
if (IsMaximaEdge) {
TEdge *eMaxPair = GetMaximaPairEx(e);
IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
}
if (IsMaximaEdge) {
if (m_StrictSimple)
m_Maxima.push_back(e->Top.X);
TEdge *ePrev = e->PrevInAEL;
DoMaxima(e);
if (!ePrev)
e = m_ActiveEdges;
else
e = ePrev->NextInAEL;
} else {
// 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
UpdateEdgeIntoAEL(e);
if (e->OutIdx >= 0)
AddOutPt(e, e->Bot);
AddEdgeToSEL(e);
} else {
e->Curr.X = TopX(*e, topY);
e->Curr.Y = topY;
#ifdef use_xyz
e->Curr.Z =
topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
#endif
}
// When StrictlySimple and 'e' is being touched by another edge, then
// make sure both edges have a vertex here ...
if (m_StrictSimple) {
TEdge *ePrev = e->PrevInAEL;
if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev &&
(ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) &&
(ePrev->WindDelta != 0)) {
IntPoint pt = e->Curr;
#ifdef use_xyz
SetZ(pt, *ePrev, *e);
#endif
OutPt *op = AddOutPt(ePrev, pt);
OutPt *op2 = AddOutPt(e, pt);
AddJoin(op, op2, pt); // StrictlySimple (type-3) join
}
}
e = e->NextInAEL;
}
}
// 3. Process horizontals at the Top of the scanbeam ...
m_Maxima.sort();
ProcessHorizontals();
m_Maxima.clear();
// 4. Promote intermediate vertices ...
e = m_ActiveEdges;
while (e) {
if (IsIntermediate(e, topY)) {
OutPt *op = 0;
if (e->OutIdx >= 0)
op = AddOutPt(e, e->Top);
UpdateEdgeIntoAEL(e);
// if output polygons share an edge, they'll need joining later ...
TEdge *ePrev = e->PrevInAEL;
TEdge *eNext = e->NextInAEL;
if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y &&
op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top,
m_UseFullRange) &&
(e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
OutPt *op2 = AddOutPt(ePrev, e->Bot);
AddJoin(op, op2, e->Top);
} else if (eNext && eNext->Curr.X == e->Bot.X &&
eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 &&
eNext->Curr.Y > eNext->Top.Y &&
SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top,
m_UseFullRange) &&
(e->WindDelta != 0) && (eNext->WindDelta != 0)) {
OutPt *op2 = AddOutPt(eNext, e->Bot);
AddJoin(op, op2, e->Top);
}
}
e = e->NextInAEL;
}
}
//------------------------------------------------------------------------------
void Clipper::FixupOutPolyline(OutRec &outrec) {
OutPt *pp = outrec.Pts;
OutPt *lastPP = pp->Prev;
while (pp != lastPP) {
pp = pp->Next;
if (pp->Pt == pp->Prev->Pt) {
if (pp == lastPP)
lastPP = pp->Prev;
OutPt *tmpPP = pp->Prev;
tmpPP->Next = pp->Next;
pp->Next->Prev = tmpPP;
delete pp;
pp = tmpPP;
}
}
if (pp == pp->Prev) {
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
}
//------------------------------------------------------------------------------
void Clipper::FixupOutPolygon(OutRec &outrec) {
// FixupOutPolygon() - removes duplicate points and simplifies consecutive
// parallel edges by removing the middle vertex.
OutPt *lastOK = 0;
outrec.BottomPt = 0;
OutPt *pp = outrec.Pts;
bool preserveCol = m_PreserveCollinear || m_StrictSimple;
for (;;) {
if (pp->Prev == pp || pp->Prev == pp->Next) {
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
// test for duplicate points and collinear edges ...
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
(!preserveCol ||
!Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
lastOK = 0;
OutPt *tmp = pp;
pp->Prev->Next = pp->Next;
pp->Next->Prev = pp->Prev;
pp = pp->Prev;
delete tmp;
} else if (pp == lastOK)
break;
else {
if (!lastOK)
lastOK = pp;
pp = pp->Next;
}
}
outrec.Pts = pp;
}
//------------------------------------------------------------------------------
int PointCount(OutPt *Pts) {
if (!Pts)
return 0;
int result = 0;
OutPt *p = Pts;
do {
result++;
p = p->Next;
} while (p != Pts);
return result;
}
//------------------------------------------------------------------------------
void Clipper::BuildResult(Paths &polys) {
polys.reserve(m_PolyOuts.size());
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
if (!m_PolyOuts[i]->Pts)
continue;
Path pg;
OutPt *p = m_PolyOuts[i]->Pts->Prev;
int cnt = PointCount(p);
if (cnt < 2)
continue;
pg.reserve(cnt);
for (int i = 0; i < cnt; ++i) {
pg.push_back(p->Pt);
p = p->Prev;
}
polys.push_back(pg);
}
}
//------------------------------------------------------------------------------
void Clipper::BuildResult2(PolyTree &polytree) {
polytree.Clear();
polytree.AllNodes.reserve(m_PolyOuts.size());
// add each output polygon/contour to polytree ...
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
OutRec *outRec = m_PolyOuts[i];
int cnt = PointCount(outRec->Pts);
if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
continue;
FixHoleLinkage(*outRec);
PolyNode *pn = new PolyNode();
// nb: polytree takes ownership of all the PolyNodes
polytree.AllNodes.push_back(pn);
outRec->PolyNd = pn;
pn->Parent = 0;
pn->Index = 0;
pn->Contour.reserve(cnt);
OutPt *op = outRec->Pts->Prev;
for (int j = 0; j < cnt; j++) {
pn->Contour.push_back(op->Pt);
op = op->Prev;
}
}
// fixup PolyNode links etc ...
polytree.Childs.reserve(m_PolyOuts.size());
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->PolyNd)
continue;
if (outRec->IsOpen) {
outRec->PolyNd->m_IsOpen = true;
polytree.AddChild(*outRec->PolyNd);
} else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
else
polytree.AddChild(*outRec->PolyNd);
}
}
//------------------------------------------------------------------------------
void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) {
// just swap the contents (because fIntersectNodes is a single-linked-list)
IntersectNode inode = int1; // gets a copy of Int1
int1.Edge1 = int2.Edge1;
int1.Edge2 = int2.Edge2;
int1.Pt = int2.Pt;
int2.Edge1 = inode.Edge1;
int2.Edge2 = inode.Edge2;
int2.Pt = inode.Pt;
}
//------------------------------------------------------------------------------
inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) {
if (e2.Curr.X == e1.Curr.X) {
if (e2.Top.Y > e1.Top.Y)
return e2.Top.X < TopX(e1, e2.Top.Y);
else
return e1.Top.X > TopX(e2, e1.Top.Y);
} else
return e2.Curr.X < e1.Curr.X;
}
//------------------------------------------------------------------------------
bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
cInt &Left, cInt &Right) {
if (a1 < a2) {
if (b1 < b2) {
Left = std::max(a1, b1);
Right = std::min(a2, b2);
} else {
Left = std::max(a1, b2);
Right = std::min(a2, b1);
}
} else {
if (b1 < b2) {
Left = std::max(a2, b1);
Right = std::min(a1, b2);
} else {
Left = std::max(a2, b2);
Right = std::min(a1, b1);
}
}
return Left < Right;
}
//------------------------------------------------------------------------------
inline void UpdateOutPtIdxs(OutRec &outrec) {
OutPt *op = outrec.Pts;
do {
op->Idx = outrec.Idx;
op = op->Prev;
} while (op != outrec.Pts);
}
//------------------------------------------------------------------------------
void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) {
if (!m_ActiveEdges) {
edge->PrevInAEL = 0;
edge->NextInAEL = 0;
m_ActiveEdges = edge;
} else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
edge->PrevInAEL = 0;
edge->NextInAEL = m_ActiveEdges;
m_ActiveEdges->PrevInAEL = edge;
m_ActiveEdges = edge;
} else {
if (!startEdge)
startEdge = m_ActiveEdges;
while (startEdge->NextInAEL &&
!E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
startEdge = startEdge->NextInAEL;
edge->NextInAEL = startEdge->NextInAEL;
if (startEdge->NextInAEL)
startEdge->NextInAEL->PrevInAEL = edge;
edge->PrevInAEL = startEdge;
startEdge->NextInAEL = edge;
}
}
//----------------------------------------------------------------------
OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) {
OutPt *result = new OutPt;
result->Pt = outPt->Pt;
result->Idx = outPt->Idx;
if (InsertAfter) {
result->Next = outPt->Next;
result->Prev = outPt;
outPt->Next->Prev = result;
outPt->Next = result;
} else {
result->Prev = outPt->Prev;
result->Next = outPt;
outPt->Prev->Next = result;
outPt->Prev = result;
}
return result;
}
//------------------------------------------------------------------------------
bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b,
const IntPoint Pt, bool DiscardLeft) {
Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
if (Dir1 == Dir2)
return false;
// When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
// want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
// So, to facilitate this while inserting Op1b and Op2b ...
// when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
// otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
if (Dir1 == dLeftToRight) {
while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X &&
op1->Next->Pt.Y == Pt.Y)
op1 = op1->Next;
if (DiscardLeft && (op1->Pt.X != Pt.X))
op1 = op1->Next;
op1b = DupOutPt(op1, !DiscardLeft);
if (op1b->Pt != Pt) {
op1 = op1b;
op1->Pt = Pt;
op1b = DupOutPt(op1, !DiscardLeft);
}
} else {
while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X &&
op1->Next->Pt.Y == Pt.Y)
op1 = op1->Next;
if (!DiscardLeft && (op1->Pt.X != Pt.X))
op1 = op1->Next;
op1b = DupOutPt(op1, DiscardLeft);
if (op1b->Pt != Pt) {
op1 = op1b;
op1->Pt = Pt;
op1b = DupOutPt(op1, DiscardLeft);
}
}
if (Dir2 == dLeftToRight) {
while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X &&
op2->Next->Pt.Y == Pt.Y)
op2 = op2->Next;
if (DiscardLeft && (op2->Pt.X != Pt.X))
op2 = op2->Next;
op2b = DupOutPt(op2, !DiscardLeft);
if (op2b->Pt != Pt) {
op2 = op2b;
op2->Pt = Pt;
op2b = DupOutPt(op2, !DiscardLeft);
};
} else {
while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X &&
op2->Next->Pt.Y == Pt.Y)
op2 = op2->Next;
if (!DiscardLeft && (op2->Pt.X != Pt.X))
op2 = op2->Next;
op2b = DupOutPt(op2, DiscardLeft);
if (op2b->Pt != Pt) {
op2 = op2b;
op2->Pt = Pt;
op2b = DupOutPt(op2, DiscardLeft);
};
};
if ((Dir1 == dLeftToRight) == DiscardLeft) {
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
} else {
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
}
return true;
}
//------------------------------------------------------------------------------
bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) {
OutPt *op1 = j->OutPt1, *op1b;
OutPt *op2 = j->OutPt2, *op2b;
// There are 3 kinds of joins for output polygons ...
// 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
// along (horizontal) collinear edges (& Join.OffPt is on the same
// horizontal).
// 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
// location at the Bottom of the overlapping segment (& Join.OffPt is above).
// 3. StrictSimple joins where edges touch but are not collinear and where
// Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
(j->OffPt == j->OutPt2->Pt)) {
// Strictly Simple join ...
if (outRec1 != outRec2)
return false;
op1b = j->OutPt1->Next;
while (op1b != op1 && (op1b->Pt == j->OffPt))
op1b = op1b->Next;
bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
op2b = j->OutPt2->Next;
while (op2b != op2 && (op2b->Pt == j->OffPt))
op2b = op2b->Next;
bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
if (reverse1 == reverse2)
return false;
if (reverse1) {
op1b = DupOutPt(op1, false);
op2b = DupOutPt(op2, true);
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
} else {
op1b = DupOutPt(op1, true);
op2b = DupOutPt(op2, false);
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
}
} else if (isHorizontal) {
// treat horizontal joins differently to non-horizontal joins since with
// them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
// may be anywhere along the horizontal edge.
op1b = op1;
while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b &&
op1->Prev != op2)
op1 = op1->Prev;
while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 &&
op1b->Next != op2)
op1b = op1b->Next;
if (op1b->Next == op1 || op1b->Next == op2)
return false; // a flat 'polygon'
op2b = op2;
while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b &&
op2->Prev != op1b)
op2 = op2->Prev;
while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 &&
op2b->Next != op1)
op2b = op2b->Next;
if (op2b->Next == op2 || op2b->Next == op1)
return false; // a flat 'polygon'
cInt Left, Right;
// Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
return false;
// DiscardLeftSide: when overlapping edges are joined, a spike will created
// which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
// on the discard Side as either may still be needed for other joins ...
IntPoint Pt;
bool DiscardLeftSide;
if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
Pt = op1->Pt;
DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
} else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
Pt = op2->Pt;
DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
} else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
Pt = op1b->Pt;
DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
} else {
Pt = op2b->Pt;
DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
}
j->OutPt1 = op1;
j->OutPt2 = op2;
return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
} else {
// nb: For non-horizontal joins ...
// 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
// 2. Jr.OutPt1.Pt > Jr.OffPt.Y
// make sure the polygons are correctly oriented ...
op1b = op1->Next;
while ((op1b->Pt == op1->Pt) && (op1b != op1))
op1b = op1b->Next;
bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
!SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
if (Reverse1) {
op1b = op1->Prev;
while ((op1b->Pt == op1->Pt) && (op1b != op1))
op1b = op1b->Prev;
if ((op1b->Pt.Y > op1->Pt.Y) ||
!SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
return false;
};
op2b = op2->Next;
while ((op2b->Pt == op2->Pt) && (op2b != op2))
op2b = op2b->Next;
bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
!SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
if (Reverse2) {
op2b = op2->Prev;
while ((op2b->Pt == op2->Pt) && (op2b != op2))
op2b = op2b->Prev;
if ((op2b->Pt.Y > op2->Pt.Y) ||
!SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
return false;
}
if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
((outRec1 == outRec2) && (Reverse1 == Reverse2)))
return false;
if (Reverse1) {
op1b = DupOutPt(op1, false);
op2b = DupOutPt(op2, true);
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
} else {
op1b = DupOutPt(op1, true);
op2b = DupOutPt(op2, false);
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
}
}
}
//----------------------------------------------------------------------
static OutRec *ParseFirstLeft(OutRec *FirstLeft) {
while (FirstLeft && !FirstLeft->Pts)
FirstLeft = FirstLeft->FirstLeft;
return FirstLeft;
}
//------------------------------------------------------------------------------
void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) {
// tests if NewOutRec contains the polygon before reassigning FirstLeft
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (outRec->Pts && firstLeft == OldOutRec) {
if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
outRec->FirstLeft = NewOutRec;
}
}
}
//----------------------------------------------------------------------
void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) {
// A polygon has split into two such that one is now the inner of the other.
// It's possible that these polygons now wrap around other polygons, so check
// every polygon that's also contained by OuterOutRec's FirstLeft container
//(including 0) to see if they've become inner to the new inner polygon ...
OutRec *orfl = OuterOutRec->FirstLeft;
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
continue;
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (firstLeft != orfl && firstLeft != InnerOutRec &&
firstLeft != OuterOutRec)
continue;
if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
outRec->FirstLeft = InnerOutRec;
else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
outRec->FirstLeft = OuterOutRec;
else if (outRec->FirstLeft == InnerOutRec ||
outRec->FirstLeft == OuterOutRec)
outRec->FirstLeft = orfl;
}
}
//----------------------------------------------------------------------
void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) {
// reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (outRec->Pts && firstLeft == OldOutRec)
outRec->FirstLeft = NewOutRec;
}
}
//----------------------------------------------------------------------
void Clipper::JoinCommonEdges() {
for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
Join *join = m_Joins[i];
OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
if (!outRec1->Pts || !outRec2->Pts)
continue;
if (outRec1->IsOpen || outRec2->IsOpen)
continue;
// get the polygon fragment with the correct hole state (FirstLeft)
// before calling JoinPoints() ...
OutRec *holeStateRec;
if (outRec1 == outRec2)
holeStateRec = outRec1;
else if (OutRec1RightOfOutRec2(outRec1, outRec2))
holeStateRec = outRec2;
else if (OutRec1RightOfOutRec2(outRec2, outRec1))
holeStateRec = outRec1;
else
holeStateRec = GetLowermostRec(outRec1, outRec2);
if (!JoinPoints(join, outRec1, outRec2))
continue;
if (outRec1 == outRec2) {
// instead of joining two polygons, we've just created a new one by
// splitting one polygon into two.
outRec1->Pts = join->OutPt1;
outRec1->BottomPt = 0;
outRec2 = CreateOutRec();
outRec2->Pts = join->OutPt2;
// update all OutRec2.Pts Idx's ...
UpdateOutPtIdxs(*outRec2);
if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
// outRec1 contains outRec2 ...
outRec2->IsHole = !outRec1->IsHole;
outRec2->FirstLeft = outRec1;
if (m_UsingPolyTree)
FixupFirstLefts2(outRec2, outRec1);
if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
ReversePolyPtLinks(outRec2->Pts);
} else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
// outRec2 contains outRec1 ...
outRec2->IsHole = outRec1->IsHole;
outRec1->IsHole = !outRec2->IsHole;
outRec2->FirstLeft = outRec1->FirstLeft;
outRec1->FirstLeft = outRec2;
if (m_UsingPolyTree)
FixupFirstLefts2(outRec1, outRec2);
if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
ReversePolyPtLinks(outRec1->Pts);
} else {
// the 2 polygons are completely separate ...
outRec2->IsHole = outRec1->IsHole;
outRec2->FirstLeft = outRec1->FirstLeft;
// fixup FirstLeft pointers that may need reassigning to OutRec2
if (m_UsingPolyTree)
FixupFirstLefts1(outRec1, outRec2);
}
} else {
// joined 2 polygons together ...
outRec2->Pts = 0;
outRec2->BottomPt = 0;
outRec2->Idx = outRec1->Idx;
outRec1->IsHole = holeStateRec->IsHole;
if (holeStateRec == outRec2)
outRec1->FirstLeft = outRec2->FirstLeft;
outRec2->FirstLeft = outRec1;
if (m_UsingPolyTree)
FixupFirstLefts3(outRec2, outRec1);
}
}
}
//------------------------------------------------------------------------------
// ClipperOffset support functions ...
//------------------------------------------------------------------------------
DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
if (pt2.X == pt1.X && pt2.Y == pt1.Y)
return DoublePoint(0, 0);
double Dx = (double)(pt2.X - pt1.X);
double dy = (double)(pt2.Y - pt1.Y);
double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy);
Dx *= f;
dy *= f;
return DoublePoint(dy, -Dx);
}
//------------------------------------------------------------------------------
// ClipperOffset class
//------------------------------------------------------------------------------
ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
this->MiterLimit = miterLimit;
this->ArcTolerance = arcTolerance;
m_lowest.X = -1;
}
//------------------------------------------------------------------------------
ClipperOffset::~ClipperOffset() { Clear(); }
//------------------------------------------------------------------------------
void ClipperOffset::Clear() {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
delete m_polyNodes.Childs[i];
m_polyNodes.Childs.clear();
m_lowest.X = -1;
}
//------------------------------------------------------------------------------
void ClipperOffset::AddPath(const Path &path, JoinType joinType,
EndType endType) {
int highI = (int)path.size() - 1;
if (highI < 0)
return;
PolyNode *newNode = new PolyNode();
newNode->m_jointype = joinType;
newNode->m_endtype = endType;
// strip duplicate points from path and also get index to the lowest point ...
if (endType == etClosedLine || endType == etClosedPolygon)
while (highI > 0 && path[0] == path[highI])
highI--;
newNode->Contour.reserve(highI + 1);
newNode->Contour.push_back(path[0]);
int j = 0, k = 0;
for (int i = 1; i <= highI; i++)
if (newNode->Contour[j] != path[i]) {
j++;
newNode->Contour.push_back(path[i]);
if (path[i].Y > newNode->Contour[k].Y ||
(path[i].Y == newNode->Contour[k].Y &&
path[i].X < newNode->Contour[k].X))
k = j;
}
if (endType == etClosedPolygon && j < 2) {
delete newNode;
return;
}
m_polyNodes.AddChild(*newNode);
// if this path's lowest pt is lower than all the others then update m_lowest
if (endType != etClosedPolygon)
return;
if (m_lowest.X < 0)
m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
else {
IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
if (newNode->Contour[k].Y > ip.Y ||
(newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X))
m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
}
}
//------------------------------------------------------------------------------
void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType,
EndType endType) {
for (Paths::size_type i = 0; i < paths.size(); ++i)
AddPath(paths[i], joinType, endType);
}
//------------------------------------------------------------------------------
void ClipperOffset::FixOrientations() {
// fixup orientations of all closed paths if the orientation of the
// closed path with the lowermost vertex is wrong ...
if (m_lowest.X >= 0 &&
!Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedPolygon ||
(node.m_endtype == etClosedLine && Orientation(node.Contour)))
ReversePath(node.Contour);
}
} else {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
ReversePath(node.Contour);
}
}
}
//------------------------------------------------------------------------------
void ClipperOffset::Execute(Paths &solution, double delta) {
solution.clear();
FixOrientations();
DoOffset(delta);
// now clean up 'corners' ...
Clipper clpr;
clpr.AddPaths(m_destPolys, ptSubject, true);
if (delta > 0) {
clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
} else {
IntRect r = clpr.GetBounds();
Path outer(4);
outer[0] = IntPoint(r.left - 10, r.bottom + 10);
outer[1] = IntPoint(r.right + 10, r.bottom + 10);
outer[2] = IntPoint(r.right + 10, r.top - 10);
outer[3] = IntPoint(r.left - 10, r.top - 10);
clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
if (solution.size() > 0)
solution.erase(solution.begin());
}
}
//------------------------------------------------------------------------------
void ClipperOffset::Execute(PolyTree &solution, double delta) {
solution.Clear();
FixOrientations();
DoOffset(delta);
// now clean up 'corners' ...
Clipper clpr;
clpr.AddPaths(m_destPolys, ptSubject, true);
if (delta > 0) {
clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
} else {
IntRect r = clpr.GetBounds();
Path outer(4);
outer[0] = IntPoint(r.left - 10, r.bottom + 10);
outer[1] = IntPoint(r.right + 10, r.bottom + 10);
outer[2] = IntPoint(r.right + 10, r.top - 10);
outer[3] = IntPoint(r.left - 10, r.top - 10);
clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
// remove the outer PolyNode rectangle ...
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
PolyNode *outerNode = solution.Childs[0];
solution.Childs.reserve(outerNode->ChildCount());
solution.Childs[0] = outerNode->Childs[0];
solution.Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
solution.AddChild(*outerNode->Childs[i]);
} else
solution.Clear();
}
}
//------------------------------------------------------------------------------
void ClipperOffset::DoOffset(double delta) {
m_destPolys.clear();
m_delta = delta;
// if Zero offset, just copy any CLOSED polygons to m_p and return ...
if (NEAR_ZERO(delta)) {
m_destPolys.reserve(m_polyNodes.ChildCount());
for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedPolygon)
m_destPolys.push_back(node.Contour);
}
return;
}
// see offset_triginometry3.svg in the documentation folder ...
if (MiterLimit > 2)
m_miterLim = 2 / (MiterLimit * MiterLimit);
else
m_miterLim = 0.5;
double y;
if (ArcTolerance <= 0.0)
y = def_arc_tolerance;
else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
y = std::fabs(delta) * def_arc_tolerance;
else
y = ArcTolerance;
// see offset_triginometry2.svg in the documentation folder ...
double steps = pi / std::acos(1 - y / std::fabs(delta));
if (steps > std::fabs(delta) * pi)
steps = std::fabs(delta) * pi; // ie excessive precision check
m_sin = std::sin(two_pi / steps);
m_cos = std::cos(two_pi / steps);
m_StepsPerRad = steps / two_pi;
if (delta < 0.0)
m_sin = -m_sin;
m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
PolyNode &node = *m_polyNodes.Childs[i];
m_srcPoly = node.Contour;
int len = (int)m_srcPoly.size();
if (len == 0 ||
(delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
continue;
m_destPoly.clear();
if (len == 1) {
if (node.m_jointype == jtRound) {
double X = 1.0, Y = 0.0;
for (cInt j = 1; j <= steps; j++) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
Round(m_srcPoly[0].Y + Y * delta)));
double X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
}
} else {
double X = -1.0, Y = -1.0;
for (int j = 0; j < 4; ++j) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
Round(m_srcPoly[0].Y + Y * delta)));
if (X < 0)
X = 1;
else if (Y < 0)
Y = 1;
else
X = -1;
}
}
m_destPolys.push_back(m_destPoly);
continue;
}
// build m_normals ...
m_normals.clear();
m_normals.reserve(len);
for (int j = 0; j < len - 1; ++j)
m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
else
m_normals.push_back(DoublePoint(m_normals[len - 2]));
if (node.m_endtype == etClosedPolygon) {
int k = len - 1;
for (int j = 0; j < len; ++j)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
} else if (node.m_endtype == etClosedLine) {
int k = len - 1;
for (int j = 0; j < len; ++j)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
m_destPoly.clear();
// re-build m_normals ...
DoublePoint n = m_normals[len - 1];
for (int j = len - 1; j > 0; j--)
m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
m_normals[0] = DoublePoint(-n.X, -n.Y);
k = 0;
for (int j = len - 1; j >= 0; j--)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
} else {
int k = 0;
for (int j = 1; j < len - 1; ++j)
OffsetPoint(j, k, node.m_jointype);
IntPoint pt1;
if (node.m_endtype == etOpenButt) {
int j = len - 1;
pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta),
(cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
m_destPoly.push_back(pt1);
pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta),
(cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
m_destPoly.push_back(pt1);
} else {
int j = len - 1;
k = len - 2;
m_sinA = 0;
m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
if (node.m_endtype == etOpenSquare)
DoSquare(j, k);
else
DoRound(j, k);
}
// re-build m_normals ...
for (int j = len - 1; j > 0; j--)
m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
k = len - 1;
for (int j = k - 1; j > 0; --j)
OffsetPoint(j, k, node.m_jointype);
if (node.m_endtype == etOpenButt) {
pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta),
(cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
m_destPoly.push_back(pt1);
pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta),
(cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
m_destPoly.push_back(pt1);
} else {
k = 1;
m_sinA = 0;
if (node.m_endtype == etOpenSquare)
DoSquare(0, 1);
else
DoRound(0, 1);
}
m_destPolys.push_back(m_destPoly);
}
}
}
//------------------------------------------------------------------------------
void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) {
// cross product ...
m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
if (std::fabs(m_sinA * m_delta) < 1.0) {
// dot product ...
double cosA =
(m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
if (cosA > 0) // angle => 0 degrees
{
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
return;
}
// else angle => 180 degrees
} else if (m_sinA > 1.0)
m_sinA = 1.0;
else if (m_sinA < -1.0)
m_sinA = -1.0;
if (m_sinA * m_delta < 0) {
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
m_destPoly.push_back(m_srcPoly[j]);
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
} else
switch (jointype) {
case jtMiter: {
double r = 1 + (m_normals[j].X * m_normals[k].X +
m_normals[j].Y * m_normals[k].Y);
if (r >= m_miterLim)
DoMiter(j, k, r);
else
DoSquare(j, k);
break;
}
case jtSquare:
DoSquare(j, k);
break;
case jtRound:
DoRound(j, k);
break;
}
k = j;
}
//------------------------------------------------------------------------------
void ClipperOffset::DoSquare(int j, int k) {
double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
m_normals[k].Y * m_normals[j].Y) /
4);
m_destPoly.push_back(IntPoint(
Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
Round(m_srcPoly[j].Y +
m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
m_destPoly.push_back(IntPoint(
Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
Round(m_srcPoly[j].Y +
m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
}
//------------------------------------------------------------------------------
void ClipperOffset::DoMiter(int j, int k, double r) {
double q = m_delta / r;
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
}
//------------------------------------------------------------------------------
void ClipperOffset::DoRound(int j, int k) {
double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
m_normals[k].Y * m_normals[j].Y);
int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1);
double X = m_normals[k].X, Y = m_normals[k].Y, X2;
for (int i = 0; i < steps; ++i) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + X * m_delta),
Round(m_srcPoly[j].Y + Y * m_delta)));
X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
}
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
}
//------------------------------------------------------------------------------
// Miscellaneous public functions
//------------------------------------------------------------------------------
void Clipper::DoSimplePolygons() {
PolyOutList::size_type i = 0;
while (i < m_PolyOuts.size()) {
OutRec *outrec = m_PolyOuts[i++];
OutPt *op = outrec->Pts;
if (!op || outrec->IsOpen)
continue;
do // for each Pt in Polygon until duplicate found do ...
{
OutPt *op2 = op->Next;
while (op2 != outrec->Pts) {
if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
// split the polygon into two ...
OutPt *op3 = op->Prev;
OutPt *op4 = op2->Prev;
op->Prev = op4;
op4->Next = op;
op2->Prev = op3;
op3->Next = op2;
outrec->Pts = op;
OutRec *outrec2 = CreateOutRec();
outrec2->Pts = op2;
UpdateOutPtIdxs(*outrec2);
if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
// OutRec2 is contained by OutRec1 ...
outrec2->IsHole = !outrec->IsHole;
outrec2->FirstLeft = outrec;
if (m_UsingPolyTree)
FixupFirstLefts2(outrec2, outrec);
} else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
// OutRec1 is contained by OutRec2 ...
outrec2->IsHole = outrec->IsHole;
outrec->IsHole = !outrec2->IsHole;
outrec2->FirstLeft = outrec->FirstLeft;
outrec->FirstLeft = outrec2;
if (m_UsingPolyTree)
FixupFirstLefts2(outrec, outrec2);
} else {
// the 2 polygons are separate ...
outrec2->IsHole = outrec->IsHole;
outrec2->FirstLeft = outrec->FirstLeft;
if (m_UsingPolyTree)
FixupFirstLefts1(outrec, outrec2);
}
op2 = op; // ie get ready for the Next iteration
}
op2 = op2->Next;
}
op = op->Next;
} while (op != outrec->Pts);
}
}
//------------------------------------------------------------------------------
void ReversePath(Path &p) { std::reverse(p.begin(), p.end()); }
//------------------------------------------------------------------------------
void ReversePaths(Paths &p) {
for (Paths::size_type i = 0; i < p.size(); ++i)
ReversePath(p[i]);
}
//------------------------------------------------------------------------------
void SimplifyPolygon(const Path &in_poly, Paths &out_polys,
PolyFillType fillType) {
Clipper c;
c.StrictlySimple(true);
c.AddPath(in_poly, ptSubject, true);
c.Execute(ctUnion, out_polys, fillType, fillType);
}
//------------------------------------------------------------------------------
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys,
PolyFillType fillType) {
Clipper c;
c.StrictlySimple(true);
c.AddPaths(in_polys, ptSubject, true);
c.Execute(ctUnion, out_polys, fillType, fillType);
}
//------------------------------------------------------------------------------
void SimplifyPolygons(Paths &polys, PolyFillType fillType) {
SimplifyPolygons(polys, polys, fillType);
}
//------------------------------------------------------------------------------
inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) {
double Dx = ((double)pt1.X - pt2.X);
double dy = ((double)pt1.Y - pt2.Y);
return (Dx * Dx + dy * dy);
}
//------------------------------------------------------------------------------
double DistanceFromLineSqrd(const IntPoint &pt, const IntPoint &ln1,
const IntPoint &ln2) {
// The equation of a line in general form (Ax + By + C = 0)
// given 2 points (x�,y�) & (x�,y�) is ...
//(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
// A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
// perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
// see http://en.wikipedia.org/wiki/Perpendicular_distance
double A = double(ln1.Y - ln2.Y);
double B = double(ln2.X - ln1.X);
double C = A * ln1.X + B * ln1.Y;
C = A * pt.X + B * pt.Y - C;
return (C * C) / (A * A + B * B);
}
//---------------------------------------------------------------------------
bool SlopesNearCollinear(const IntPoint &pt1, const IntPoint &pt2,
const IntPoint &pt3, double distSqrd) {
// this function is more accurate when the point that's geometrically
// between the other 2 points is the one that's tested for distance.
// ie makes it more likely to pick up 'spikes' ...
if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
else
return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
} else {
if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
else
return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
}
}
//------------------------------------------------------------------------------
bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) {
double Dx = (double)pt1.X - pt2.X;
double dy = (double)pt1.Y - pt2.Y;
return ((Dx * Dx) + (dy * dy) <= distSqrd);
}
//------------------------------------------------------------------------------
OutPt *ExcludeOp(OutPt *op) {
OutPt *result = op->Prev;
result->Next = op->Next;
op->Next->Prev = result;
result->Idx = 0;
return result;
}
//------------------------------------------------------------------------------
void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) {
// distance = proximity in units/pixels below which vertices
// will be stripped. Default ~= sqrt(2).
size_t size = in_poly.size();
if (size == 0) {
out_poly.clear();
return;
}
OutPt *outPts = new OutPt[size];
for (size_t i = 0; i < size; ++i) {
outPts[i].Pt = in_poly[i];
outPts[i].Next = &outPts[(i + 1) % size];
outPts[i].Next->Prev = &outPts[i];
outPts[i].Idx = 0;
}
double distSqrd = distance * distance;
OutPt *op = &outPts[0];
while (op->Idx == 0 && op->Next != op->Prev) {
if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
op = ExcludeOp(op);
size--;
} else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
ExcludeOp(op->Next);
op = ExcludeOp(op);
size -= 2;
} else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt,
distSqrd)) {
op = ExcludeOp(op);
size--;
} else {
op->Idx = 1;
op = op->Next;
}
}
if (size < 3)
size = 0;
out_poly.resize(size);
for (size_t i = 0; i < size; ++i) {
out_poly[i] = op->Pt;
op = op->Next;
}
delete[] outPts;
}
//------------------------------------------------------------------------------
void CleanPolygon(Path &poly, double distance) {
CleanPolygon(poly, poly, distance);
}
//------------------------------------------------------------------------------
void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) {
out_polys.resize(in_polys.size());
for (Paths::size_type i = 0; i < in_polys.size(); ++i)
CleanPolygon(in_polys[i], out_polys[i], distance);
}
//------------------------------------------------------------------------------
void CleanPolygons(Paths &polys, double distance) {
CleanPolygons(polys, polys, distance);
}
//------------------------------------------------------------------------------
void Minkowski(const Path &poly, const Path &path, Paths &solution, bool isSum,
bool isClosed) {
int delta = (isClosed ? 1 : 0);
size_t polyCnt = poly.size();
size_t pathCnt = path.size();
Paths pp;
pp.reserve(pathCnt);
if (isSum)
for (size_t i = 0; i < pathCnt; ++i) {
Path p;
p.reserve(polyCnt);
for (size_t j = 0; j < poly.size(); ++j)
p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
pp.push_back(p);
}
else
for (size_t i = 0; i < pathCnt; ++i) {
Path p;
p.reserve(polyCnt);
for (size_t j = 0; j < poly.size(); ++j)
p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
pp.push_back(p);
}
solution.clear();
solution.reserve((pathCnt + delta) * (polyCnt + 1));
for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
for (size_t j = 0; j < polyCnt; ++j) {
Path quad;
quad.reserve(4);
quad.push_back(pp[i % pathCnt][j % polyCnt]);
quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
if (!Orientation(quad))
ReversePath(quad);
solution.push_back(quad);
}
}
//------------------------------------------------------------------------------
void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution,
bool pathIsClosed) {
Minkowski(pattern, path, solution, true, pathIsClosed);
Clipper c;
c.AddPaths(solution, ptSubject, true);
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
void TranslatePath(const Path &input, Path &output, const IntPoint delta) {
// precondition: input != output
output.resize(input.size());
for (size_t i = 0; i < input.size(); ++i)
output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
}
//------------------------------------------------------------------------------
void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution,
bool pathIsClosed) {
Clipper c;
for (size_t i = 0; i < paths.size(); ++i) {
Paths tmp;
Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
c.AddPaths(tmp, ptSubject, true);
if (pathIsClosed) {
Path tmp2;
TranslatePath(paths[i], tmp2, pattern[0]);
c.AddPath(tmp2, ptClip, true);
}
}
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) {
Minkowski(poly1, poly2, solution, false, true);
Clipper c;
c.AddPaths(solution, ptSubject, true);
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
enum NodeType { ntAny, ntOpen, ntClosed };
void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype,
Paths &paths) {
bool match = true;
if (nodetype == ntClosed)
match = !polynode.IsOpen();
else if (nodetype == ntOpen)
return;
if (!polynode.Contour.empty() && match)
paths.push_back(polynode.Contour);
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
}
//------------------------------------------------------------------------------
void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths);
}
//------------------------------------------------------------------------------
void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths);
}
//------------------------------------------------------------------------------
void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
// Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i)
if (polytree.Childs[i]->IsOpen())
paths.push_back(polytree.Childs[i]->Contour);
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const IntPoint &p) {
s << "(" << p.X << "," << p.Y << ")";
return s;
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const Path &p) {
if (p.empty())
return s;
Path::size_type last = p.size() - 1;
for (Path::size_type i = 0; i < last; i++)
s << "(" << p[i].X << "," << p[i].Y << "), ";
s << "(" << p[last].X << "," << p[last].Y << ")\n";
return s;
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const Paths &p) {
for (Paths::size_type i = 0; i < p.size(); i++)
s << p[i];
s << "\n";
return s;
}
//------------------------------------------------------------------------------
} // ClipperLib namespace
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "glog/logging.h"
#include "omp.h"
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <chrono>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <numeric>
#include <glog/logging.h>
#include <include/ocr_det.h>
#include <sys/stat.h>
#include <gflags/gflags.h>
DEFINE_bool(use_gpu, false, "Infering with GPU or CPU.");
DEFINE_int32(gpu_id, 0, "Device id of GPU to execute.");
DEFINE_int32(gpu_mem, 4000, "GPU id when infering with GPU.");
DEFINE_int32(cpu_math_library_num_threads, 10, "Num of threads with CPU.");
DEFINE_bool(use_mkldnn, false, "Whether use mkldnn with CPU.");
DEFINE_string(image_dir, "", "Dir of input image.");
DEFINE_string(det_model_dir, "", "Path of det inference model.");
DEFINE_int32(max_side_len, 960, "max_side_len of input image.");
DEFINE_double(det_db_thresh, 0.3, "Threshold of det_db_thresh.");
DEFINE_double(det_db_box_thresh, 0.5, "Threshold of det_db_box_thresh.");
DEFINE_double(det_db_unclip_ratio, 1.6, "Threshold of det_db_unclip_ratio.");
DEFINE_bool(use_polygon_score, false, "Whether use polygon score.");
DEFINE_bool(visualize, true, "Whether show the detection results.");
DEFINE_bool(use_tensorrt, false, "Whether use tensorrt.");
DEFINE_bool(use_fp16, false, "Whether use fp16 when use tensorrt.");
using namespace std;
using namespace cv;
using namespace PaddleOCR;
static bool PathExists(const std::string& path){
#ifdef _WIN32
struct _stat buffer;
return (_stat(path.c_str(), &buffer) == 0);
#else
struct stat buffer;
return (stat(path.c_str(), &buffer) == 0);
#endif // !_WIN32
}
int main(int argc, char **argv) {
// Parsing command-line
google::ParseCommandLineFlags(&argc, &argv, true);
if (FLAGS_det_model_dir.empty() || FLAGS_image_dir.empty()) {
std::cout << "Usage: ./ocr_det --det_model_dir=/PATH/TO/INFERENCE_MODEL/ "
<< "--image_dir=/PATH/TO/INPUT/IMAGE/" << std::endl;
return -1;
}
if (!PathExists(FLAGS_image_dir)) {
std::cerr << "[ERROR] image path not exist! image_dir: " << FLAGS_image_dir << endl;
exit(1);
}
std::vector<cv::String> cv_all_img_names;
cv::glob(FLAGS_image_dir, cv_all_img_names);
std::cout << "total images num: " << cv_all_img_names.size() << endl;
DBDetector det(FLAGS_det_model_dir, FLAGS_use_gpu, FLAGS_gpu_id,
FLAGS_gpu_mem, FLAGS_cpu_math_library_num_threads,
FLAGS_use_mkldnn, FLAGS_max_side_len, FLAGS_det_db_thresh,
FLAGS_det_db_box_thresh, FLAGS_det_db_unclip_ratio,
FLAGS_use_polygon_score, FLAGS_visualize,
FLAGS_use_tensorrt, FLAGS_use_fp16);
auto start = std::chrono::system_clock::now();
for (int i = 0; i < cv_all_img_names.size(); ++i) {
LOG(INFO) << "The predict img: " << cv_all_img_names[i];
cv::Mat srcimg = cv::imread(cv_all_img_names[i], cv::IMREAD_COLOR);
if (!srcimg.data) {
std::cerr << "[ERROR] image read failed! image path: " << cv_all_img_names[i] << endl;
exit(1);
}
std::vector<std::vector<std::vector<int>>> boxes;
det.Run(srcimg, boxes);
auto end = std::chrono::system_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Cost "
<< double(duration.count()) *
std::chrono::microseconds::period::num /
std::chrono::microseconds::period::den
<< "s" << std::endl;
}
return 0;
}
...@@ -150,7 +150,8 @@ void DBDetector::Run(cv::Mat &img, ...@@ -150,7 +150,8 @@ void DBDetector::Run(cv::Mat &img,
this->det_db_unclip_ratio_, this->use_polygon_score_); this->det_db_unclip_ratio_, this->use_polygon_score_);
boxes = post_processor_.FilterTagDetRes(boxes, ratio_h, ratio_w, srcimg); boxes = post_processor_.FilterTagDetRes(boxes, ratio_h, ratio_w, srcimg);
std::cout << "Detected boxes num: " << boxes.size() << endl;
//// visualization //// visualization
if (this->visualize_) { if (this->visualize_) {
Utility::VisualizeBboxes(srcimg, boxes); Utility::VisualizeBboxes(srcimg, boxes);
......
...@@ -28,67 +28,76 @@ ...@@ -28,67 +28,76 @@
#include <numeric> #include <numeric>
#include <glog/logging.h> #include <glog/logging.h>
#include <include/config.h>
#include <include/ocr_det.h>
#include <include/ocr_rec.h> #include <include/ocr_rec.h>
#include <include/utility.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <gflags/gflags.h>
DEFINE_bool(use_gpu, false, "Infering with GPU or CPU.");
DEFINE_int32(gpu_id, 0, "Device id of GPU to execute.");
DEFINE_int32(gpu_mem, 4000, "GPU id when infering with GPU.");
DEFINE_int32(cpu_math_library_num_threads, 10, "Num of threads with CPU.");
DEFINE_bool(use_mkldnn, false, "Whether use mkldnn with CPU.");
DEFINE_string(image_dir, "", "Dir of input image.");
DEFINE_string(rec_model_dir, "", "Path of rec inference model.");
DEFINE_string(char_list_file, "../../ppocr/utils/ppocr_keys_v1.txt", "Path of dictionary.");
DEFINE_bool(use_tensorrt, false, "Whether use tensorrt.");
DEFINE_bool(use_fp16, false, "Whether use fp16 when use tensorrt.");
using namespace std; using namespace std;
using namespace cv; using namespace cv;
using namespace PaddleOCR; using namespace PaddleOCR;
int main(int argc, char **argv) {
if (argc < 3) {
std::cerr << "[ERROR] usage: " << argv[0]
<< " configure_filepath image_path\n";
exit(1);
}
OCRConfig config(argv[1]);
config.PrintConfigInfo(); static bool PathExists(const std::string& path){
#ifdef _WIN32
struct _stat buffer;
return (_stat(path.c_str(), &buffer) == 0);
#else
struct stat buffer;
return (stat(path.c_str(), &buffer) == 0);
#endif // !_WIN32
}
std::string img_path(argv[2]);
std::vector<std::string> all_img_names;
Utility::GetAllFiles((char *)img_path.c_str(), all_img_names);
DBDetector det(config.det_model_dir, config.use_gpu, config.gpu_id, int main(int argc, char **argv) {
config.gpu_mem, config.cpu_math_library_num_threads, // Parsing command-line
config.use_mkldnn, config.max_side_len, config.det_db_thresh, google::ParseCommandLineFlags(&argc, &argv, true);
config.det_db_box_thresh, config.det_db_unclip_ratio, if (FLAGS_rec_model_dir.empty() || FLAGS_image_dir.empty()) {
config.use_polygon_score, config.visualize, std::cout << "Usage: ./ocr_rec --rec_model_dir=/PATH/TO/INFERENCE_MODEL/ "
config.use_tensorrt, config.use_fp16); << "--image_dir=/PATH/TO/INPUT/IMAGE/" << std::endl;
return -1;
}
Classifier *cls = nullptr; if (!PathExists(FLAGS_image_dir)) {
if (config.use_angle_cls == true) { std::cerr << "[ERROR] image path not exist! image_dir: " << FLAGS_image_dir << endl;
cls = new Classifier(config.cls_model_dir, config.use_gpu, config.gpu_id, exit(1);
config.gpu_mem, config.cpu_math_library_num_threads,
config.use_mkldnn, config.cls_thresh,
config.use_tensorrt, config.use_fp16);
} }
std::vector<cv::String> cv_all_img_names;
cv::glob(FLAGS_image_dir, cv_all_img_names);
std::cout << "total images num: " << cv_all_img_names.size() << endl;
CRNNRecognizer rec(config.rec_model_dir, config.use_gpu, config.gpu_id, CRNNRecognizer rec(FLAGS_rec_model_dir, FLAGS_use_gpu, FLAGS_gpu_id,
config.gpu_mem, config.cpu_math_library_num_threads, FLAGS_gpu_mem, FLAGS_cpu_math_library_num_threads,
config.use_mkldnn, config.char_list_file, FLAGS_use_mkldnn, FLAGS_char_list_file,
config.use_tensorrt, config.use_fp16); FLAGS_use_tensorrt, FLAGS_use_fp16);
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
for (auto img_dir : all_img_names) { for (int i = 0; i < cv_all_img_names.size(); ++i) {
LOG(INFO) << "The predict img: " << img_dir; LOG(INFO) << "The predict img: " << cv_all_img_names[i];
cv::Mat srcimg = cv::imread(img_dir, cv::IMREAD_COLOR); cv::Mat srcimg = cv::imread(cv_all_img_names[i], cv::IMREAD_COLOR);
if (!srcimg.data) { if (!srcimg.data) {
std::cerr << "[ERROR] image read failed! image path: " << img_path std::cerr << "[ERROR] image read failed! image path: " << cv_all_img_names[i] << endl;
<< "\n";
exit(1); exit(1);
} }
std::vector<std::vector<std::vector<int>>> boxes;
det.Run(srcimg, boxes);
rec.Run(boxes, srcimg, cls); rec.Run(srcimg);
auto end = std::chrono::system_clock::now(); auto end = std::chrono::system_clock::now();
auto duration = auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start); std::chrono::duration_cast<std::chrono::microseconds>(end - start);
......
...@@ -16,80 +16,68 @@ ...@@ -16,80 +16,68 @@
namespace PaddleOCR { namespace PaddleOCR {
void CRNNRecognizer::Run(std::vector<std::vector<std::vector<int>>> boxes, void CRNNRecognizer::Run(cv::Mat &img) {
cv::Mat &img, Classifier *cls) {
cv::Mat srcimg; cv::Mat srcimg;
img.copyTo(srcimg); img.copyTo(srcimg);
cv::Mat crop_img;
cv::Mat resize_img; cv::Mat resize_img;
std::cout << "The predicted text is :" << std::endl; float wh_ratio = float(srcimg.cols) / float(srcimg.rows);
int index = 0;
for (int i = 0; i < boxes.size(); i++) {
crop_img = GetRotateCropImage(srcimg, boxes[i]);
if (cls != nullptr) { this->resize_op_.Run(srcimg, resize_img, wh_ratio, this->use_tensorrt_);
crop_img = cls->Run(crop_img);
}
float wh_ratio = float(crop_img.cols) / float(crop_img.rows); this->normalize_op_.Run(&resize_img, this->mean_, this->scale_,
this->is_scale_);
this->resize_op_.Run(crop_img, resize_img, wh_ratio, this->use_tensorrt_);
std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);
this->normalize_op_.Run(&resize_img, this->mean_, this->scale_,
this->is_scale_); this->permute_op_.Run(&resize_img, input.data());
std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f); // Inference.
auto input_names = this->predictor_->GetInputNames();
this->permute_op_.Run(&resize_img, input.data()); auto input_t = this->predictor_->GetInputHandle(input_names[0]);
input_t->Reshape({1, 3, resize_img.rows, resize_img.cols});
// Inference. input_t->CopyFromCpu(input.data());
auto input_names = this->predictor_->GetInputNames(); this->predictor_->Run();
auto input_t = this->predictor_->GetInputHandle(input_names[0]);
input_t->Reshape({1, 3, resize_img.rows, resize_img.cols}); std::vector<float> predict_batch;
input_t->CopyFromCpu(input.data()); auto output_names = this->predictor_->GetOutputNames();
this->predictor_->Run(); auto output_t = this->predictor_->GetOutputHandle(output_names[0]);
auto predict_shape = output_t->shape();
std::vector<float> predict_batch;
auto output_names = this->predictor_->GetOutputNames(); int out_num = std::accumulate(predict_shape.begin(), predict_shape.end(), 1,
auto output_t = this->predictor_->GetOutputHandle(output_names[0]); std::multiplies<int>());
auto predict_shape = output_t->shape(); predict_batch.resize(out_num);
int out_num = std::accumulate(predict_shape.begin(), predict_shape.end(), 1, output_t->CopyToCpu(predict_batch.data());
std::multiplies<int>());
predict_batch.resize(out_num); // ctc decode
std::vector<std::string> str_res;
output_t->CopyToCpu(predict_batch.data()); int argmax_idx;
int last_index = 0;
// ctc decode float score = 0.f;
std::vector<std::string> str_res; int count = 0;
int argmax_idx; float max_value = 0.0f;
int last_index = 0;
float score = 0.f; for (int n = 0; n < predict_shape[1]; n++) {
int count = 0; argmax_idx =
float max_value = 0.0f; int(Utility::argmax(&predict_batch[n * predict_shape[2]],
&predict_batch[(n + 1) * predict_shape[2]]));
for (int n = 0; n < predict_shape[1]; n++) { max_value =
argmax_idx = float(*std::max_element(&predict_batch[n * predict_shape[2]],
int(Utility::argmax(&predict_batch[n * predict_shape[2]], &predict_batch[(n + 1) * predict_shape[2]]));
&predict_batch[(n + 1) * predict_shape[2]]));
max_value = if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) {
float(*std::max_element(&predict_batch[n * predict_shape[2]], score += max_value;
&predict_batch[(n + 1) * predict_shape[2]])); count += 1;
str_res.push_back(label_list_[argmax_idx]);
if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) {
score += max_value;
count += 1;
str_res.push_back(label_list_[argmax_idx]);
}
last_index = argmax_idx;
}
score /= count;
for (int i = 0; i < str_res.size(); i++) {
std::cout << str_res[i];
} }
std::cout << "\tscore: " << score << std::endl; last_index = argmax_idx;
}
score /= count;
for (int i = 0; i < str_res.size(); i++) {
std::cout << str_res[i];
} }
std::cout << "\tscore: " << score << std::endl;
} }
void CRNNRecognizer::LoadModel(const std::string &model_dir) { void CRNNRecognizer::LoadModel(const std::string &model_dir) {
......
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <include/postprocess_op.h>
namespace PaddleOCR {
void PostProcessor::GetContourArea(const std::vector<std::vector<float>> &box,
float unclip_ratio, float &distance) {
int pts_num = 4;
float area = 0.0f;
float dist = 0.0f;
for (int i = 0; i < pts_num; i++) {
area += box[i][0] * box[(i + 1) % pts_num][1] -
box[i][1] * box[(i + 1) % pts_num][0];
dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) *
(box[i][0] - box[(i + 1) % pts_num][0]) +
(box[i][1] - box[(i + 1) % pts_num][1]) *
(box[i][1] - box[(i + 1) % pts_num][1]));
}
area = fabs(float(area / 2.0));
distance = area * unclip_ratio / dist;
}
cv::RotatedRect PostProcessor::UnClip(std::vector<std::vector<float>> box,
const float &unclip_ratio) {
float distance = 1.0;
GetContourArea(box, unclip_ratio, distance);
ClipperLib::ClipperOffset offset;
ClipperLib::Path p;
p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1]))
<< ClipperLib::IntPoint(int(box[1][0]), int(box[1][1]))
<< ClipperLib::IntPoint(int(box[2][0]), int(box[2][1]))
<< ClipperLib::IntPoint(int(box[3][0]), int(box[3][1]));
offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
ClipperLib::Paths soln;
offset.Execute(soln, distance);
std::vector<cv::Point2f> points;
for (int j = 0; j < soln.size(); j++) {
for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
points.emplace_back(soln[j][i].X, soln[j][i].Y);
}
}
cv::RotatedRect res;
if (points.size() <= 0) {
res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
} else {
res = cv::minAreaRect(points);
}
return res;
}
float **PostProcessor::Mat2Vec(cv::Mat mat) {
auto **array = new float *[mat.rows];
for (int i = 0; i < mat.rows; ++i)
array[i] = new float[mat.cols];
for (int i = 0; i < mat.rows; ++i) {
for (int j = 0; j < mat.cols; ++j) {
array[i][j] = mat.at<float>(i, j);
}
}
return array;
}
std::vector<std::vector<int>>
PostProcessor::OrderPointsClockwise(std::vector<std::vector<int>> pts) {
std::vector<std::vector<int>> box = pts;
std::sort(box.begin(), box.end(), XsortInt);
std::vector<std::vector<int>> leftmost = {box[0], box[1]};
std::vector<std::vector<int>> rightmost = {box[2], box[3]};
if (leftmost[0][1] > leftmost[1][1])
std::swap(leftmost[0], leftmost[1]);
if (rightmost[0][1] > rightmost[1][1])
std::swap(rightmost[0], rightmost[1]);
std::vector<std::vector<int>> rect = {leftmost[0], rightmost[0], rightmost[1],
leftmost[1]};
return rect;
}
std::vector<std::vector<float>> PostProcessor::Mat2Vector(cv::Mat mat) {
std::vector<std::vector<float>> img_vec;
std::vector<float> tmp;
for (int i = 0; i < mat.rows; ++i) {
tmp.clear();
for (int j = 0; j < mat.cols; ++j) {
tmp.push_back(mat.at<float>(i, j));
}
img_vec.push_back(tmp);
}
return img_vec;
}
bool PostProcessor::XsortFp32(std::vector<float> a, std::vector<float> b) {
if (a[0] != b[0])
return a[0] < b[0];
return false;
}
bool PostProcessor::XsortInt(std::vector<int> a, std::vector<int> b) {
if (a[0] != b[0])
return a[0] < b[0];
return false;
}
std::vector<std::vector<float>> PostProcessor::GetMiniBoxes(cv::RotatedRect box,
float &ssid) {
ssid = std::max(box.size.width, box.size.height);
cv::Mat points;
cv::boxPoints(box, points);
auto array = Mat2Vector(points);
std::sort(array.begin(), array.end(), XsortFp32);
std::vector<float> idx1 = array[0], idx2 = array[1], idx3 = array[2],
idx4 = array[3];
if (array[3][1] <= array[2][1]) {
idx2 = array[3];
idx3 = array[2];
} else {
idx2 = array[2];
idx3 = array[3];
}
if (array[1][1] <= array[0][1]) {
idx1 = array[1];
idx4 = array[0];
} else {
idx1 = array[0];
idx4 = array[1];
}
array[0] = idx1;
array[1] = idx2;
array[2] = idx3;
array[3] = idx4;
return array;
}
float PostProcessor::PolygonScoreAcc(std::vector<cv::Point> contour,
cv::Mat pred) {
int width = pred.cols;
int height = pred.rows;
std::vector<float> box_x;
std::vector<float> box_y;
for (int i = 0; i < contour.size(); ++i) {
box_x.push_back(contour[i].x);
box_y.push_back(contour[i].y);
}
int xmin =
clamp(int(std::floor(*(std::min_element(box_x.begin(), box_x.end())))), 0,
width - 1);
int xmax =
clamp(int(std::ceil(*(std::max_element(box_x.begin(), box_x.end())))), 0,
width - 1);
int ymin =
clamp(int(std::floor(*(std::min_element(box_y.begin(), box_y.end())))), 0,
height - 1);
int ymax =
clamp(int(std::ceil(*(std::max_element(box_y.begin(), box_y.end())))), 0,
height - 1);
cv::Mat mask;
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
cv::Point* rook_point = new cv::Point[contour.size()];
for (int i = 0; i < contour.size(); ++i) {
rook_point[i] = cv::Point(int(box_x[i]) - xmin, int(box_y[i]) - ymin);
}
const cv::Point *ppt[1] = {rook_point};
int npt[] = {int(contour.size())};
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
cv::Mat croppedImg;
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)).copyTo(croppedImg);
float score = cv::mean(croppedImg, mask)[0];
delete []rook_point;
return score;
}
float PostProcessor::BoxScoreFast(std::vector<std::vector<float>> box_array,
cv::Mat pred) {
auto array = box_array;
int width = pred.cols;
int height = pred.rows;
float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]};
float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]};
int xmin = clamp(int(std::floor(*(std::min_element(box_x, box_x + 4)))), 0,
width - 1);
int xmax = clamp(int(std::ceil(*(std::max_element(box_x, box_x + 4)))), 0,
width - 1);
int ymin = clamp(int(std::floor(*(std::min_element(box_y, box_y + 4)))), 0,
height - 1);
int ymax = clamp(int(std::ceil(*(std::max_element(box_y, box_y + 4)))), 0,
height - 1);
cv::Mat mask;
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
cv::Point root_point[4];
root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin);
root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin);
root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin);
root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin);
const cv::Point *ppt[1] = {root_point};
int npt[] = {4};
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
cv::Mat croppedImg;
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1))
.copyTo(croppedImg);
auto score = cv::mean(croppedImg, mask)[0];
return score;
}
std::vector<std::vector<std::vector<int>>> PostProcessor::BoxesFromBitmap(
const cv::Mat pred, const cv::Mat bitmap, const float &box_thresh,
const float &det_db_unclip_ratio, const bool &use_polygon_score) {
const int min_size = 3;
const int max_candidates = 1000;
int width = bitmap.cols;
int height = bitmap.rows;
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST,
cv::CHAIN_APPROX_SIMPLE);
int num_contours =
contours.size() >= max_candidates ? max_candidates : contours.size();
std::vector<std::vector<std::vector<int>>> boxes;
for (int _i = 0; _i < num_contours; _i++) {
if (contours[_i].size() <= 2) {
continue;
}
float ssid;
cv::RotatedRect box = cv::minAreaRect(contours[_i]);
auto array = GetMiniBoxes(box, ssid);
auto box_for_unclip = array;
// end get_mini_box
if (ssid < min_size) {
continue;
}
float score;
if (use_polygon_score)
/* compute using polygon*/
score = PolygonScoreAcc(contours[_i], pred);
else
score = BoxScoreFast(array, pred);
if (score < box_thresh)
continue;
// start for unclip
cv::RotatedRect points = UnClip(box_for_unclip, det_db_unclip_ratio);
if (points.size.height < 1.001 && points.size.width < 1.001) {
continue;
}
// end for unclip
cv::RotatedRect clipbox = points;
auto cliparray = GetMiniBoxes(clipbox, ssid);
if (ssid < min_size + 2)
continue;
int dest_width = pred.cols;
int dest_height = pred.rows;
std::vector<std::vector<int>> intcliparray;
for (int num_pt = 0; num_pt < 4; num_pt++) {
std::vector<int> a{int(clampf(roundf(cliparray[num_pt][0] / float(width) *
float(dest_width)),
0, float(dest_width))),
int(clampf(roundf(cliparray[num_pt][1] /
float(height) * float(dest_height)),
0, float(dest_height)))};
intcliparray.push_back(a);
}
boxes.push_back(intcliparray);
} // end for
return boxes;
}
std::vector<std::vector<std::vector<int>>>
PostProcessor::FilterTagDetRes(std::vector<std::vector<std::vector<int>>> boxes,
float ratio_h, float ratio_w, cv::Mat srcimg) {
int oriimg_h = srcimg.rows;
int oriimg_w = srcimg.cols;
std::vector<std::vector<std::vector<int>>> root_points;
for (int n = 0; n < boxes.size(); n++) {
boxes[n] = OrderPointsClockwise(boxes[n]);
for (int m = 0; m < boxes[0].size(); m++) {
boxes[n][m][0] /= ratio_w;
boxes[n][m][1] /= ratio_h;
boxes[n][m][0] = int(_min(_max(boxes[n][m][0], 0), oriimg_w - 1));
boxes[n][m][1] = int(_min(_max(boxes[n][m][1], 0), oriimg_h - 1));
}
}
for (int n = 0; n < boxes.size(); n++) {
int rect_width, rect_height;
rect_width = int(sqrt(pow(boxes[n][0][0] - boxes[n][1][0], 2) +
pow(boxes[n][0][1] - boxes[n][1][1], 2)));
rect_height = int(sqrt(pow(boxes[n][0][0] - boxes[n][3][0], 2) +
pow(boxes[n][0][1] - boxes[n][3][1], 2)));
if (rect_width <= 4 || rect_height <= 4)
continue;
root_points.push_back(boxes[n]);
}
return root_points;
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "paddle_api.h"
#include "paddle_inference_api.h"
#include <chrono>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <numeric>
#include <include/preprocess_op.h>
namespace PaddleOCR {
void Permute::Run(const cv::Mat *im, float *data) {
int rh = im->rows;
int rw = im->cols;
int rc = im->channels();
for (int i = 0; i < rc; ++i) {
cv::extractChannel(*im, cv::Mat(rh, rw, CV_32FC1, data + i * rh * rw), i);
}
}
void Normalize::Run(cv::Mat *im, const std::vector<float> &mean,
const std::vector<float> &scale, const bool is_scale) {
double e = 1.0;
if (is_scale) {
e /= 255.0;
}
(*im).convertTo(*im, CV_32FC3, e);
std::vector<cv::Mat> bgr_channels(3);
cv::split(*im, bgr_channels);
for (auto i = 0; i < bgr_channels.size(); i++) {
bgr_channels[i].convertTo(bgr_channels[i], CV_32FC1, 1.0 * scale[i],
(0.0 - mean[i]) * scale[i]);
}
cv::merge(bgr_channels, *im);
}
void ResizeImgType0::Run(const cv::Mat &img, cv::Mat &resize_img,
int max_size_len, float &ratio_h, float &ratio_w,
bool use_tensorrt) {
int w = img.cols;
int h = img.rows;
float ratio = 1.f;
int max_wh = w >= h ? w : h;
if (max_wh > max_size_len) {
if (h > w) {
ratio = float(max_size_len) / float(h);
} else {
ratio = float(max_size_len) / float(w);
}
}
int resize_h = int(float(h) * ratio);
int resize_w = int(float(w) * ratio);
resize_h = max(int(round(float(resize_h) / 32) * 32), 32);
resize_w = max(int(round(float(resize_w) / 32) * 32), 32);
cv::resize(img, resize_img, cv::Size(resize_w, resize_h));
ratio_h = float(resize_h) / float(h);
ratio_w = float(resize_w) / float(w);
}
void CrnnResizeImg::Run(const cv::Mat &img, cv::Mat &resize_img, float wh_ratio,
bool use_tensorrt,
const std::vector<int> &rec_image_shape) {
int imgC, imgH, imgW;
imgC = rec_image_shape[0];
imgH = rec_image_shape[1];
imgW = rec_image_shape[2];
imgW = int(32 * wh_ratio);
float ratio = float(img.cols) / float(img.rows);
int resize_w, resize_h;
if (ceilf(imgH * ratio) > imgW)
resize_w = imgW;
else
resize_w = int(ceilf(imgH * ratio));
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
cv::INTER_LINEAR);
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0,
int(imgW - resize_img.cols), cv::BORDER_CONSTANT,
{127, 127, 127});
}
void ClsResizeImg::Run(const cv::Mat &img, cv::Mat &resize_img,
bool use_tensorrt,
const std::vector<int> &rec_image_shape) {
int imgC, imgH, imgW;
imgC = rec_image_shape[0];
imgH = rec_image_shape[1];
imgW = rec_image_shape[2];
float ratio = float(img.cols) / float(img.rows);
int resize_w, resize_h;
if (ceilf(imgH * ratio) > imgW)
resize_w = imgW;
else
resize_w = int(ceilf(imgH * ratio));
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
cv::INTER_LINEAR);
if (resize_w < imgW) {
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, imgW - resize_w,
cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
}
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <dirent.h>
#include <include/utility.h>
#include <iostream>
#include <ostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
namespace PaddleOCR {
std::vector<std::string> Utility::ReadDict(const std::string &path) {
std::ifstream in(path);
std::string line;
std::vector<std::string> m_vec;
if (in) {
while (getline(in, line)) {
m_vec.push_back(line);
}
} else {
std::cout << "no such label file: " << path << ", exit the program..."
<< std::endl;
exit(1);
}
return m_vec;
}
void Utility::VisualizeBboxes(
const cv::Mat &srcimg,
const std::vector<std::vector<std::vector<int>>> &boxes) {
cv::Mat img_vis;
srcimg.copyTo(img_vis);
for (int n = 0; n < boxes.size(); n++) {
cv::Point rook_points[4];
for (int m = 0; m < boxes[n].size(); m++) {
rook_points[m] = cv::Point(int(boxes[n][m][0]), int(boxes[n][m][1]));
}
const cv::Point *ppt[1] = {rook_points};
int npt[] = {4};
cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0);
}
cv::imwrite("./ocr_vis.png", img_vis);
std::cout << "The detection visualized image saved in ./ocr_vis.png"
<< std::endl;
}
// list all files under a directory
void Utility::GetAllFiles(const char *dir_name,
std::vector<std::string> &all_inputs) {
if (NULL == dir_name) {
std::cout << " dir_name is null ! " << std::endl;
return;
}
struct stat s;
lstat(dir_name, &s);
if (!S_ISDIR(s.st_mode)) {
std::cout << "dir_name is not a valid directory !" << std::endl;
all_inputs.push_back(dir_name);
return;
} else {
struct dirent *filename; // return value for readdir()
DIR *dir; // return value for opendir()
dir = opendir(dir_name);
if (NULL == dir) {
std::cout << "Can not open dir " << dir_name << std::endl;
return;
}
std::cout << "Successfully opened the dir !" << std::endl;
while ((filename = readdir(dir)) != NULL) {
if (strcmp(filename->d_name, ".") == 0 ||
strcmp(filename->d_name, "..") == 0)
continue;
// img_dir + std::string("/") + all_inputs[0];
all_inputs.push_back(dir_name + std::string("/") +
std::string(filename->d_name));
}
}
}
} // namespace PaddleOCR
\ No newline at end of file
/*******************************************************************************
* *
* 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 <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <ostream>
#include <stdexcept>
#include <vector>
#include "include/clipper.h"
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<cInt>(val - 0.5);
else
return static_cast<cInt>(val + 0.5);
}
//------------------------------------------------------------------------------
inline cInt Abs(cInt val) { return val < 0 ? -val : val; }
//------------------------------------------------------------------------------
// PolyTree methods ...
//------------------------------------------------------------------------------
void PolyTree::Clear() {
for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i)
delete AllNodes[i];
AllNodes.resize(0);
Childs.resize(0);
}
//------------------------------------------------------------------------------
PolyNode *PolyTree::GetFirst() const {
if (!Childs.empty())
return Childs[0];
else
return 0;
}
//------------------------------------------------------------------------------
int PolyTree::Total() const {
int result = (int)AllNodes.size();
// with negative offsets, ignore the hidden outer polygon ...
if (result > 0 && Childs[0] != AllNodes[0])
result--;
return result;
}
//------------------------------------------------------------------------------
// PolyNode methods ...
//------------------------------------------------------------------------------
PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {}
//------------------------------------------------------------------------------
int PolyNode::ChildCount() const { return (int)Childs.size(); }
//------------------------------------------------------------------------------
void PolyNode::AddChild(PolyNode &child) {
unsigned cnt = (unsigned)Childs.size();
Childs.push_back(&child);
child.Parent = this;
child.Index = cnt;
}
//------------------------------------------------------------------------------
PolyNode *PolyNode::GetNext() const {
if (!Childs.empty())
return Childs[0];
else
return GetNextSiblingUp();
}
//------------------------------------------------------------------------------
PolyNode *PolyNode::GetNextSiblingUp() const {
if (!Parent) // protects against PolyTree.GetNextSiblingUp()
return 0;
else if (Index == Parent->Childs.size() - 1)
return Parent->GetNextSiblingUp();
else
return Parent->Childs[Index + 1];
}
//------------------------------------------------------------------------------
bool PolyNode::IsHole() const {
bool result = true;
PolyNode *node = Parent;
while (node) {
result = !result;
node = node->Parent;
}
return result;
}
//------------------------------------------------------------------------------
bool PolyNode::IsOpen() const { return m_IsOpen; }
//------------------------------------------------------------------------------
#ifndef use_int32
//------------------------------------------------------------------------------
// Int128 class (enables safe math on signed 64bit integers)
// eg Int128 val1((long64)9223372036854775807); //ie 2^63 -1
// Int128 val2((long64)9223372036854775807);
// Int128 val3 = val1 * val2;
// val3.AsString => "85070591730234615847396907784232501249" (8.5e+37)
//------------------------------------------------------------------------------
class Int128 {
public:
ulong64 lo;
long64 hi;
Int128(long64 _lo = 0) {
lo = (ulong64)_lo;
if (_lo < 0)
hi = -1;
else
hi = 0;
}
Int128(const Int128 &val) : lo(val.lo), hi(val.hi) {}
Int128(const long64 &_hi, const ulong64 &_lo) : lo(_lo), hi(_hi) {}
Int128 &operator=(const long64 &val) {
lo = (ulong64)val;
if (val < 0)
hi = -1;
else
hi = 0;
return *this;
}
bool operator==(const Int128 &val) const {
return (hi == val.hi && lo == val.lo);
}
bool operator!=(const Int128 &val) const { return !(*this == val); }
bool operator>(const Int128 &val) const {
if (hi != val.hi)
return hi > val.hi;
else
return lo > val.lo;
}
bool operator<(const Int128 &val) const {
if (hi != val.hi)
return hi < val.hi;
else
return lo < val.lo;
}
bool operator>=(const Int128 &val) const { return !(*this < val); }
bool operator<=(const Int128 &val) const { return !(*this > val); }
Int128 &operator+=(const Int128 &rhs) {
hi += rhs.hi;
lo += rhs.lo;
if (lo < rhs.lo)
hi++;
return *this;
}
Int128 operator+(const Int128 &rhs) const {
Int128 result(*this);
result += rhs;
return result;
}
Int128 &operator-=(const Int128 &rhs) {
*this += -rhs;
return *this;
}
Int128 operator-(const Int128 &rhs) const {
Int128 result(*this);
result -= rhs;
return result;
}
Int128 operator-() const // unary negation
{
if (lo == 0)
return Int128(-hi, 0);
else
return Int128(~hi, ~lo + 1);
}
operator double() const {
const double shift64 = 18446744073709551616.0; // 2^64
if (hi < 0) {
if (lo == 0)
return (double)hi * shift64;
else
return -(double)(~lo + ~hi * shift64);
} else
return (double)(lo + hi * shift64);
}
};
//------------------------------------------------------------------------------
Int128 Int128Mul(long64 lhs, long64 rhs) {
bool negate = (lhs < 0) != (rhs < 0);
if (lhs < 0)
lhs = -lhs;
ulong64 int1Hi = ulong64(lhs) >> 32;
ulong64 int1Lo = ulong64(lhs & 0xFFFFFFFF);
if (rhs < 0)
rhs = -rhs;
ulong64 int2Hi = ulong64(rhs) >> 32;
ulong64 int2Lo = ulong64(rhs & 0xFFFFFFFF);
// nb: see comments in clipper.pas
ulong64 a = int1Hi * int2Hi;
ulong64 b = int1Lo * int2Lo;
ulong64 c = int1Hi * int2Lo + int1Lo * int2Hi;
Int128 tmp;
tmp.hi = long64(a + (c >> 32));
tmp.lo = long64(c << 32);
tmp.lo += long64(b);
if (tmp.lo < b)
tmp.hi++;
if (negate)
tmp = -tmp;
return tmp;
};
#endif
//------------------------------------------------------------------------------
// Miscellaneous global functions
//------------------------------------------------------------------------------
bool Orientation(const Path &poly) { return Area(poly) >= 0; }
//------------------------------------------------------------------------------
double Area(const Path &poly) {
int size = (int)poly.size();
if (size < 3)
return 0;
double a = 0;
for (int i = 0, j = size - 1; i < size; ++i) {
a += ((double)poly[j].X + poly[i].X) * ((double)poly[j].Y - poly[i].Y);
j = i;
}
return -a * 0.5;
}
//------------------------------------------------------------------------------
double Area(const OutPt *op) {
const OutPt *startOp = op;
if (!op)
return 0;
double a = 0;
do {
a += (double)(op->Prev->Pt.X + op->Pt.X) *
(double)(op->Prev->Pt.Y - op->Pt.Y);
op = op->Next;
} while (op != startOp);
return a * 0.5;
}
//------------------------------------------------------------------------------
double Area(const OutRec &outRec) { return Area(outRec.Pts); }
//------------------------------------------------------------------------------
bool PointIsVertex(const IntPoint &Pt, OutPt *pp) {
OutPt *pp2 = pp;
do {
if (pp2->Pt == Pt)
return true;
pp2 = pp2->Next;
} while (pp2 != pp);
return false;
}
//------------------------------------------------------------------------------
// See "The Point in Polygon Problem for Arbitrary Polygons" by Hormann &
// Agathos
// http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.88.5498&rep=rep1&type=pdf
int PointInPolygon(const IntPoint &pt, const Path &path) {
// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
int result = 0;
size_t cnt = path.size();
if (cnt < 3)
return 0;
IntPoint ip = path[0];
for (size_t i = 1; i <= cnt; ++i) {
IntPoint ipNext = (i == cnt ? path[0] : path[i]);
if (ipNext.Y == pt.Y) {
if ((ipNext.X == pt.X) ||
(ip.Y == pt.Y && ((ipNext.X > pt.X) == (ip.X < pt.X))))
return -1;
}
if ((ip.Y < pt.Y) != (ipNext.Y < pt.Y)) {
if (ip.X >= pt.X) {
if (ipNext.X > pt.X)
result = 1 - result;
else {
double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
(double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (ipNext.Y > ip.Y))
result = 1 - result;
}
} else {
if (ipNext.X > pt.X) {
double d = (double)(ip.X - pt.X) * (ipNext.Y - pt.Y) -
(double)(ipNext.X - pt.X) * (ip.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (ipNext.Y > ip.Y))
result = 1 - result;
}
}
}
ip = ipNext;
}
return result;
}
//------------------------------------------------------------------------------
int PointInPolygon(const IntPoint &pt, OutPt *op) {
// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
int result = 0;
OutPt *startOp = op;
for (;;) {
if (op->Next->Pt.Y == pt.Y) {
if ((op->Next->Pt.X == pt.X) ||
(op->Pt.Y == pt.Y && ((op->Next->Pt.X > pt.X) == (op->Pt.X < pt.X))))
return -1;
}
if ((op->Pt.Y < pt.Y) != (op->Next->Pt.Y < pt.Y)) {
if (op->Pt.X >= pt.X) {
if (op->Next->Pt.X > pt.X)
result = 1 - result;
else {
double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
(double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
result = 1 - result;
}
} else {
if (op->Next->Pt.X > pt.X) {
double d = (double)(op->Pt.X - pt.X) * (op->Next->Pt.Y - pt.Y) -
(double)(op->Next->Pt.X - pt.X) * (op->Pt.Y - pt.Y);
if (!d)
return -1;
if ((d > 0) == (op->Next->Pt.Y > op->Pt.Y))
result = 1 - result;
}
}
}
op = op->Next;
if (startOp == op)
break;
}
return result;
}
//------------------------------------------------------------------------------
bool Poly2ContainsPoly1(OutPt *OutPt1, OutPt *OutPt2) {
OutPt *op = OutPt1;
do {
// nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
int res = PointInPolygon(op->Pt, OutPt2);
if (res >= 0)
return res > 0;
op = op->Next;
} while (op != OutPt1);
return true;
}
//----------------------------------------------------------------------
bool SlopesEqual(const TEdge &e1, const TEdge &e2, bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(e1.Top.Y - e1.Bot.Y, e2.Top.X - e2.Bot.X) ==
Int128Mul(e1.Top.X - e1.Bot.X, e2.Top.Y - e2.Bot.Y);
else
#endif
return (e1.Top.Y - e1.Bot.Y) * (e2.Top.X - e2.Bot.X) ==
(e1.Top.X - e1.Bot.X) * (e2.Top.Y - e2.Bot.Y);
}
//------------------------------------------------------------------------------
bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(pt1.Y - pt2.Y, pt2.X - pt3.X) ==
Int128Mul(pt1.X - pt2.X, pt2.Y - pt3.Y);
else
#endif
return (pt1.Y - pt2.Y) * (pt2.X - pt3.X) ==
(pt1.X - pt2.X) * (pt2.Y - pt3.Y);
}
//------------------------------------------------------------------------------
bool SlopesEqual(const IntPoint pt1, const IntPoint pt2, const IntPoint pt3,
const IntPoint pt4, bool UseFullInt64Range) {
#ifndef use_int32
if (UseFullInt64Range)
return Int128Mul(pt1.Y - pt2.Y, pt3.X - pt4.X) ==
Int128Mul(pt1.X - pt2.X, pt3.Y - pt4.Y);
else
#endif
return (pt1.Y - pt2.Y) * (pt3.X - pt4.X) ==
(pt1.X - pt2.X) * (pt3.Y - pt4.Y);
}
//------------------------------------------------------------------------------
inline bool IsHorizontal(TEdge &e) { return e.Dx == HORIZONTAL; }
//------------------------------------------------------------------------------
inline double GetDx(const IntPoint pt1, const IntPoint pt2) {
return (pt1.Y == pt2.Y) ? HORIZONTAL
: (double)(pt2.X - pt1.X) / (pt2.Y - pt1.Y);
}
//---------------------------------------------------------------------------
inline void SetDx(TEdge &e) {
cInt dy = (e.Top.Y - e.Bot.Y);
if (dy == 0)
e.Dx = HORIZONTAL;
else
e.Dx = (double)(e.Top.X - e.Bot.X) / dy;
}
//---------------------------------------------------------------------------
inline void SwapSides(TEdge &Edge1, TEdge &Edge2) {
EdgeSide Side = Edge1.Side;
Edge1.Side = Edge2.Side;
Edge2.Side = Side;
}
//------------------------------------------------------------------------------
inline void SwapPolyIndexes(TEdge &Edge1, TEdge &Edge2) {
int OutIdx = Edge1.OutIdx;
Edge1.OutIdx = Edge2.OutIdx;
Edge2.OutIdx = OutIdx;
}
//------------------------------------------------------------------------------
inline cInt TopX(TEdge &edge, const cInt currentY) {
return (currentY == edge.Top.Y)
? edge.Top.X
: edge.Bot.X + Round(edge.Dx * (currentY - edge.Bot.Y));
}
//------------------------------------------------------------------------------
void IntersectPoint(TEdge &Edge1, TEdge &Edge2, IntPoint &ip) {
#ifdef use_xyz
ip.Z = 0;
#endif
double b1, b2;
if (Edge1.Dx == Edge2.Dx) {
ip.Y = Edge1.Curr.Y;
ip.X = TopX(Edge1, ip.Y);
return;
} else if (Edge1.Dx == 0) {
ip.X = Edge1.Bot.X;
if (IsHorizontal(Edge2))
ip.Y = Edge2.Bot.Y;
else {
b2 = Edge2.Bot.Y - (Edge2.Bot.X / Edge2.Dx);
ip.Y = Round(ip.X / Edge2.Dx + b2);
}
} else if (Edge2.Dx == 0) {
ip.X = Edge2.Bot.X;
if (IsHorizontal(Edge1))
ip.Y = Edge1.Bot.Y;
else {
b1 = Edge1.Bot.Y - (Edge1.Bot.X / Edge1.Dx);
ip.Y = Round(ip.X / Edge1.Dx + b1);
}
} else {
b1 = Edge1.Bot.X - Edge1.Bot.Y * Edge1.Dx;
b2 = Edge2.Bot.X - Edge2.Bot.Y * Edge2.Dx;
double q = (b2 - b1) / (Edge1.Dx - Edge2.Dx);
ip.Y = Round(q);
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.X = Round(Edge1.Dx * q + b1);
else
ip.X = Round(Edge2.Dx * q + b2);
}
if (ip.Y < Edge1.Top.Y || ip.Y < Edge2.Top.Y) {
if (Edge1.Top.Y > Edge2.Top.Y)
ip.Y = Edge1.Top.Y;
else
ip.Y = Edge2.Top.Y;
if (std::fabs(Edge1.Dx) < std::fabs(Edge2.Dx))
ip.X = TopX(Edge1, ip.Y);
else
ip.X = TopX(Edge2, ip.Y);
}
// finally, don't allow 'ip' to be BELOW curr.Y (ie bottom of scanbeam) ...
if (ip.Y > Edge1.Curr.Y) {
ip.Y = Edge1.Curr.Y;
// use the more vertical edge to derive X ...
if (std::fabs(Edge1.Dx) > std::fabs(Edge2.Dx))
ip.X = TopX(Edge2, ip.Y);
else
ip.X = TopX(Edge1, ip.Y);
}
}
//------------------------------------------------------------------------------
void ReversePolyPtLinks(OutPt *pp) {
if (!pp)
return;
OutPt *pp1, *pp2;
pp1 = pp;
do {
pp2 = pp1->Next;
pp1->Next = pp1->Prev;
pp1->Prev = pp2;
pp1 = pp2;
} while (pp1 != pp);
}
//------------------------------------------------------------------------------
void DisposeOutPts(OutPt *&pp) {
if (pp == 0)
return;
pp->Prev->Next = 0;
while (pp) {
OutPt *tmpPp = pp;
pp = pp->Next;
delete tmpPp;
}
}
//------------------------------------------------------------------------------
inline void InitEdge(TEdge *e, TEdge *eNext, TEdge *ePrev, const IntPoint &Pt) {
std::memset(e, 0, sizeof(TEdge));
e->Next = eNext;
e->Prev = ePrev;
e->Curr = Pt;
e->OutIdx = Unassigned;
}
//------------------------------------------------------------------------------
void InitEdge2(TEdge &e, PolyType Pt) {
if (e.Curr.Y >= e.Next->Curr.Y) {
e.Bot = e.Curr;
e.Top = e.Next->Curr;
} else {
e.Top = e.Curr;
e.Bot = e.Next->Curr;
}
SetDx(e);
e.PolyTyp = Pt;
}
//------------------------------------------------------------------------------
TEdge *RemoveEdge(TEdge *e) {
// removes e from double_linked_list (but without removing from memory)
e->Prev->Next = e->Next;
e->Next->Prev = e->Prev;
TEdge *result = e->Next;
e->Prev = 0; // flag as removed (see ClipperBase.Clear)
return result;
}
//------------------------------------------------------------------------------
inline void ReverseHorizontal(TEdge &e) {
// swap horizontal edges' Top and Bottom x's so they follow the natural
// progression of the bounds - ie so their xbots will align with the
// adjoining lower edge. [Helpful in the ProcessHorizontal() method.]
std::swap(e.Top.X, e.Bot.X);
#ifdef use_xyz
std::swap(e.Top.Z, e.Bot.Z);
#endif
}
//------------------------------------------------------------------------------
void SwapPoints(IntPoint &pt1, IntPoint &pt2) {
IntPoint tmp = pt1;
pt1 = pt2;
pt2 = tmp;
}
//------------------------------------------------------------------------------
bool GetOverlapSegment(IntPoint pt1a, IntPoint pt1b, IntPoint pt2a,
IntPoint pt2b, IntPoint &pt1, IntPoint &pt2) {
// precondition: segments are Collinear.
if (Abs(pt1a.X - pt1b.X) > Abs(pt1a.Y - pt1b.Y)) {
if (pt1a.X > pt1b.X)
SwapPoints(pt1a, pt1b);
if (pt2a.X > pt2b.X)
SwapPoints(pt2a, pt2b);
if (pt1a.X > pt2a.X)
pt1 = pt1a;
else
pt1 = pt2a;
if (pt1b.X < pt2b.X)
pt2 = pt1b;
else
pt2 = pt2b;
return pt1.X < pt2.X;
} else {
if (pt1a.Y < pt1b.Y)
SwapPoints(pt1a, pt1b);
if (pt2a.Y < pt2b.Y)
SwapPoints(pt2a, pt2b);
if (pt1a.Y < pt2a.Y)
pt1 = pt1a;
else
pt1 = pt2a;
if (pt1b.Y > pt2b.Y)
pt2 = pt1b;
else
pt2 = pt2b;
return pt1.Y > pt2.Y;
}
}
//------------------------------------------------------------------------------
bool FirstIsBottomPt(const OutPt *btmPt1, const OutPt *btmPt2) {
OutPt *p = btmPt1->Prev;
while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
p = p->Prev;
double dx1p = std::fabs(GetDx(btmPt1->Pt, p->Pt));
p = btmPt1->Next;
while ((p->Pt == btmPt1->Pt) && (p != btmPt1))
p = p->Next;
double dx1n = std::fabs(GetDx(btmPt1->Pt, p->Pt));
p = btmPt2->Prev;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
p = p->Prev;
double dx2p = std::fabs(GetDx(btmPt2->Pt, p->Pt));
p = btmPt2->Next;
while ((p->Pt == btmPt2->Pt) && (p != btmPt2))
p = p->Next;
double dx2n = std::fabs(GetDx(btmPt2->Pt, p->Pt));
if (std::max(dx1p, dx1n) == std::max(dx2p, dx2n) &&
std::min(dx1p, dx1n) == std::min(dx2p, dx2n))
return Area(btmPt1) > 0; // if otherwise identical use orientation
else
return (dx1p >= dx2p && dx1p >= dx2n) || (dx1n >= dx2p && dx1n >= dx2n);
}
//------------------------------------------------------------------------------
OutPt *GetBottomPt(OutPt *pp) {
OutPt *dups = 0;
OutPt *p = pp->Next;
while (p != pp) {
if (p->Pt.Y > pp->Pt.Y) {
pp = p;
dups = 0;
} else if (p->Pt.Y == pp->Pt.Y && p->Pt.X <= pp->Pt.X) {
if (p->Pt.X < pp->Pt.X) {
dups = 0;
pp = p;
} else {
if (p->Next != pp && p->Prev != pp)
dups = p;
}
}
p = p->Next;
}
if (dups) {
// there appears to be at least 2 vertices at BottomPt so ...
while (dups != p) {
if (!FirstIsBottomPt(p, dups))
pp = dups;
dups = dups->Next;
while (dups->Pt != pp->Pt)
dups = dups->Next;
}
}
return pp;
}
//------------------------------------------------------------------------------
bool Pt2IsBetweenPt1AndPt3(const IntPoint pt1, const IntPoint pt2,
const IntPoint pt3) {
if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2))
return false;
else if (pt1.X != pt3.X)
return (pt2.X > pt1.X) == (pt2.X < pt3.X);
else
return (pt2.Y > pt1.Y) == (pt2.Y < pt3.Y);
}
//------------------------------------------------------------------------------
bool HorzSegmentsOverlap(cInt seg1a, cInt seg1b, cInt seg2a, cInt seg2b) {
if (seg1a > seg1b)
std::swap(seg1a, seg1b);
if (seg2a > seg2b)
std::swap(seg2a, seg2b);
return (seg1a < seg2b) && (seg2a < seg1b);
}
//------------------------------------------------------------------------------
// ClipperBase class methods ...
//------------------------------------------------------------------------------
ClipperBase::ClipperBase() // constructor
{
m_CurrentLM = m_MinimaList.begin(); // begin() == end() here
m_UseFullRange = false;
}
//------------------------------------------------------------------------------
ClipperBase::~ClipperBase() // destructor
{
Clear();
}
//------------------------------------------------------------------------------
void RangeTest(const IntPoint &Pt, bool &useFullRange) {
if (useFullRange) {
if (Pt.X > hiRange || Pt.Y > hiRange || -Pt.X > hiRange || -Pt.Y > hiRange)
throw clipperException("Coordinate outside allowed range");
} else if (Pt.X > loRange || Pt.Y > loRange || -Pt.X > loRange ||
-Pt.Y > loRange) {
useFullRange = true;
RangeTest(Pt, useFullRange);
}
}
//------------------------------------------------------------------------------
TEdge *FindNextLocMin(TEdge *E) {
for (;;) {
while (E->Bot != E->Prev->Bot || E->Curr == E->Top)
E = E->Next;
if (!IsHorizontal(*E) && !IsHorizontal(*E->Prev))
break;
while (IsHorizontal(*E->Prev))
E = E->Prev;
TEdge *E2 = E;
while (IsHorizontal(*E))
E = E->Next;
if (E->Top.Y == E->Prev->Bot.Y)
continue; // ie just an intermediate horz.
if (E2->Prev->Bot.X < E->Bot.X)
E = E2;
break;
}
return E;
}
//------------------------------------------------------------------------------
TEdge *ClipperBase::ProcessBound(TEdge *E, bool NextIsForward) {
TEdge *Result = E;
TEdge *Horz = 0;
if (E->OutIdx == Skip) {
// if edges still remain in the current bound beyond the skip edge then
// create another LocMin and call ProcessBound once more
if (NextIsForward) {
while (E->Top.Y == E->Next->Bot.Y)
E = E->Next;
// don't include top horizontals when parsing a bound a second time,
// they will be contained in the opposite bound ...
while (E != Result && IsHorizontal(*E))
E = E->Prev;
} else {
while (E->Top.Y == E->Prev->Bot.Y)
E = E->Prev;
while (E != Result && IsHorizontal(*E))
E = E->Next;
}
if (E == Result) {
if (NextIsForward)
Result = E->Next;
else
Result = E->Prev;
} else {
// there are more edges in the bound beyond result starting with E
if (NextIsForward)
E = Result->Next;
else
E = Result->Prev;
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
locMin.LeftBound = 0;
locMin.RightBound = E;
E->WindDelta = 0;
Result = ProcessBound(E, NextIsForward);
m_MinimaList.push_back(locMin);
}
return Result;
}
TEdge *EStart;
if (IsHorizontal(*E)) {
// We need to be careful with open paths because this may not be a
// true local minima (ie E may be following a skip edge).
// Also, consecutive horz. edges may start heading left before going right.
if (NextIsForward)
EStart = E->Prev;
else
EStart = E->Next;
if (IsHorizontal(*EStart)) // ie an adjoining horizontal skip edge
{
if (EStart->Bot.X != E->Bot.X && EStart->Top.X != E->Bot.X)
ReverseHorizontal(*E);
} else if (EStart->Bot.X != E->Bot.X)
ReverseHorizontal(*E);
}
EStart = E;
if (NextIsForward) {
while (Result->Top.Y == Result->Next->Bot.Y && Result->Next->OutIdx != Skip)
Result = Result->Next;
if (IsHorizontal(*Result) && Result->Next->OutIdx != Skip) {
// nb: at the top of a bound, horizontals are added to the bound
// only when the preceding edge attaches to the horizontal's left vertex
// unless a Skip edge is encountered when that becomes the top divide
Horz = Result;
while (IsHorizontal(*Horz->Prev))
Horz = Horz->Prev;
if (Horz->Prev->Top.X > Result->Next->Top.X)
Result = Horz->Prev;
}
while (E != Result) {
E->NextInLML = E->Next;
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
E = E->Next;
}
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
Result = Result->Next; // move to the edge just beyond current bound
} else {
while (Result->Top.Y == Result->Prev->Bot.Y && Result->Prev->OutIdx != Skip)
Result = Result->Prev;
if (IsHorizontal(*Result) && Result->Prev->OutIdx != Skip) {
Horz = Result;
while (IsHorizontal(*Horz->Next))
Horz = Horz->Next;
if (Horz->Next->Top.X == Result->Prev->Top.X ||
Horz->Next->Top.X > Result->Prev->Top.X)
Result = Horz->Next;
}
while (E != Result) {
E->NextInLML = E->Prev;
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
ReverseHorizontal(*E);
E = E->Prev;
}
if (IsHorizontal(*E) && E != EStart && E->Bot.X != E->Next->Top.X)
ReverseHorizontal(*E);
Result = Result->Prev; // move to the edge just beyond current bound
}
return Result;
}
//------------------------------------------------------------------------------
bool ClipperBase::AddPath(const Path &pg, PolyType PolyTyp, bool Closed) {
#ifdef use_lines
if (!Closed && PolyTyp == ptClip)
throw clipperException("AddPath: Open paths must be subject.");
#else
if (!Closed)
throw clipperException("AddPath: Open paths have been disabled.");
#endif
int highI = (int)pg.size() - 1;
if (Closed)
while (highI > 0 && (pg[highI] == pg[0]))
--highI;
while (highI > 0 && (pg[highI] == pg[highI - 1]))
--highI;
if ((Closed && highI < 2) || (!Closed && highI < 1))
return false;
// create a new edge array ...
TEdge *edges = new TEdge[highI + 1];
bool IsFlat = true;
// 1. Basic (first) edge initialization ...
try {
edges[1].Curr = pg[1];
RangeTest(pg[0], m_UseFullRange);
RangeTest(pg[highI], m_UseFullRange);
InitEdge(&edges[0], &edges[1], &edges[highI], pg[0]);
InitEdge(&edges[highI], &edges[0], &edges[highI - 1], pg[highI]);
for (int i = highI - 1; i >= 1; --i) {
RangeTest(pg[i], m_UseFullRange);
InitEdge(&edges[i], &edges[i + 1], &edges[i - 1], pg[i]);
}
} catch (...) {
delete[] edges;
throw; // range test fails
}
TEdge *eStart = &edges[0];
// 2. Remove duplicate vertices, and (when closed) collinear edges ...
TEdge *E = eStart, *eLoopStop = eStart;
for (;;) {
// nb: allows matching start and end points when not Closed ...
if (E->Curr == E->Next->Curr && (Closed || E->Next != eStart)) {
if (E == E->Next)
break;
if (E == eStart)
eStart = E->Next;
E = RemoveEdge(E);
eLoopStop = E;
continue;
}
if (E->Prev == E->Next)
break; // only two vertices
else if (Closed && SlopesEqual(E->Prev->Curr, E->Curr, E->Next->Curr,
m_UseFullRange) &&
(!m_PreserveCollinear ||
!Pt2IsBetweenPt1AndPt3(E->Prev->Curr, E->Curr, E->Next->Curr))) {
// Collinear edges are allowed for open paths but in closed paths
// the default is to merge adjacent collinear edges into a single edge.
// However, if the PreserveCollinear property is enabled, only overlapping
// collinear edges (ie spikes) will be removed from closed paths.
if (E == eStart)
eStart = E->Next;
E = RemoveEdge(E);
E = E->Prev;
eLoopStop = E;
continue;
}
E = E->Next;
if ((E == eLoopStop) || (!Closed && E->Next == eStart))
break;
}
if ((!Closed && (E == E->Next)) || (Closed && (E->Prev == E->Next))) {
delete[] edges;
return false;
}
if (!Closed) {
m_HasOpenPaths = true;
eStart->Prev->OutIdx = Skip;
}
// 3. Do second stage of edge initialization ...
E = eStart;
do {
InitEdge2(*E, PolyTyp);
E = E->Next;
if (IsFlat && E->Curr.Y != eStart->Curr.Y)
IsFlat = false;
} while (E != eStart);
// 4. Finally, add edge bounds to LocalMinima list ...
// Totally flat paths must be handled differently when adding them
// to LocalMinima list to avoid endless loops etc ...
if (IsFlat) {
if (Closed) {
delete[] edges;
return false;
}
E->Prev->OutIdx = Skip;
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
locMin.LeftBound = 0;
locMin.RightBound = E;
locMin.RightBound->Side = esRight;
locMin.RightBound->WindDelta = 0;
for (;;) {
if (E->Bot.X != E->Prev->Top.X)
ReverseHorizontal(*E);
if (E->Next->OutIdx == Skip)
break;
E->NextInLML = E->Next;
E = E->Next;
}
m_MinimaList.push_back(locMin);
m_edges.push_back(edges);
return true;
}
m_edges.push_back(edges);
bool leftBoundIsForward;
TEdge *EMin = 0;
// workaround to avoid an endless loop in the while loop below when
// open paths have matching start and end points ...
if (E->Prev->Bot == E->Prev->Top)
E = E->Next;
for (;;) {
E = FindNextLocMin(E);
if (E == EMin)
break;
else if (!EMin)
EMin = E;
// E and E.Prev now share a local minima (left aligned if horizontal).
// Compare their slopes to find which starts which bound ...
MinimaList::value_type locMin;
locMin.Y = E->Bot.Y;
if (E->Dx < E->Prev->Dx) {
locMin.LeftBound = E->Prev;
locMin.RightBound = E;
leftBoundIsForward = false; // Q.nextInLML = Q.prev
} else {
locMin.LeftBound = E;
locMin.RightBound = E->Prev;
leftBoundIsForward = true; // Q.nextInLML = Q.next
}
if (!Closed)
locMin.LeftBound->WindDelta = 0;
else if (locMin.LeftBound->Next == locMin.RightBound)
locMin.LeftBound->WindDelta = -1;
else
locMin.LeftBound->WindDelta = 1;
locMin.RightBound->WindDelta = -locMin.LeftBound->WindDelta;
E = ProcessBound(locMin.LeftBound, leftBoundIsForward);
if (E->OutIdx == Skip)
E = ProcessBound(E, leftBoundIsForward);
TEdge *E2 = ProcessBound(locMin.RightBound, !leftBoundIsForward);
if (E2->OutIdx == Skip)
E2 = ProcessBound(E2, !leftBoundIsForward);
if (locMin.LeftBound->OutIdx == Skip)
locMin.LeftBound = 0;
else if (locMin.RightBound->OutIdx == Skip)
locMin.RightBound = 0;
m_MinimaList.push_back(locMin);
if (!leftBoundIsForward)
E = E2;
}
return true;
}
//------------------------------------------------------------------------------
bool ClipperBase::AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed) {
bool result = false;
for (Paths::size_type i = 0; i < ppg.size(); ++i)
if (AddPath(ppg[i], PolyTyp, Closed))
result = true;
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::Clear() {
DisposeLocalMinimaList();
for (EdgeList::size_type i = 0; i < m_edges.size(); ++i) {
TEdge *edges = m_edges[i];
delete[] edges;
}
m_edges.clear();
m_UseFullRange = false;
m_HasOpenPaths = false;
}
//------------------------------------------------------------------------------
void ClipperBase::Reset() {
m_CurrentLM = m_MinimaList.begin();
if (m_CurrentLM == m_MinimaList.end())
return; // ie nothing to process
std::sort(m_MinimaList.begin(), m_MinimaList.end(), LocMinSorter());
m_Scanbeam = ScanbeamList(); // clears/resets priority_queue
// reset all edges ...
for (MinimaList::iterator lm = m_MinimaList.begin(); lm != m_MinimaList.end();
++lm) {
InsertScanbeam(lm->Y);
TEdge *e = lm->LeftBound;
if (e) {
e->Curr = e->Bot;
e->Side = esLeft;
e->OutIdx = Unassigned;
}
e = lm->RightBound;
if (e) {
e->Curr = e->Bot;
e->Side = esRight;
e->OutIdx = Unassigned;
}
}
m_ActiveEdges = 0;
m_CurrentLM = m_MinimaList.begin();
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeLocalMinimaList() {
m_MinimaList.clear();
m_CurrentLM = m_MinimaList.begin();
}
//------------------------------------------------------------------------------
bool ClipperBase::PopLocalMinima(cInt Y, const LocalMinimum *&locMin) {
if (m_CurrentLM == m_MinimaList.end() || (*m_CurrentLM).Y != Y)
return false;
locMin = &(*m_CurrentLM);
++m_CurrentLM;
return true;
}
//------------------------------------------------------------------------------
IntRect ClipperBase::GetBounds() {
IntRect result;
MinimaList::iterator lm = m_MinimaList.begin();
if (lm == m_MinimaList.end()) {
result.left = result.top = result.right = result.bottom = 0;
return result;
}
result.left = lm->LeftBound->Bot.X;
result.top = lm->LeftBound->Bot.Y;
result.right = lm->LeftBound->Bot.X;
result.bottom = lm->LeftBound->Bot.Y;
while (lm != m_MinimaList.end()) {
// todo - needs fixing for open paths
result.bottom = std::max(result.bottom, lm->LeftBound->Bot.Y);
TEdge *e = lm->LeftBound;
for (;;) {
TEdge *bottomE = e;
while (e->NextInLML) {
if (e->Bot.X < result.left)
result.left = e->Bot.X;
if (e->Bot.X > result.right)
result.right = e->Bot.X;
e = e->NextInLML;
}
result.left = std::min(result.left, e->Bot.X);
result.right = std::max(result.right, e->Bot.X);
result.left = std::min(result.left, e->Top.X);
result.right = std::max(result.right, e->Top.X);
result.top = std::min(result.top, e->Top.Y);
if (bottomE == lm->LeftBound)
e = lm->RightBound;
else
break;
}
++lm;
}
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::InsertScanbeam(const cInt Y) { m_Scanbeam.push(Y); }
//------------------------------------------------------------------------------
bool ClipperBase::PopScanbeam(cInt &Y) {
if (m_Scanbeam.empty())
return false;
Y = m_Scanbeam.top();
m_Scanbeam.pop();
while (!m_Scanbeam.empty() && Y == m_Scanbeam.top()) {
m_Scanbeam.pop();
} // Pop duplicates.
return true;
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeAllOutRecs() {
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i)
DisposeOutRec(i);
m_PolyOuts.clear();
}
//------------------------------------------------------------------------------
void ClipperBase::DisposeOutRec(PolyOutList::size_type index) {
OutRec *outRec = m_PolyOuts[index];
if (outRec->Pts)
DisposeOutPts(outRec->Pts);
delete outRec;
m_PolyOuts[index] = 0;
}
//------------------------------------------------------------------------------
void ClipperBase::DeleteFromAEL(TEdge *e) {
TEdge *AelPrev = e->PrevInAEL;
TEdge *AelNext = e->NextInAEL;
if (!AelPrev && !AelNext && (e != m_ActiveEdges))
return; // already deleted
if (AelPrev)
AelPrev->NextInAEL = AelNext;
else
m_ActiveEdges = AelNext;
if (AelNext)
AelNext->PrevInAEL = AelPrev;
e->NextInAEL = 0;
e->PrevInAEL = 0;
}
//------------------------------------------------------------------------------
OutRec *ClipperBase::CreateOutRec() {
OutRec *result = new OutRec;
result->IsHole = false;
result->IsOpen = false;
result->FirstLeft = 0;
result->Pts = 0;
result->BottomPt = 0;
result->PolyNd = 0;
m_PolyOuts.push_back(result);
result->Idx = (int)m_PolyOuts.size() - 1;
return result;
}
//------------------------------------------------------------------------------
void ClipperBase::SwapPositionsInAEL(TEdge *Edge1, TEdge *Edge2) {
// check that one or other edge hasn't already been removed from AEL ...
if (Edge1->NextInAEL == Edge1->PrevInAEL ||
Edge2->NextInAEL == Edge2->PrevInAEL)
return;
if (Edge1->NextInAEL == Edge2) {
TEdge *Next = Edge2->NextInAEL;
if (Next)
Next->PrevInAEL = Edge1;
TEdge *Prev = Edge1->PrevInAEL;
if (Prev)
Prev->NextInAEL = Edge2;
Edge2->PrevInAEL = Prev;
Edge2->NextInAEL = Edge1;
Edge1->PrevInAEL = Edge2;
Edge1->NextInAEL = Next;
} else if (Edge2->NextInAEL == Edge1) {
TEdge *Next = Edge1->NextInAEL;
if (Next)
Next->PrevInAEL = Edge2;
TEdge *Prev = Edge2->PrevInAEL;
if (Prev)
Prev->NextInAEL = Edge1;
Edge1->PrevInAEL = Prev;
Edge1->NextInAEL = Edge2;
Edge2->PrevInAEL = Edge1;
Edge2->NextInAEL = Next;
} else {
TEdge *Next = Edge1->NextInAEL;
TEdge *Prev = Edge1->PrevInAEL;
Edge1->NextInAEL = Edge2->NextInAEL;
if (Edge1->NextInAEL)
Edge1->NextInAEL->PrevInAEL = Edge1;
Edge1->PrevInAEL = Edge2->PrevInAEL;
if (Edge1->PrevInAEL)
Edge1->PrevInAEL->NextInAEL = Edge1;
Edge2->NextInAEL = Next;
if (Edge2->NextInAEL)
Edge2->NextInAEL->PrevInAEL = Edge2;
Edge2->PrevInAEL = Prev;
if (Edge2->PrevInAEL)
Edge2->PrevInAEL->NextInAEL = Edge2;
}
if (!Edge1->PrevInAEL)
m_ActiveEdges = Edge1;
else if (!Edge2->PrevInAEL)
m_ActiveEdges = Edge2;
}
//------------------------------------------------------------------------------
void ClipperBase::UpdateEdgeIntoAEL(TEdge *&e) {
if (!e->NextInLML)
throw clipperException("UpdateEdgeIntoAEL: invalid call");
e->NextInLML->OutIdx = e->OutIdx;
TEdge *AelPrev = e->PrevInAEL;
TEdge *AelNext = e->NextInAEL;
if (AelPrev)
AelPrev->NextInAEL = e->NextInLML;
else
m_ActiveEdges = e->NextInLML;
if (AelNext)
AelNext->PrevInAEL = e->NextInLML;
e->NextInLML->Side = e->Side;
e->NextInLML->WindDelta = e->WindDelta;
e->NextInLML->WindCnt = e->WindCnt;
e->NextInLML->WindCnt2 = e->WindCnt2;
e = e->NextInLML;
e->Curr = e->Bot;
e->PrevInAEL = AelPrev;
e->NextInAEL = AelNext;
if (!IsHorizontal(*e))
InsertScanbeam(e->Top.Y);
}
//------------------------------------------------------------------------------
bool ClipperBase::LocalMinimaPending() {
return (m_CurrentLM != m_MinimaList.end());
}
//------------------------------------------------------------------------------
// TClipper methods ...
//------------------------------------------------------------------------------
Clipper::Clipper(int initOptions)
: ClipperBase() // constructor
{
m_ExecuteLocked = false;
m_UseFullRange = false;
m_ReverseOutput = ((initOptions & ioReverseSolution) != 0);
m_StrictSimple = ((initOptions & ioStrictlySimple) != 0);
m_PreserveCollinear = ((initOptions & ioPreserveCollinear) != 0);
m_HasOpenPaths = false;
#ifdef use_xyz
m_ZFill = 0;
#endif
}
//------------------------------------------------------------------------------
#ifdef use_xyz
void Clipper::ZFillFunction(ZFillCallback zFillFunc) { m_ZFill = zFillFunc; }
//------------------------------------------------------------------------------
#endif
bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType fillType) {
return Execute(clipType, solution, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
PolyFillType fillType) {
return Execute(clipType, polytree, fillType, fillType);
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, Paths &solution,
PolyFillType subjFillType, PolyFillType clipFillType) {
if (m_ExecuteLocked)
return false;
if (m_HasOpenPaths)
throw clipperException(
"Error: PolyTree struct is needed for open path clipping.");
m_ExecuteLocked = true;
solution.resize(0);
m_SubjFillType = subjFillType;
m_ClipFillType = clipFillType;
m_ClipType = clipType;
m_UsingPolyTree = false;
bool succeeded = ExecuteInternal();
if (succeeded)
BuildResult(solution);
DisposeAllOutRecs();
m_ExecuteLocked = false;
return succeeded;
}
//------------------------------------------------------------------------------
bool Clipper::Execute(ClipType clipType, PolyTree &polytree,
PolyFillType subjFillType, PolyFillType clipFillType) {
if (m_ExecuteLocked)
return false;
m_ExecuteLocked = true;
m_SubjFillType = subjFillType;
m_ClipFillType = clipFillType;
m_ClipType = clipType;
m_UsingPolyTree = true;
bool succeeded = ExecuteInternal();
if (succeeded)
BuildResult2(polytree);
DisposeAllOutRecs();
m_ExecuteLocked = false;
return succeeded;
}
//------------------------------------------------------------------------------
void Clipper::FixHoleLinkage(OutRec &outrec) {
// skip OutRecs that (a) contain outermost polygons or
//(b) already have the correct owner/child linkage ...
if (!outrec.FirstLeft ||
(outrec.IsHole != outrec.FirstLeft->IsHole && outrec.FirstLeft->Pts))
return;
OutRec *orfl = outrec.FirstLeft;
while (orfl && ((orfl->IsHole == outrec.IsHole) || !orfl->Pts))
orfl = orfl->FirstLeft;
outrec.FirstLeft = orfl;
}
//------------------------------------------------------------------------------
bool Clipper::ExecuteInternal() {
bool succeeded = true;
try {
Reset();
m_Maxima = MaximaList();
m_SortedEdges = 0;
succeeded = true;
cInt botY, topY;
if (!PopScanbeam(botY))
return false;
InsertLocalMinimaIntoAEL(botY);
while (PopScanbeam(topY) || LocalMinimaPending()) {
ProcessHorizontals();
ClearGhostJoins();
if (!ProcessIntersections(topY)) {
succeeded = false;
break;
}
ProcessEdgesAtTopOfScanbeam(topY);
botY = topY;
InsertLocalMinimaIntoAEL(botY);
}
} catch (...) {
succeeded = false;
}
if (succeeded) {
// fix orientations ...
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts || outRec->IsOpen)
continue;
if ((outRec->IsHole ^ m_ReverseOutput) == (Area(*outRec) > 0))
ReversePolyPtLinks(outRec->Pts);
}
if (!m_Joins.empty())
JoinCommonEdges();
// unfortunately FixupOutPolygon() must be done after JoinCommonEdges()
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts)
continue;
if (outRec->IsOpen)
FixupOutPolyline(*outRec);
else
FixupOutPolygon(*outRec);
}
if (m_StrictSimple)
DoSimplePolygons();
}
ClearJoins();
ClearGhostJoins();
return succeeded;
}
//------------------------------------------------------------------------------
void Clipper::SetWindingCount(TEdge &edge) {
TEdge *e = edge.PrevInAEL;
// find the edge of the same polytype that immediately preceeds 'edge' in AEL
while (e && ((e->PolyTyp != edge.PolyTyp) || (e->WindDelta == 0)))
e = e->PrevInAEL;
if (!e) {
if (edge.WindDelta == 0) {
PolyFillType pft =
(edge.PolyTyp == ptSubject ? m_SubjFillType : m_ClipFillType);
edge.WindCnt = (pft == pftNegative ? -1 : 1);
} else
edge.WindCnt = edge.WindDelta;
edge.WindCnt2 = 0;
e = m_ActiveEdges; // ie get ready to calc WindCnt2
} else if (edge.WindDelta == 0 && m_ClipType != ctUnion) {
edge.WindCnt = 1;
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
} else if (IsEvenOddFillType(edge)) {
// EvenOdd filling ...
if (edge.WindDelta == 0) {
// are we inside a subj polygon ...
bool Inside = true;
TEdge *e2 = e->PrevInAEL;
while (e2) {
if (e2->PolyTyp == e->PolyTyp && e2->WindDelta != 0)
Inside = !Inside;
e2 = e2->PrevInAEL;
}
edge.WindCnt = (Inside ? 0 : 1);
} else {
edge.WindCnt = edge.WindDelta;
}
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
} else {
// nonZero, Positive or Negative filling ...
if (e->WindCnt * e->WindDelta < 0) {
// prev edge is 'decreasing' WindCount (WC) toward zero
// so we're outside the previous polygon ...
if (Abs(e->WindCnt) > 1) {
// outside prev poly but still inside another.
// when reversing direction of prev poly use the same WC
if (e->WindDelta * edge.WindDelta < 0)
edge.WindCnt = e->WindCnt;
// otherwise continue to 'decrease' WC ...
else
edge.WindCnt = e->WindCnt + edge.WindDelta;
} else
// now outside all polys of same polytype so set own WC ...
edge.WindCnt = (edge.WindDelta == 0 ? 1 : edge.WindDelta);
} else {
// prev edge is 'increasing' WindCount (WC) away from zero
// so we're inside the previous polygon ...
if (edge.WindDelta == 0)
edge.WindCnt = (e->WindCnt < 0 ? e->WindCnt - 1 : e->WindCnt + 1);
// if wind direction is reversing prev then use same WC
else if (e->WindDelta * edge.WindDelta < 0)
edge.WindCnt = e->WindCnt;
// otherwise add to WC ...
else
edge.WindCnt = e->WindCnt + edge.WindDelta;
}
edge.WindCnt2 = e->WindCnt2;
e = e->NextInAEL; // ie get ready to calc WindCnt2
}
// update WindCnt2 ...
if (IsEvenOddAltFillType(edge)) {
// EvenOdd filling ...
while (e != &edge) {
if (e->WindDelta != 0)
edge.WindCnt2 = (edge.WindCnt2 == 0 ? 1 : 0);
e = e->NextInAEL;
}
} else {
// nonZero, Positive or Negative filling ...
while (e != &edge) {
edge.WindCnt2 += e->WindDelta;
e = e->NextInAEL;
}
}
}
//------------------------------------------------------------------------------
bool Clipper::IsEvenOddFillType(const TEdge &edge) const {
if (edge.PolyTyp == ptSubject)
return m_SubjFillType == pftEvenOdd;
else
return m_ClipFillType == pftEvenOdd;
}
//------------------------------------------------------------------------------
bool Clipper::IsEvenOddAltFillType(const TEdge &edge) const {
if (edge.PolyTyp == ptSubject)
return m_ClipFillType == pftEvenOdd;
else
return m_SubjFillType == pftEvenOdd;
}
//------------------------------------------------------------------------------
bool Clipper::IsContributing(const TEdge &edge) const {
PolyFillType pft, pft2;
if (edge.PolyTyp == ptSubject) {
pft = m_SubjFillType;
pft2 = m_ClipFillType;
} else {
pft = m_ClipFillType;
pft2 = m_SubjFillType;
}
switch (pft) {
case pftEvenOdd:
// return false if a subj line has been flagged as inside a subj polygon
if (edge.WindDelta == 0 && edge.WindCnt != 1)
return false;
break;
case pftNonZero:
if (Abs(edge.WindCnt) != 1)
return false;
break;
case pftPositive:
if (edge.WindCnt != 1)
return false;
break;
default: // pftNegative
if (edge.WindCnt != -1)
return false;
}
switch (m_ClipType) {
case ctIntersection:
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 != 0);
case pftPositive:
return (edge.WindCnt2 > 0);
default:
return (edge.WindCnt2 < 0);
}
break;
case ctUnion:
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
break;
case ctDifference:
if (edge.PolyTyp == ptSubject)
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
else
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 != 0);
case pftPositive:
return (edge.WindCnt2 > 0);
default:
return (edge.WindCnt2 < 0);
}
break;
case ctXor:
if (edge.WindDelta == 0) // XOr always contributing unless open
switch (pft2) {
case pftEvenOdd:
case pftNonZero:
return (edge.WindCnt2 == 0);
case pftPositive:
return (edge.WindCnt2 <= 0);
default:
return (edge.WindCnt2 >= 0);
}
else
return true;
break;
default:
return true;
}
}
//------------------------------------------------------------------------------
OutPt *Clipper::AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
OutPt *result;
TEdge *e, *prevE;
if (IsHorizontal(*e2) || (e1->Dx > e2->Dx)) {
result = AddOutPt(e1, Pt);
e2->OutIdx = e1->OutIdx;
e1->Side = esLeft;
e2->Side = esRight;
e = e1;
if (e->PrevInAEL == e2)
prevE = e2->PrevInAEL;
else
prevE = e->PrevInAEL;
} else {
result = AddOutPt(e2, Pt);
e1->OutIdx = e2->OutIdx;
e1->Side = esRight;
e2->Side = esLeft;
e = e2;
if (e->PrevInAEL == e1)
prevE = e1->PrevInAEL;
else
prevE = e->PrevInAEL;
}
if (prevE && prevE->OutIdx >= 0 && prevE->Top.Y < Pt.Y && e->Top.Y < Pt.Y) {
cInt xPrev = TopX(*prevE, Pt.Y);
cInt xE = TopX(*e, Pt.Y);
if (xPrev == xE && (e->WindDelta != 0) && (prevE->WindDelta != 0) &&
SlopesEqual(IntPoint(xPrev, Pt.Y), prevE->Top, IntPoint(xE, Pt.Y),
e->Top, m_UseFullRange)) {
OutPt *outPt = AddOutPt(prevE, Pt);
AddJoin(result, outPt, e->Top);
}
}
return result;
}
//------------------------------------------------------------------------------
void Clipper::AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &Pt) {
AddOutPt(e1, Pt);
if (e2->WindDelta == 0)
AddOutPt(e2, Pt);
if (e1->OutIdx == e2->OutIdx) {
e1->OutIdx = Unassigned;
e2->OutIdx = Unassigned;
} else if (e1->OutIdx < e2->OutIdx)
AppendPolygon(e1, e2);
else
AppendPolygon(e2, e1);
}
//------------------------------------------------------------------------------
void Clipper::AddEdgeToSEL(TEdge *edge) {
// SEL pointers in PEdge are reused to build a list of horizontal edges.
// However, we don't need to worry about order with horizontal edge
// processing.
if (!m_SortedEdges) {
m_SortedEdges = edge;
edge->PrevInSEL = 0;
edge->NextInSEL = 0;
} else {
edge->NextInSEL = m_SortedEdges;
edge->PrevInSEL = 0;
m_SortedEdges->PrevInSEL = edge;
m_SortedEdges = edge;
}
}
//------------------------------------------------------------------------------
bool Clipper::PopEdgeFromSEL(TEdge *&edge) {
if (!m_SortedEdges)
return false;
edge = m_SortedEdges;
DeleteFromSEL(m_SortedEdges);
return true;
}
//------------------------------------------------------------------------------
void Clipper::CopyAELToSEL() {
TEdge *e = m_ActiveEdges;
m_SortedEdges = e;
while (e) {
e->PrevInSEL = e->PrevInAEL;
e->NextInSEL = e->NextInAEL;
e = e->NextInAEL;
}
}
//------------------------------------------------------------------------------
void Clipper::AddJoin(OutPt *op1, OutPt *op2, const IntPoint OffPt) {
Join *j = new Join;
j->OutPt1 = op1;
j->OutPt2 = op2;
j->OffPt = OffPt;
m_Joins.push_back(j);
}
//------------------------------------------------------------------------------
void Clipper::ClearJoins() {
for (JoinList::size_type i = 0; i < m_Joins.size(); i++)
delete m_Joins[i];
m_Joins.resize(0);
}
//------------------------------------------------------------------------------
void Clipper::ClearGhostJoins() {
for (JoinList::size_type i = 0; i < m_GhostJoins.size(); i++)
delete m_GhostJoins[i];
m_GhostJoins.resize(0);
}
//------------------------------------------------------------------------------
void Clipper::AddGhostJoin(OutPt *op, const IntPoint OffPt) {
Join *j = new Join;
j->OutPt1 = op;
j->OutPt2 = 0;
j->OffPt = OffPt;
m_GhostJoins.push_back(j);
}
//------------------------------------------------------------------------------
void Clipper::InsertLocalMinimaIntoAEL(const cInt botY) {
const LocalMinimum *lm;
while (PopLocalMinima(botY, lm)) {
TEdge *lb = lm->LeftBound;
TEdge *rb = lm->RightBound;
OutPt *Op1 = 0;
if (!lb) {
// nb: don't insert LB into either AEL or SEL
InsertEdgeIntoAEL(rb, 0);
SetWindingCount(*rb);
if (IsContributing(*rb))
Op1 = AddOutPt(rb, rb->Bot);
} else if (!rb) {
InsertEdgeIntoAEL(lb, 0);
SetWindingCount(*lb);
if (IsContributing(*lb))
Op1 = AddOutPt(lb, lb->Bot);
InsertScanbeam(lb->Top.Y);
} else {
InsertEdgeIntoAEL(lb, 0);
InsertEdgeIntoAEL(rb, lb);
SetWindingCount(*lb);
rb->WindCnt = lb->WindCnt;
rb->WindCnt2 = lb->WindCnt2;
if (IsContributing(*lb))
Op1 = AddLocalMinPoly(lb, rb, lb->Bot);
InsertScanbeam(lb->Top.Y);
}
if (rb) {
if (IsHorizontal(*rb)) {
AddEdgeToSEL(rb);
if (rb->NextInLML)
InsertScanbeam(rb->NextInLML->Top.Y);
} else
InsertScanbeam(rb->Top.Y);
}
if (!lb || !rb)
continue;
// if any output polygons share an edge, they'll need joining later ...
if (Op1 && IsHorizontal(*rb) && m_GhostJoins.size() > 0 &&
(rb->WindDelta != 0)) {
for (JoinList::size_type i = 0; i < m_GhostJoins.size(); ++i) {
Join *jr = m_GhostJoins[i];
// if the horizontal Rb and a 'ghost' horizontal overlap, then convert
// the 'ghost' join to a real join ready for later ...
if (HorzSegmentsOverlap(jr->OutPt1->Pt.X, jr->OffPt.X, rb->Bot.X,
rb->Top.X))
AddJoin(jr->OutPt1, Op1, jr->OffPt);
}
}
if (lb->OutIdx >= 0 && lb->PrevInAEL &&
lb->PrevInAEL->Curr.X == lb->Bot.X && lb->PrevInAEL->OutIdx >= 0 &&
SlopesEqual(lb->PrevInAEL->Bot, lb->PrevInAEL->Top, lb->Curr, lb->Top,
m_UseFullRange) &&
(lb->WindDelta != 0) && (lb->PrevInAEL->WindDelta != 0)) {
OutPt *Op2 = AddOutPt(lb->PrevInAEL, lb->Bot);
AddJoin(Op1, Op2, lb->Top);
}
if (lb->NextInAEL != rb) {
if (rb->OutIdx >= 0 && rb->PrevInAEL->OutIdx >= 0 &&
SlopesEqual(rb->PrevInAEL->Curr, rb->PrevInAEL->Top, rb->Curr,
rb->Top, m_UseFullRange) &&
(rb->WindDelta != 0) && (rb->PrevInAEL->WindDelta != 0)) {
OutPt *Op2 = AddOutPt(rb->PrevInAEL, rb->Bot);
AddJoin(Op1, Op2, rb->Top);
}
TEdge *e = lb->NextInAEL;
if (e) {
while (e != rb) {
// nb: For calculating winding counts etc, IntersectEdges() assumes
// that param1 will be to the Right of param2 ABOVE the intersection
// ...
IntersectEdges(rb, e, lb->Curr); // order important here
e = e->NextInAEL;
}
}
}
}
}
//------------------------------------------------------------------------------
void Clipper::DeleteFromSEL(TEdge *e) {
TEdge *SelPrev = e->PrevInSEL;
TEdge *SelNext = e->NextInSEL;
if (!SelPrev && !SelNext && (e != m_SortedEdges))
return; // already deleted
if (SelPrev)
SelPrev->NextInSEL = SelNext;
else
m_SortedEdges = SelNext;
if (SelNext)
SelNext->PrevInSEL = SelPrev;
e->NextInSEL = 0;
e->PrevInSEL = 0;
}
//------------------------------------------------------------------------------
#ifdef use_xyz
void Clipper::SetZ(IntPoint &pt, TEdge &e1, TEdge &e2) {
if (pt.Z != 0 || !m_ZFill)
return;
else if (pt == e1.Bot)
pt.Z = e1.Bot.Z;
else if (pt == e1.Top)
pt.Z = e1.Top.Z;
else if (pt == e2.Bot)
pt.Z = e2.Bot.Z;
else if (pt == e2.Top)
pt.Z = e2.Top.Z;
else
(*m_ZFill)(e1.Bot, e1.Top, e2.Bot, e2.Top, pt);
}
//------------------------------------------------------------------------------
#endif
void Clipper::IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &Pt) {
bool e1Contributing = (e1->OutIdx >= 0);
bool e2Contributing = (e2->OutIdx >= 0);
#ifdef use_xyz
SetZ(Pt, *e1, *e2);
#endif
#ifdef use_lines
// if either edge is on an OPEN path ...
if (e1->WindDelta == 0 || e2->WindDelta == 0) {
// ignore subject-subject open path intersections UNLESS they
// are both open paths, AND they are both 'contributing maximas' ...
if (e1->WindDelta == 0 && e2->WindDelta == 0)
return;
// if intersecting a subj line with a subj poly ...
else if (e1->PolyTyp == e2->PolyTyp && e1->WindDelta != e2->WindDelta &&
m_ClipType == ctUnion) {
if (e1->WindDelta == 0) {
if (e2Contributing) {
AddOutPt(e1, Pt);
if (e1Contributing)
e1->OutIdx = Unassigned;
}
} else {
if (e1Contributing) {
AddOutPt(e2, Pt);
if (e2Contributing)
e2->OutIdx = Unassigned;
}
}
} else if (e1->PolyTyp != e2->PolyTyp) {
// toggle subj open path OutIdx on/off when Abs(clip.WndCnt) == 1 ...
if ((e1->WindDelta == 0) && abs(e2->WindCnt) == 1 &&
(m_ClipType != ctUnion || e2->WindCnt2 == 0)) {
AddOutPt(e1, Pt);
if (e1Contributing)
e1->OutIdx = Unassigned;
} else if ((e2->WindDelta == 0) && (abs(e1->WindCnt) == 1) &&
(m_ClipType != ctUnion || e1->WindCnt2 == 0)) {
AddOutPt(e2, Pt);
if (e2Contributing)
e2->OutIdx = Unassigned;
}
}
return;
}
#endif
// update winding counts...
// assumes that e1 will be to the Right of e2 ABOVE the intersection
if (e1->PolyTyp == e2->PolyTyp) {
if (IsEvenOddFillType(*e1)) {
int oldE1WindCnt = e1->WindCnt;
e1->WindCnt = e2->WindCnt;
e2->WindCnt = oldE1WindCnt;
} else {
if (e1->WindCnt + e2->WindDelta == 0)
e1->WindCnt = -e1->WindCnt;
else
e1->WindCnt += e2->WindDelta;
if (e2->WindCnt - e1->WindDelta == 0)
e2->WindCnt = -e2->WindCnt;
else
e2->WindCnt -= e1->WindDelta;
}
} else {
if (!IsEvenOddFillType(*e2))
e1->WindCnt2 += e2->WindDelta;
else
e1->WindCnt2 = (e1->WindCnt2 == 0) ? 1 : 0;
if (!IsEvenOddFillType(*e1))
e2->WindCnt2 -= e1->WindDelta;
else
e2->WindCnt2 = (e2->WindCnt2 == 0) ? 1 : 0;
}
PolyFillType e1FillType, e2FillType, e1FillType2, e2FillType2;
if (e1->PolyTyp == ptSubject) {
e1FillType = m_SubjFillType;
e1FillType2 = m_ClipFillType;
} else {
e1FillType = m_ClipFillType;
e1FillType2 = m_SubjFillType;
}
if (e2->PolyTyp == ptSubject) {
e2FillType = m_SubjFillType;
e2FillType2 = m_ClipFillType;
} else {
e2FillType = m_ClipFillType;
e2FillType2 = m_SubjFillType;
}
cInt e1Wc, e2Wc;
switch (e1FillType) {
case pftPositive:
e1Wc = e1->WindCnt;
break;
case pftNegative:
e1Wc = -e1->WindCnt;
break;
default:
e1Wc = Abs(e1->WindCnt);
}
switch (e2FillType) {
case pftPositive:
e2Wc = e2->WindCnt;
break;
case pftNegative:
e2Wc = -e2->WindCnt;
break;
default:
e2Wc = Abs(e2->WindCnt);
}
if (e1Contributing && e2Contributing) {
if ((e1Wc != 0 && e1Wc != 1) || (e2Wc != 0 && e2Wc != 1) ||
(e1->PolyTyp != e2->PolyTyp && m_ClipType != ctXor)) {
AddLocalMaxPoly(e1, e2, Pt);
} else {
AddOutPt(e1, Pt);
AddOutPt(e2, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if (e1Contributing) {
if (e2Wc == 0 || e2Wc == 1) {
AddOutPt(e1, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if (e2Contributing) {
if (e1Wc == 0 || e1Wc == 1) {
AddOutPt(e2, Pt);
SwapSides(*e1, *e2);
SwapPolyIndexes(*e1, *e2);
}
} else if ((e1Wc == 0 || e1Wc == 1) && (e2Wc == 0 || e2Wc == 1)) {
// neither edge is currently contributing ...
cInt e1Wc2, e2Wc2;
switch (e1FillType2) {
case pftPositive:
e1Wc2 = e1->WindCnt2;
break;
case pftNegative:
e1Wc2 = -e1->WindCnt2;
break;
default:
e1Wc2 = Abs(e1->WindCnt2);
}
switch (e2FillType2) {
case pftPositive:
e2Wc2 = e2->WindCnt2;
break;
case pftNegative:
e2Wc2 = -e2->WindCnt2;
break;
default:
e2Wc2 = Abs(e2->WindCnt2);
}
if (e1->PolyTyp != e2->PolyTyp) {
AddLocalMinPoly(e1, e2, Pt);
} else if (e1Wc == 1 && e2Wc == 1)
switch (m_ClipType) {
case ctIntersection:
if (e1Wc2 > 0 && e2Wc2 > 0)
AddLocalMinPoly(e1, e2, Pt);
break;
case ctUnion:
if (e1Wc2 <= 0 && e2Wc2 <= 0)
AddLocalMinPoly(e1, e2, Pt);
break;
case ctDifference:
if (((e1->PolyTyp == ptClip) && (e1Wc2 > 0) && (e2Wc2 > 0)) ||
((e1->PolyTyp == ptSubject) && (e1Wc2 <= 0) && (e2Wc2 <= 0)))
AddLocalMinPoly(e1, e2, Pt);
break;
case ctXor:
AddLocalMinPoly(e1, e2, Pt);
}
else
SwapSides(*e1, *e2);
}
}
//------------------------------------------------------------------------------
void Clipper::SetHoleState(TEdge *e, OutRec *outrec) {
TEdge *e2 = e->PrevInAEL;
TEdge *eTmp = 0;
while (e2) {
if (e2->OutIdx >= 0 && e2->WindDelta != 0) {
if (!eTmp)
eTmp = e2;
else if (eTmp->OutIdx == e2->OutIdx)
eTmp = 0;
}
e2 = e2->PrevInAEL;
}
if (!eTmp) {
outrec->FirstLeft = 0;
outrec->IsHole = false;
} else {
outrec->FirstLeft = m_PolyOuts[eTmp->OutIdx];
outrec->IsHole = !outrec->FirstLeft->IsHole;
}
}
//------------------------------------------------------------------------------
OutRec *GetLowermostRec(OutRec *outRec1, OutRec *outRec2) {
// work out which polygon fragment has the correct hole state ...
if (!outRec1->BottomPt)
outRec1->BottomPt = GetBottomPt(outRec1->Pts);
if (!outRec2->BottomPt)
outRec2->BottomPt = GetBottomPt(outRec2->Pts);
OutPt *OutPt1 = outRec1->BottomPt;
OutPt *OutPt2 = outRec2->BottomPt;
if (OutPt1->Pt.Y > OutPt2->Pt.Y)
return outRec1;
else if (OutPt1->Pt.Y < OutPt2->Pt.Y)
return outRec2;
else if (OutPt1->Pt.X < OutPt2->Pt.X)
return outRec1;
else if (OutPt1->Pt.X > OutPt2->Pt.X)
return outRec2;
else if (OutPt1->Next == OutPt1)
return outRec2;
else if (OutPt2->Next == OutPt2)
return outRec1;
else if (FirstIsBottomPt(OutPt1, OutPt2))
return outRec1;
else
return outRec2;
}
//------------------------------------------------------------------------------
bool OutRec1RightOfOutRec2(OutRec *outRec1, OutRec *outRec2) {
do {
outRec1 = outRec1->FirstLeft;
if (outRec1 == outRec2)
return true;
} while (outRec1);
return false;
}
//------------------------------------------------------------------------------
OutRec *Clipper::GetOutRec(int Idx) {
OutRec *outrec = m_PolyOuts[Idx];
while (outrec != m_PolyOuts[outrec->Idx])
outrec = m_PolyOuts[outrec->Idx];
return outrec;
}
//------------------------------------------------------------------------------
void Clipper::AppendPolygon(TEdge *e1, TEdge *e2) {
// get the start and ends of both output polygons ...
OutRec *outRec1 = m_PolyOuts[e1->OutIdx];
OutRec *outRec2 = m_PolyOuts[e2->OutIdx];
OutRec *holeStateRec;
if (OutRec1RightOfOutRec2(outRec1, outRec2))
holeStateRec = outRec2;
else if (OutRec1RightOfOutRec2(outRec2, outRec1))
holeStateRec = outRec1;
else
holeStateRec = GetLowermostRec(outRec1, outRec2);
// get the start and ends of both output polygons and
// join e2 poly onto e1 poly and delete pointers to e2 ...
OutPt *p1_lft = outRec1->Pts;
OutPt *p1_rt = p1_lft->Prev;
OutPt *p2_lft = outRec2->Pts;
OutPt *p2_rt = p2_lft->Prev;
// join e2 poly onto e1 poly and delete pointers to e2 ...
if (e1->Side == esLeft) {
if (e2->Side == esLeft) {
// z y x a b c
ReversePolyPtLinks(p2_lft);
p2_lft->Next = p1_lft;
p1_lft->Prev = p2_lft;
p1_rt->Next = p2_rt;
p2_rt->Prev = p1_rt;
outRec1->Pts = p2_rt;
} else {
// x y z a b c
p2_rt->Next = p1_lft;
p1_lft->Prev = p2_rt;
p2_lft->Prev = p1_rt;
p1_rt->Next = p2_lft;
outRec1->Pts = p2_lft;
}
} else {
if (e2->Side == esRight) {
// a b c z y x
ReversePolyPtLinks(p2_lft);
p1_rt->Next = p2_rt;
p2_rt->Prev = p1_rt;
p2_lft->Next = p1_lft;
p1_lft->Prev = p2_lft;
} else {
// a b c x y z
p1_rt->Next = p2_lft;
p2_lft->Prev = p1_rt;
p1_lft->Prev = p2_rt;
p2_rt->Next = p1_lft;
}
}
outRec1->BottomPt = 0;
if (holeStateRec == outRec2) {
if (outRec2->FirstLeft != outRec1)
outRec1->FirstLeft = outRec2->FirstLeft;
outRec1->IsHole = outRec2->IsHole;
}
outRec2->Pts = 0;
outRec2->BottomPt = 0;
outRec2->FirstLeft = outRec1;
int OKIdx = e1->OutIdx;
int ObsoleteIdx = e2->OutIdx;
e1->OutIdx =
Unassigned; // nb: safe because we only get here via AddLocalMaxPoly
e2->OutIdx = Unassigned;
TEdge *e = m_ActiveEdges;
while (e) {
if (e->OutIdx == ObsoleteIdx) {
e->OutIdx = OKIdx;
e->Side = e1->Side;
break;
}
e = e->NextInAEL;
}
outRec2->Idx = outRec1->Idx;
}
//------------------------------------------------------------------------------
OutPt *Clipper::AddOutPt(TEdge *e, const IntPoint &pt) {
if (e->OutIdx < 0) {
OutRec *outRec = CreateOutRec();
outRec->IsOpen = (e->WindDelta == 0);
OutPt *newOp = new OutPt;
outRec->Pts = newOp;
newOp->Idx = outRec->Idx;
newOp->Pt = pt;
newOp->Next = newOp;
newOp->Prev = newOp;
if (!outRec->IsOpen)
SetHoleState(e, outRec);
e->OutIdx = outRec->Idx;
return newOp;
} else {
OutRec *outRec = m_PolyOuts[e->OutIdx];
// OutRec.Pts is the 'Left-most' point & OutRec.Pts.Prev is the 'Right-most'
OutPt *op = outRec->Pts;
bool ToFront = (e->Side == esLeft);
if (ToFront && (pt == op->Pt))
return op;
else if (!ToFront && (pt == op->Prev->Pt))
return op->Prev;
OutPt *newOp = new OutPt;
newOp->Idx = outRec->Idx;
newOp->Pt = pt;
newOp->Next = op;
newOp->Prev = op->Prev;
newOp->Prev->Next = newOp;
op->Prev = newOp;
if (ToFront)
outRec->Pts = newOp;
return newOp;
}
}
//------------------------------------------------------------------------------
OutPt *Clipper::GetLastOutPt(TEdge *e) {
OutRec *outRec = m_PolyOuts[e->OutIdx];
if (e->Side == esLeft)
return outRec->Pts;
else
return outRec->Pts->Prev;
}
//------------------------------------------------------------------------------
void Clipper::ProcessHorizontals() {
TEdge *horzEdge;
while (PopEdgeFromSEL(horzEdge))
ProcessHorizontal(horzEdge);
}
//------------------------------------------------------------------------------
inline bool IsMinima(TEdge *e) {
return e && (e->Prev->NextInLML != e) && (e->Next->NextInLML != e);
}
//------------------------------------------------------------------------------
inline bool IsMaxima(TEdge *e, const cInt Y) {
return e && e->Top.Y == Y && !e->NextInLML;
}
//------------------------------------------------------------------------------
inline bool IsIntermediate(TEdge *e, const cInt Y) {
return e->Top.Y == Y && e->NextInLML;
}
//------------------------------------------------------------------------------
TEdge *GetMaximaPair(TEdge *e) {
if ((e->Next->Top == e->Top) && !e->Next->NextInLML)
return e->Next;
else if ((e->Prev->Top == e->Top) && !e->Prev->NextInLML)
return e->Prev;
else
return 0;
}
//------------------------------------------------------------------------------
TEdge *GetMaximaPairEx(TEdge *e) {
// as GetMaximaPair() but returns 0 if MaxPair isn't in AEL (unless it's
// horizontal)
TEdge *result = GetMaximaPair(e);
if (result &&
(result->OutIdx == Skip ||
(result->NextInAEL == result->PrevInAEL && !IsHorizontal(*result))))
return 0;
return result;
}
//------------------------------------------------------------------------------
void Clipper::SwapPositionsInSEL(TEdge *Edge1, TEdge *Edge2) {
if (!(Edge1->NextInSEL) && !(Edge1->PrevInSEL))
return;
if (!(Edge2->NextInSEL) && !(Edge2->PrevInSEL))
return;
if (Edge1->NextInSEL == Edge2) {
TEdge *Next = Edge2->NextInSEL;
if (Next)
Next->PrevInSEL = Edge1;
TEdge *Prev = Edge1->PrevInSEL;
if (Prev)
Prev->NextInSEL = Edge2;
Edge2->PrevInSEL = Prev;
Edge2->NextInSEL = Edge1;
Edge1->PrevInSEL = Edge2;
Edge1->NextInSEL = Next;
} else if (Edge2->NextInSEL == Edge1) {
TEdge *Next = Edge1->NextInSEL;
if (Next)
Next->PrevInSEL = Edge2;
TEdge *Prev = Edge2->PrevInSEL;
if (Prev)
Prev->NextInSEL = Edge1;
Edge1->PrevInSEL = Prev;
Edge1->NextInSEL = Edge2;
Edge2->PrevInSEL = Edge1;
Edge2->NextInSEL = Next;
} else {
TEdge *Next = Edge1->NextInSEL;
TEdge *Prev = Edge1->PrevInSEL;
Edge1->NextInSEL = Edge2->NextInSEL;
if (Edge1->NextInSEL)
Edge1->NextInSEL->PrevInSEL = Edge1;
Edge1->PrevInSEL = Edge2->PrevInSEL;
if (Edge1->PrevInSEL)
Edge1->PrevInSEL->NextInSEL = Edge1;
Edge2->NextInSEL = Next;
if (Edge2->NextInSEL)
Edge2->NextInSEL->PrevInSEL = Edge2;
Edge2->PrevInSEL = Prev;
if (Edge2->PrevInSEL)
Edge2->PrevInSEL->NextInSEL = Edge2;
}
if (!Edge1->PrevInSEL)
m_SortedEdges = Edge1;
else if (!Edge2->PrevInSEL)
m_SortedEdges = Edge2;
}
//------------------------------------------------------------------------------
TEdge *GetNextInAEL(TEdge *e, Direction dir) {
return dir == dLeftToRight ? e->NextInAEL : e->PrevInAEL;
}
//------------------------------------------------------------------------------
void GetHorzDirection(TEdge &HorzEdge, Direction &Dir, cInt &Left,
cInt &Right) {
if (HorzEdge.Bot.X < HorzEdge.Top.X) {
Left = HorzEdge.Bot.X;
Right = HorzEdge.Top.X;
Dir = dLeftToRight;
} else {
Left = HorzEdge.Top.X;
Right = HorzEdge.Bot.X;
Dir = dRightToLeft;
}
}
//------------------------------------------------------------------------
/*******************************************************************************
* Notes: Horizontal edges (HEs) at scanline intersections (ie at the Top or *
* Bottom of a scanbeam) are processed as if layered. The order in which HEs *
* are processed doesn't matter. HEs intersect with other HE Bot.Xs only [#] *
* (or they could intersect with Top.Xs only, ie EITHER Bot.Xs OR Top.Xs), *
* and with other non-horizontal edges [*]. Once these intersections are *
* processed, intermediate HEs then 'promote' the Edge above (NextInLML) into *
* the AEL. These 'promoted' edges may in turn intersect [%] with other HEs. *
*******************************************************************************/
void Clipper::ProcessHorizontal(TEdge *horzEdge) {
Direction dir;
cInt horzLeft, horzRight;
bool IsOpen = (horzEdge->WindDelta == 0);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
TEdge *eLastHorz = horzEdge, *eMaxPair = 0;
while (eLastHorz->NextInLML && IsHorizontal(*eLastHorz->NextInLML))
eLastHorz = eLastHorz->NextInLML;
if (!eLastHorz->NextInLML)
eMaxPair = GetMaximaPair(eLastHorz);
MaximaList::const_iterator maxIt;
MaximaList::const_reverse_iterator maxRit;
if (m_Maxima.size() > 0) {
// get the first maxima in range (X) ...
if (dir == dLeftToRight) {
maxIt = m_Maxima.begin();
while (maxIt != m_Maxima.end() && *maxIt <= horzEdge->Bot.X)
maxIt++;
if (maxIt != m_Maxima.end() && *maxIt >= eLastHorz->Top.X)
maxIt = m_Maxima.end();
} else {
maxRit = m_Maxima.rbegin();
while (maxRit != m_Maxima.rend() && *maxRit > horzEdge->Bot.X)
maxRit++;
if (maxRit != m_Maxima.rend() && *maxRit <= eLastHorz->Top.X)
maxRit = m_Maxima.rend();
}
}
OutPt *op1 = 0;
for (;;) // loop through consec. horizontal edges
{
bool IsLastHorz = (horzEdge == eLastHorz);
TEdge *e = GetNextInAEL(horzEdge, dir);
while (e) {
// this code block inserts extra coords into horizontal edges (in output
// polygons) whereever maxima touch these horizontal edges. This helps
//'simplifying' polygons (ie if the Simplify property is set).
if (m_Maxima.size() > 0) {
if (dir == dLeftToRight) {
while (maxIt != m_Maxima.end() && *maxIt < e->Curr.X) {
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.Y));
maxIt++;
}
} else {
while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.X) {
if (horzEdge->OutIdx >= 0 && !IsOpen)
AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.Y));
maxRit++;
}
}
};
if ((dir == dLeftToRight && e->Curr.X > horzRight) ||
(dir == dRightToLeft && e->Curr.X < horzLeft))
break;
// Also break if we've got to the end of an intermediate horizontal edge
// ...
// nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
if (e->Curr.X == horzEdge->Top.X && horzEdge->NextInLML &&
e->Dx < horzEdge->NextInLML->Dx)
break;
if (horzEdge->OutIdx >= 0 && !IsOpen) // note: may be done multiple times
{
#ifdef use_xyz
if (dir == dLeftToRight)
SetZ(e->Curr, *horzEdge, *e);
else
SetZ(e->Curr, *e, *horzEdge);
#endif
op1 = AddOutPt(horzEdge, e->Curr);
TEdge *eNextHorz = m_SortedEdges;
while (eNextHorz) {
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
eNextHorz->Bot.X, eNextHorz->Top.X)) {
OutPt *op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Bot);
}
// OK, so far we're still in range of the horizontal Edge but make sure
// we're at the last of consec. horizontals when matching with eMaxPair
if (e == eMaxPair && IsLastHorz) {
if (horzEdge->OutIdx >= 0)
AddLocalMaxPoly(horzEdge, eMaxPair, horzEdge->Top);
DeleteFromAEL(horzEdge);
DeleteFromAEL(eMaxPair);
return;
}
if (dir == dLeftToRight) {
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges(horzEdge, e, Pt);
} else {
IntPoint Pt = IntPoint(e->Curr.X, horzEdge->Curr.Y);
IntersectEdges(e, horzEdge, Pt);
}
TEdge *eNext = GetNextInAEL(e, dir);
SwapPositionsInAEL(horzEdge, e);
e = eNext;
} // end while(e)
// Break out of loop if HorzEdge.NextInLML is not also horizontal ...
if (!horzEdge->NextInLML || !IsHorizontal(*horzEdge->NextInLML))
break;
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->OutIdx >= 0)
AddOutPt(horzEdge, horzEdge->Bot);
GetHorzDirection(*horzEdge, dir, horzLeft, horzRight);
} // end for (;;)
if (horzEdge->OutIdx >= 0 && !op1) {
op1 = GetLastOutPt(horzEdge);
TEdge *eNextHorz = m_SortedEdges;
while (eNextHorz) {
if (eNextHorz->OutIdx >= 0 &&
HorzSegmentsOverlap(horzEdge->Bot.X, horzEdge->Top.X,
eNextHorz->Bot.X, eNextHorz->Top.X)) {
OutPt *op2 = GetLastOutPt(eNextHorz);
AddJoin(op2, op1, eNextHorz->Top);
}
eNextHorz = eNextHorz->NextInSEL;
}
AddGhostJoin(op1, horzEdge->Top);
}
if (horzEdge->NextInLML) {
if (horzEdge->OutIdx >= 0) {
op1 = AddOutPt(horzEdge, horzEdge->Top);
UpdateEdgeIntoAEL(horzEdge);
if (horzEdge->WindDelta == 0)
return;
// nb: HorzEdge is no longer horizontal here
TEdge *ePrev = horzEdge->PrevInAEL;
TEdge *eNext = horzEdge->NextInAEL;
if (ePrev && ePrev->Curr.X == horzEdge->Bot.X &&
ePrev->Curr.Y == horzEdge->Bot.Y && ePrev->WindDelta != 0 &&
(ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
SlopesEqual(*horzEdge, *ePrev, m_UseFullRange))) {
OutPt *op2 = AddOutPt(ePrev, horzEdge->Bot);
AddJoin(op1, op2, horzEdge->Top);
} else if (eNext && eNext->Curr.X == horzEdge->Bot.X &&
eNext->Curr.Y == horzEdge->Bot.Y && eNext->WindDelta != 0 &&
eNext->OutIdx >= 0 && eNext->Curr.Y > eNext->Top.Y &&
SlopesEqual(*horzEdge, *eNext, m_UseFullRange)) {
OutPt *op2 = AddOutPt(eNext, horzEdge->Bot);
AddJoin(op1, op2, horzEdge->Top);
}
} else
UpdateEdgeIntoAEL(horzEdge);
} else {
if (horzEdge->OutIdx >= 0)
AddOutPt(horzEdge, horzEdge->Top);
DeleteFromAEL(horzEdge);
}
}
//------------------------------------------------------------------------------
bool Clipper::ProcessIntersections(const cInt topY) {
if (!m_ActiveEdges)
return true;
try {
BuildIntersectList(topY);
size_t IlSize = m_IntersectList.size();
if (IlSize == 0)
return true;
if (IlSize == 1 || FixupIntersectionOrder())
ProcessIntersectList();
else
return false;
} catch (...) {
m_SortedEdges = 0;
DisposeIntersectNodes();
throw clipperException("ProcessIntersections error");
}
m_SortedEdges = 0;
return true;
}
//------------------------------------------------------------------------------
void Clipper::DisposeIntersectNodes() {
for (size_t i = 0; i < m_IntersectList.size(); ++i)
delete m_IntersectList[i];
m_IntersectList.clear();
}
//------------------------------------------------------------------------------
void Clipper::BuildIntersectList(const cInt topY) {
if (!m_ActiveEdges)
return;
// prepare for sorting ...
TEdge *e = m_ActiveEdges;
m_SortedEdges = e;
while (e) {
e->PrevInSEL = e->PrevInAEL;
e->NextInSEL = e->NextInAEL;
e->Curr.X = TopX(*e, topY);
e = e->NextInAEL;
}
// bubblesort ...
bool isModified;
do {
isModified = false;
e = m_SortedEdges;
while (e->NextInSEL) {
TEdge *eNext = e->NextInSEL;
IntPoint Pt;
if (e->Curr.X > eNext->Curr.X) {
IntersectPoint(*e, *eNext, Pt);
if (Pt.Y < topY)
Pt = IntPoint(TopX(*e, topY), topY);
IntersectNode *newNode = new IntersectNode;
newNode->Edge1 = e;
newNode->Edge2 = eNext;
newNode->Pt = Pt;
m_IntersectList.push_back(newNode);
SwapPositionsInSEL(e, eNext);
isModified = true;
} else
e = eNext;
}
if (e->PrevInSEL)
e->PrevInSEL->NextInSEL = 0;
else
break;
} while (isModified);
m_SortedEdges = 0; // important
}
//------------------------------------------------------------------------------
void Clipper::ProcessIntersectList() {
for (size_t i = 0; i < m_IntersectList.size(); ++i) {
IntersectNode *iNode = m_IntersectList[i];
{
IntersectEdges(iNode->Edge1, iNode->Edge2, iNode->Pt);
SwapPositionsInAEL(iNode->Edge1, iNode->Edge2);
}
delete iNode;
}
m_IntersectList.clear();
}
//------------------------------------------------------------------------------
bool IntersectListSort(IntersectNode *node1, IntersectNode *node2) {
return node2->Pt.Y < node1->Pt.Y;
}
//------------------------------------------------------------------------------
inline bool EdgesAdjacent(const IntersectNode &inode) {
return (inode.Edge1->NextInSEL == inode.Edge2) ||
(inode.Edge1->PrevInSEL == inode.Edge2);
}
//------------------------------------------------------------------------------
bool Clipper::FixupIntersectionOrder() {
// pre-condition: intersections are sorted Bottom-most first.
// Now it's crucial that intersections are made only between adjacent edges,
// so to ensure this the order of intersections may need adjusting ...
CopyAELToSEL();
std::sort(m_IntersectList.begin(), m_IntersectList.end(), IntersectListSort);
size_t cnt = m_IntersectList.size();
for (size_t i = 0; i < cnt; ++i) {
if (!EdgesAdjacent(*m_IntersectList[i])) {
size_t j = i + 1;
while (j < cnt && !EdgesAdjacent(*m_IntersectList[j]))
j++;
if (j == cnt)
return false;
std::swap(m_IntersectList[i], m_IntersectList[j]);
}
SwapPositionsInSEL(m_IntersectList[i]->Edge1, m_IntersectList[i]->Edge2);
}
return true;
}
//------------------------------------------------------------------------------
void Clipper::DoMaxima(TEdge *e) {
TEdge *eMaxPair = GetMaximaPairEx(e);
if (!eMaxPair) {
if (e->OutIdx >= 0)
AddOutPt(e, e->Top);
DeleteFromAEL(e);
return;
}
TEdge *eNext = e->NextInAEL;
while (eNext && eNext != eMaxPair) {
IntersectEdges(e, eNext, e->Top);
SwapPositionsInAEL(e, eNext);
eNext = e->NextInAEL;
}
if (e->OutIdx == Unassigned && eMaxPair->OutIdx == Unassigned) {
DeleteFromAEL(e);
DeleteFromAEL(eMaxPair);
} else if (e->OutIdx >= 0 && eMaxPair->OutIdx >= 0) {
if (e->OutIdx >= 0)
AddLocalMaxPoly(e, eMaxPair, e->Top);
DeleteFromAEL(e);
DeleteFromAEL(eMaxPair);
}
#ifdef use_lines
else if (e->WindDelta == 0) {
if (e->OutIdx >= 0) {
AddOutPt(e, e->Top);
e->OutIdx = Unassigned;
}
DeleteFromAEL(e);
if (eMaxPair->OutIdx >= 0) {
AddOutPt(eMaxPair, e->Top);
eMaxPair->OutIdx = Unassigned;
}
DeleteFromAEL(eMaxPair);
}
#endif
else
throw clipperException("DoMaxima error");
}
//------------------------------------------------------------------------------
void Clipper::ProcessEdgesAtTopOfScanbeam(const cInt topY) {
TEdge *e = m_ActiveEdges;
while (e) {
// 1. process maxima, treating them as if they're 'bent' horizontal edges,
// but exclude maxima with horizontal edges. nb: e can't be a horizontal.
bool IsMaximaEdge = IsMaxima(e, topY);
if (IsMaximaEdge) {
TEdge *eMaxPair = GetMaximaPairEx(e);
IsMaximaEdge = (!eMaxPair || !IsHorizontal(*eMaxPair));
}
if (IsMaximaEdge) {
if (m_StrictSimple)
m_Maxima.push_back(e->Top.X);
TEdge *ePrev = e->PrevInAEL;
DoMaxima(e);
if (!ePrev)
e = m_ActiveEdges;
else
e = ePrev->NextInAEL;
} else {
// 2. promote horizontal edges, otherwise update Curr.X and Curr.Y ...
if (IsIntermediate(e, topY) && IsHorizontal(*e->NextInLML)) {
UpdateEdgeIntoAEL(e);
if (e->OutIdx >= 0)
AddOutPt(e, e->Bot);
AddEdgeToSEL(e);
} else {
e->Curr.X = TopX(*e, topY);
e->Curr.Y = topY;
#ifdef use_xyz
e->Curr.Z =
topY == e->Top.Y ? e->Top.Z : (topY == e->Bot.Y ? e->Bot.Z : 0);
#endif
}
// When StrictlySimple and 'e' is being touched by another edge, then
// make sure both edges have a vertex here ...
if (m_StrictSimple) {
TEdge *ePrev = e->PrevInAEL;
if ((e->OutIdx >= 0) && (e->WindDelta != 0) && ePrev &&
(ePrev->OutIdx >= 0) && (ePrev->Curr.X == e->Curr.X) &&
(ePrev->WindDelta != 0)) {
IntPoint pt = e->Curr;
#ifdef use_xyz
SetZ(pt, *ePrev, *e);
#endif
OutPt *op = AddOutPt(ePrev, pt);
OutPt *op2 = AddOutPt(e, pt);
AddJoin(op, op2, pt); // StrictlySimple (type-3) join
}
}
e = e->NextInAEL;
}
}
// 3. Process horizontals at the Top of the scanbeam ...
m_Maxima.sort();
ProcessHorizontals();
m_Maxima.clear();
// 4. Promote intermediate vertices ...
e = m_ActiveEdges;
while (e) {
if (IsIntermediate(e, topY)) {
OutPt *op = 0;
if (e->OutIdx >= 0)
op = AddOutPt(e, e->Top);
UpdateEdgeIntoAEL(e);
// if output polygons share an edge, they'll need joining later ...
TEdge *ePrev = e->PrevInAEL;
TEdge *eNext = e->NextInAEL;
if (ePrev && ePrev->Curr.X == e->Bot.X && ePrev->Curr.Y == e->Bot.Y &&
op && ePrev->OutIdx >= 0 && ePrev->Curr.Y > ePrev->Top.Y &&
SlopesEqual(e->Curr, e->Top, ePrev->Curr, ePrev->Top,
m_UseFullRange) &&
(e->WindDelta != 0) && (ePrev->WindDelta != 0)) {
OutPt *op2 = AddOutPt(ePrev, e->Bot);
AddJoin(op, op2, e->Top);
} else if (eNext && eNext->Curr.X == e->Bot.X &&
eNext->Curr.Y == e->Bot.Y && op && eNext->OutIdx >= 0 &&
eNext->Curr.Y > eNext->Top.Y &&
SlopesEqual(e->Curr, e->Top, eNext->Curr, eNext->Top,
m_UseFullRange) &&
(e->WindDelta != 0) && (eNext->WindDelta != 0)) {
OutPt *op2 = AddOutPt(eNext, e->Bot);
AddJoin(op, op2, e->Top);
}
}
e = e->NextInAEL;
}
}
//------------------------------------------------------------------------------
void Clipper::FixupOutPolyline(OutRec &outrec) {
OutPt *pp = outrec.Pts;
OutPt *lastPP = pp->Prev;
while (pp != lastPP) {
pp = pp->Next;
if (pp->Pt == pp->Prev->Pt) {
if (pp == lastPP)
lastPP = pp->Prev;
OutPt *tmpPP = pp->Prev;
tmpPP->Next = pp->Next;
pp->Next->Prev = tmpPP;
delete pp;
pp = tmpPP;
}
}
if (pp == pp->Prev) {
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
}
//------------------------------------------------------------------------------
void Clipper::FixupOutPolygon(OutRec &outrec) {
// FixupOutPolygon() - removes duplicate points and simplifies consecutive
// parallel edges by removing the middle vertex.
OutPt *lastOK = 0;
outrec.BottomPt = 0;
OutPt *pp = outrec.Pts;
bool preserveCol = m_PreserveCollinear || m_StrictSimple;
for (;;) {
if (pp->Prev == pp || pp->Prev == pp->Next) {
DisposeOutPts(pp);
outrec.Pts = 0;
return;
}
// test for duplicate points and collinear edges ...
if ((pp->Pt == pp->Next->Pt) || (pp->Pt == pp->Prev->Pt) ||
(SlopesEqual(pp->Prev->Pt, pp->Pt, pp->Next->Pt, m_UseFullRange) &&
(!preserveCol ||
!Pt2IsBetweenPt1AndPt3(pp->Prev->Pt, pp->Pt, pp->Next->Pt)))) {
lastOK = 0;
OutPt *tmp = pp;
pp->Prev->Next = pp->Next;
pp->Next->Prev = pp->Prev;
pp = pp->Prev;
delete tmp;
} else if (pp == lastOK)
break;
else {
if (!lastOK)
lastOK = pp;
pp = pp->Next;
}
}
outrec.Pts = pp;
}
//------------------------------------------------------------------------------
int PointCount(OutPt *Pts) {
if (!Pts)
return 0;
int result = 0;
OutPt *p = Pts;
do {
result++;
p = p->Next;
} while (p != Pts);
return result;
}
//------------------------------------------------------------------------------
void Clipper::BuildResult(Paths &polys) {
polys.reserve(m_PolyOuts.size());
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
if (!m_PolyOuts[i]->Pts)
continue;
Path pg;
OutPt *p = m_PolyOuts[i]->Pts->Prev;
int cnt = PointCount(p);
if (cnt < 2)
continue;
pg.reserve(cnt);
for (int i = 0; i < cnt; ++i) {
pg.push_back(p->Pt);
p = p->Prev;
}
polys.push_back(pg);
}
}
//------------------------------------------------------------------------------
void Clipper::BuildResult2(PolyTree &polytree) {
polytree.Clear();
polytree.AllNodes.reserve(m_PolyOuts.size());
// add each output polygon/contour to polytree ...
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
OutRec *outRec = m_PolyOuts[i];
int cnt = PointCount(outRec->Pts);
if ((outRec->IsOpen && cnt < 2) || (!outRec->IsOpen && cnt < 3))
continue;
FixHoleLinkage(*outRec);
PolyNode *pn = new PolyNode();
// nb: polytree takes ownership of all the PolyNodes
polytree.AllNodes.push_back(pn);
outRec->PolyNd = pn;
pn->Parent = 0;
pn->Index = 0;
pn->Contour.reserve(cnt);
OutPt *op = outRec->Pts->Prev;
for (int j = 0; j < cnt; j++) {
pn->Contour.push_back(op->Pt);
op = op->Prev;
}
}
// fixup PolyNode links etc ...
polytree.Childs.reserve(m_PolyOuts.size());
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); i++) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->PolyNd)
continue;
if (outRec->IsOpen) {
outRec->PolyNd->m_IsOpen = true;
polytree.AddChild(*outRec->PolyNd);
} else if (outRec->FirstLeft && outRec->FirstLeft->PolyNd)
outRec->FirstLeft->PolyNd->AddChild(*outRec->PolyNd);
else
polytree.AddChild(*outRec->PolyNd);
}
}
//------------------------------------------------------------------------------
void SwapIntersectNodes(IntersectNode &int1, IntersectNode &int2) {
// just swap the contents (because fIntersectNodes is a single-linked-list)
IntersectNode inode = int1; // gets a copy of Int1
int1.Edge1 = int2.Edge1;
int1.Edge2 = int2.Edge2;
int1.Pt = int2.Pt;
int2.Edge1 = inode.Edge1;
int2.Edge2 = inode.Edge2;
int2.Pt = inode.Pt;
}
//------------------------------------------------------------------------------
inline bool E2InsertsBeforeE1(TEdge &e1, TEdge &e2) {
if (e2.Curr.X == e1.Curr.X) {
if (e2.Top.Y > e1.Top.Y)
return e2.Top.X < TopX(e1, e2.Top.Y);
else
return e1.Top.X > TopX(e2, e1.Top.Y);
} else
return e2.Curr.X < e1.Curr.X;
}
//------------------------------------------------------------------------------
bool GetOverlap(const cInt a1, const cInt a2, const cInt b1, const cInt b2,
cInt &Left, cInt &Right) {
if (a1 < a2) {
if (b1 < b2) {
Left = std::max(a1, b1);
Right = std::min(a2, b2);
} else {
Left = std::max(a1, b2);
Right = std::min(a2, b1);
}
} else {
if (b1 < b2) {
Left = std::max(a2, b1);
Right = std::min(a1, b2);
} else {
Left = std::max(a2, b2);
Right = std::min(a1, b1);
}
}
return Left < Right;
}
//------------------------------------------------------------------------------
inline void UpdateOutPtIdxs(OutRec &outrec) {
OutPt *op = outrec.Pts;
do {
op->Idx = outrec.Idx;
op = op->Prev;
} while (op != outrec.Pts);
}
//------------------------------------------------------------------------------
void Clipper::InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge) {
if (!m_ActiveEdges) {
edge->PrevInAEL = 0;
edge->NextInAEL = 0;
m_ActiveEdges = edge;
} else if (!startEdge && E2InsertsBeforeE1(*m_ActiveEdges, *edge)) {
edge->PrevInAEL = 0;
edge->NextInAEL = m_ActiveEdges;
m_ActiveEdges->PrevInAEL = edge;
m_ActiveEdges = edge;
} else {
if (!startEdge)
startEdge = m_ActiveEdges;
while (startEdge->NextInAEL &&
!E2InsertsBeforeE1(*startEdge->NextInAEL, *edge))
startEdge = startEdge->NextInAEL;
edge->NextInAEL = startEdge->NextInAEL;
if (startEdge->NextInAEL)
startEdge->NextInAEL->PrevInAEL = edge;
edge->PrevInAEL = startEdge;
startEdge->NextInAEL = edge;
}
}
//----------------------------------------------------------------------
OutPt *DupOutPt(OutPt *outPt, bool InsertAfter) {
OutPt *result = new OutPt;
result->Pt = outPt->Pt;
result->Idx = outPt->Idx;
if (InsertAfter) {
result->Next = outPt->Next;
result->Prev = outPt;
outPt->Next->Prev = result;
outPt->Next = result;
} else {
result->Prev = outPt->Prev;
result->Next = outPt;
outPt->Prev->Next = result;
outPt->Prev = result;
}
return result;
}
//------------------------------------------------------------------------------
bool JoinHorz(OutPt *op1, OutPt *op1b, OutPt *op2, OutPt *op2b,
const IntPoint Pt, bool DiscardLeft) {
Direction Dir1 = (op1->Pt.X > op1b->Pt.X ? dRightToLeft : dLeftToRight);
Direction Dir2 = (op2->Pt.X > op2b->Pt.X ? dRightToLeft : dLeftToRight);
if (Dir1 == Dir2)
return false;
// When DiscardLeft, we want Op1b to be on the Left of Op1, otherwise we
// want Op1b to be on the Right. (And likewise with Op2 and Op2b.)
// So, to facilitate this while inserting Op1b and Op2b ...
// when DiscardLeft, make sure we're AT or RIGHT of Pt before adding Op1b,
// otherwise make sure we're AT or LEFT of Pt. (Likewise with Op2b.)
if (Dir1 == dLeftToRight) {
while (op1->Next->Pt.X <= Pt.X && op1->Next->Pt.X >= op1->Pt.X &&
op1->Next->Pt.Y == Pt.Y)
op1 = op1->Next;
if (DiscardLeft && (op1->Pt.X != Pt.X))
op1 = op1->Next;
op1b = DupOutPt(op1, !DiscardLeft);
if (op1b->Pt != Pt) {
op1 = op1b;
op1->Pt = Pt;
op1b = DupOutPt(op1, !DiscardLeft);
}
} else {
while (op1->Next->Pt.X >= Pt.X && op1->Next->Pt.X <= op1->Pt.X &&
op1->Next->Pt.Y == Pt.Y)
op1 = op1->Next;
if (!DiscardLeft && (op1->Pt.X != Pt.X))
op1 = op1->Next;
op1b = DupOutPt(op1, DiscardLeft);
if (op1b->Pt != Pt) {
op1 = op1b;
op1->Pt = Pt;
op1b = DupOutPt(op1, DiscardLeft);
}
}
if (Dir2 == dLeftToRight) {
while (op2->Next->Pt.X <= Pt.X && op2->Next->Pt.X >= op2->Pt.X &&
op2->Next->Pt.Y == Pt.Y)
op2 = op2->Next;
if (DiscardLeft && (op2->Pt.X != Pt.X))
op2 = op2->Next;
op2b = DupOutPt(op2, !DiscardLeft);
if (op2b->Pt != Pt) {
op2 = op2b;
op2->Pt = Pt;
op2b = DupOutPt(op2, !DiscardLeft);
};
} else {
while (op2->Next->Pt.X >= Pt.X && op2->Next->Pt.X <= op2->Pt.X &&
op2->Next->Pt.Y == Pt.Y)
op2 = op2->Next;
if (!DiscardLeft && (op2->Pt.X != Pt.X))
op2 = op2->Next;
op2b = DupOutPt(op2, DiscardLeft);
if (op2b->Pt != Pt) {
op2 = op2b;
op2->Pt = Pt;
op2b = DupOutPt(op2, DiscardLeft);
};
};
if ((Dir1 == dLeftToRight) == DiscardLeft) {
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
} else {
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
}
return true;
}
//------------------------------------------------------------------------------
bool Clipper::JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2) {
OutPt *op1 = j->OutPt1, *op1b;
OutPt *op2 = j->OutPt2, *op2b;
// There are 3 kinds of joins for output polygons ...
// 1. Horizontal joins where Join.OutPt1 & Join.OutPt2 are vertices anywhere
// along (horizontal) collinear edges (& Join.OffPt is on the same
// horizontal).
// 2. Non-horizontal joins where Join.OutPt1 & Join.OutPt2 are at the same
// location at the Bottom of the overlapping segment (& Join.OffPt is above).
// 3. StrictSimple joins where edges touch but are not collinear and where
// Join.OutPt1, Join.OutPt2 & Join.OffPt all share the same point.
bool isHorizontal = (j->OutPt1->Pt.Y == j->OffPt.Y);
if (isHorizontal && (j->OffPt == j->OutPt1->Pt) &&
(j->OffPt == j->OutPt2->Pt)) {
// Strictly Simple join ...
if (outRec1 != outRec2)
return false;
op1b = j->OutPt1->Next;
while (op1b != op1 && (op1b->Pt == j->OffPt))
op1b = op1b->Next;
bool reverse1 = (op1b->Pt.Y > j->OffPt.Y);
op2b = j->OutPt2->Next;
while (op2b != op2 && (op2b->Pt == j->OffPt))
op2b = op2b->Next;
bool reverse2 = (op2b->Pt.Y > j->OffPt.Y);
if (reverse1 == reverse2)
return false;
if (reverse1) {
op1b = DupOutPt(op1, false);
op2b = DupOutPt(op2, true);
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
} else {
op1b = DupOutPt(op1, true);
op2b = DupOutPt(op2, false);
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
}
} else if (isHorizontal) {
// treat horizontal joins differently to non-horizontal joins since with
// them we're not yet sure where the overlapping is. OutPt1.Pt & OutPt2.Pt
// may be anywhere along the horizontal edge.
op1b = op1;
while (op1->Prev->Pt.Y == op1->Pt.Y && op1->Prev != op1b &&
op1->Prev != op2)
op1 = op1->Prev;
while (op1b->Next->Pt.Y == op1b->Pt.Y && op1b->Next != op1 &&
op1b->Next != op2)
op1b = op1b->Next;
if (op1b->Next == op1 || op1b->Next == op2)
return false; // a flat 'polygon'
op2b = op2;
while (op2->Prev->Pt.Y == op2->Pt.Y && op2->Prev != op2b &&
op2->Prev != op1b)
op2 = op2->Prev;
while (op2b->Next->Pt.Y == op2b->Pt.Y && op2b->Next != op2 &&
op2b->Next != op1)
op2b = op2b->Next;
if (op2b->Next == op2 || op2b->Next == op1)
return false; // a flat 'polygon'
cInt Left, Right;
// Op1 --> Op1b & Op2 --> Op2b are the extremites of the horizontal edges
if (!GetOverlap(op1->Pt.X, op1b->Pt.X, op2->Pt.X, op2b->Pt.X, Left, Right))
return false;
// DiscardLeftSide: when overlapping edges are joined, a spike will created
// which needs to be cleaned up. However, we don't want Op1 or Op2 caught up
// on the discard Side as either may still be needed for other joins ...
IntPoint Pt;
bool DiscardLeftSide;
if (op1->Pt.X >= Left && op1->Pt.X <= Right) {
Pt = op1->Pt;
DiscardLeftSide = (op1->Pt.X > op1b->Pt.X);
} else if (op2->Pt.X >= Left && op2->Pt.X <= Right) {
Pt = op2->Pt;
DiscardLeftSide = (op2->Pt.X > op2b->Pt.X);
} else if (op1b->Pt.X >= Left && op1b->Pt.X <= Right) {
Pt = op1b->Pt;
DiscardLeftSide = op1b->Pt.X > op1->Pt.X;
} else {
Pt = op2b->Pt;
DiscardLeftSide = (op2b->Pt.X > op2->Pt.X);
}
j->OutPt1 = op1;
j->OutPt2 = op2;
return JoinHorz(op1, op1b, op2, op2b, Pt, DiscardLeftSide);
} else {
// nb: For non-horizontal joins ...
// 1. Jr.OutPt1.Pt.Y == Jr.OutPt2.Pt.Y
// 2. Jr.OutPt1.Pt > Jr.OffPt.Y
// make sure the polygons are correctly oriented ...
op1b = op1->Next;
while ((op1b->Pt == op1->Pt) && (op1b != op1))
op1b = op1b->Next;
bool Reverse1 = ((op1b->Pt.Y > op1->Pt.Y) ||
!SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange));
if (Reverse1) {
op1b = op1->Prev;
while ((op1b->Pt == op1->Pt) && (op1b != op1))
op1b = op1b->Prev;
if ((op1b->Pt.Y > op1->Pt.Y) ||
!SlopesEqual(op1->Pt, op1b->Pt, j->OffPt, m_UseFullRange))
return false;
};
op2b = op2->Next;
while ((op2b->Pt == op2->Pt) && (op2b != op2))
op2b = op2b->Next;
bool Reverse2 = ((op2b->Pt.Y > op2->Pt.Y) ||
!SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange));
if (Reverse2) {
op2b = op2->Prev;
while ((op2b->Pt == op2->Pt) && (op2b != op2))
op2b = op2b->Prev;
if ((op2b->Pt.Y > op2->Pt.Y) ||
!SlopesEqual(op2->Pt, op2b->Pt, j->OffPt, m_UseFullRange))
return false;
}
if ((op1b == op1) || (op2b == op2) || (op1b == op2b) ||
((outRec1 == outRec2) && (Reverse1 == Reverse2)))
return false;
if (Reverse1) {
op1b = DupOutPt(op1, false);
op2b = DupOutPt(op2, true);
op1->Prev = op2;
op2->Next = op1;
op1b->Next = op2b;
op2b->Prev = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
} else {
op1b = DupOutPt(op1, true);
op2b = DupOutPt(op2, false);
op1->Next = op2;
op2->Prev = op1;
op1b->Prev = op2b;
op2b->Next = op1b;
j->OutPt1 = op1;
j->OutPt2 = op1b;
return true;
}
}
}
//----------------------------------------------------------------------
static OutRec *ParseFirstLeft(OutRec *FirstLeft) {
while (FirstLeft && !FirstLeft->Pts)
FirstLeft = FirstLeft->FirstLeft;
return FirstLeft;
}
//------------------------------------------------------------------------------
void Clipper::FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec) {
// tests if NewOutRec contains the polygon before reassigning FirstLeft
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (outRec->Pts && firstLeft == OldOutRec) {
if (Poly2ContainsPoly1(outRec->Pts, NewOutRec->Pts))
outRec->FirstLeft = NewOutRec;
}
}
}
//----------------------------------------------------------------------
void Clipper::FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec) {
// A polygon has split into two such that one is now the inner of the other.
// It's possible that these polygons now wrap around other polygons, so check
// every polygon that's also contained by OuterOutRec's FirstLeft container
//(including 0) to see if they've become inner to the new inner polygon ...
OutRec *orfl = OuterOutRec->FirstLeft;
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
if (!outRec->Pts || outRec == OuterOutRec || outRec == InnerOutRec)
continue;
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (firstLeft != orfl && firstLeft != InnerOutRec &&
firstLeft != OuterOutRec)
continue;
if (Poly2ContainsPoly1(outRec->Pts, InnerOutRec->Pts))
outRec->FirstLeft = InnerOutRec;
else if (Poly2ContainsPoly1(outRec->Pts, OuterOutRec->Pts))
outRec->FirstLeft = OuterOutRec;
else if (outRec->FirstLeft == InnerOutRec ||
outRec->FirstLeft == OuterOutRec)
outRec->FirstLeft = orfl;
}
}
//----------------------------------------------------------------------
void Clipper::FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec) {
// reassigns FirstLeft WITHOUT testing if NewOutRec contains the polygon
for (PolyOutList::size_type i = 0; i < m_PolyOuts.size(); ++i) {
OutRec *outRec = m_PolyOuts[i];
OutRec *firstLeft = ParseFirstLeft(outRec->FirstLeft);
if (outRec->Pts && firstLeft == OldOutRec)
outRec->FirstLeft = NewOutRec;
}
}
//----------------------------------------------------------------------
void Clipper::JoinCommonEdges() {
for (JoinList::size_type i = 0; i < m_Joins.size(); i++) {
Join *join = m_Joins[i];
OutRec *outRec1 = GetOutRec(join->OutPt1->Idx);
OutRec *outRec2 = GetOutRec(join->OutPt2->Idx);
if (!outRec1->Pts || !outRec2->Pts)
continue;
if (outRec1->IsOpen || outRec2->IsOpen)
continue;
// get the polygon fragment with the correct hole state (FirstLeft)
// before calling JoinPoints() ...
OutRec *holeStateRec;
if (outRec1 == outRec2)
holeStateRec = outRec1;
else if (OutRec1RightOfOutRec2(outRec1, outRec2))
holeStateRec = outRec2;
else if (OutRec1RightOfOutRec2(outRec2, outRec1))
holeStateRec = outRec1;
else
holeStateRec = GetLowermostRec(outRec1, outRec2);
if (!JoinPoints(join, outRec1, outRec2))
continue;
if (outRec1 == outRec2) {
// instead of joining two polygons, we've just created a new one by
// splitting one polygon into two.
outRec1->Pts = join->OutPt1;
outRec1->BottomPt = 0;
outRec2 = CreateOutRec();
outRec2->Pts = join->OutPt2;
// update all OutRec2.Pts Idx's ...
UpdateOutPtIdxs(*outRec2);
if (Poly2ContainsPoly1(outRec2->Pts, outRec1->Pts)) {
// outRec1 contains outRec2 ...
outRec2->IsHole = !outRec1->IsHole;
outRec2->FirstLeft = outRec1;
if (m_UsingPolyTree)
FixupFirstLefts2(outRec2, outRec1);
if ((outRec2->IsHole ^ m_ReverseOutput) == (Area(*outRec2) > 0))
ReversePolyPtLinks(outRec2->Pts);
} else if (Poly2ContainsPoly1(outRec1->Pts, outRec2->Pts)) {
// outRec2 contains outRec1 ...
outRec2->IsHole = outRec1->IsHole;
outRec1->IsHole = !outRec2->IsHole;
outRec2->FirstLeft = outRec1->FirstLeft;
outRec1->FirstLeft = outRec2;
if (m_UsingPolyTree)
FixupFirstLefts2(outRec1, outRec2);
if ((outRec1->IsHole ^ m_ReverseOutput) == (Area(*outRec1) > 0))
ReversePolyPtLinks(outRec1->Pts);
} else {
// the 2 polygons are completely separate ...
outRec2->IsHole = outRec1->IsHole;
outRec2->FirstLeft = outRec1->FirstLeft;
// fixup FirstLeft pointers that may need reassigning to OutRec2
if (m_UsingPolyTree)
FixupFirstLefts1(outRec1, outRec2);
}
} else {
// joined 2 polygons together ...
outRec2->Pts = 0;
outRec2->BottomPt = 0;
outRec2->Idx = outRec1->Idx;
outRec1->IsHole = holeStateRec->IsHole;
if (holeStateRec == outRec2)
outRec1->FirstLeft = outRec2->FirstLeft;
outRec2->FirstLeft = outRec1;
if (m_UsingPolyTree)
FixupFirstLefts3(outRec2, outRec1);
}
}
}
//------------------------------------------------------------------------------
// ClipperOffset support functions ...
//------------------------------------------------------------------------------
DoublePoint GetUnitNormal(const IntPoint &pt1, const IntPoint &pt2) {
if (pt2.X == pt1.X && pt2.Y == pt1.Y)
return DoublePoint(0, 0);
double Dx = (double)(pt2.X - pt1.X);
double dy = (double)(pt2.Y - pt1.Y);
double f = 1 * 1.0 / std::sqrt(Dx * Dx + dy * dy);
Dx *= f;
dy *= f;
return DoublePoint(dy, -Dx);
}
//------------------------------------------------------------------------------
// ClipperOffset class
//------------------------------------------------------------------------------
ClipperOffset::ClipperOffset(double miterLimit, double arcTolerance) {
this->MiterLimit = miterLimit;
this->ArcTolerance = arcTolerance;
m_lowest.X = -1;
}
//------------------------------------------------------------------------------
ClipperOffset::~ClipperOffset() { Clear(); }
//------------------------------------------------------------------------------
void ClipperOffset::Clear() {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i)
delete m_polyNodes.Childs[i];
m_polyNodes.Childs.clear();
m_lowest.X = -1;
}
//------------------------------------------------------------------------------
void ClipperOffset::AddPath(const Path &path, JoinType joinType,
EndType endType) {
int highI = (int)path.size() - 1;
if (highI < 0)
return;
PolyNode *newNode = new PolyNode();
newNode->m_jointype = joinType;
newNode->m_endtype = endType;
// strip duplicate points from path and also get index to the lowest point ...
if (endType == etClosedLine || endType == etClosedPolygon)
while (highI > 0 && path[0] == path[highI])
highI--;
newNode->Contour.reserve(highI + 1);
newNode->Contour.push_back(path[0]);
int j = 0, k = 0;
for (int i = 1; i <= highI; i++)
if (newNode->Contour[j] != path[i]) {
j++;
newNode->Contour.push_back(path[i]);
if (path[i].Y > newNode->Contour[k].Y ||
(path[i].Y == newNode->Contour[k].Y &&
path[i].X < newNode->Contour[k].X))
k = j;
}
if (endType == etClosedPolygon && j < 2) {
delete newNode;
return;
}
m_polyNodes.AddChild(*newNode);
// if this path's lowest pt is lower than all the others then update m_lowest
if (endType != etClosedPolygon)
return;
if (m_lowest.X < 0)
m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
else {
IntPoint ip = m_polyNodes.Childs[(int)m_lowest.X]->Contour[(int)m_lowest.Y];
if (newNode->Contour[k].Y > ip.Y ||
(newNode->Contour[k].Y == ip.Y && newNode->Contour[k].X < ip.X))
m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k);
}
}
//------------------------------------------------------------------------------
void ClipperOffset::AddPaths(const Paths &paths, JoinType joinType,
EndType endType) {
for (Paths::size_type i = 0; i < paths.size(); ++i)
AddPath(paths[i], joinType, endType);
}
//------------------------------------------------------------------------------
void ClipperOffset::FixOrientations() {
// fixup orientations of all closed paths if the orientation of the
// closed path with the lowermost vertex is wrong ...
if (m_lowest.X >= 0 &&
!Orientation(m_polyNodes.Childs[(int)m_lowest.X]->Contour)) {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedPolygon ||
(node.m_endtype == etClosedLine && Orientation(node.Contour)))
ReversePath(node.Contour);
}
} else {
for (int i = 0; i < m_polyNodes.ChildCount(); ++i) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedLine && !Orientation(node.Contour))
ReversePath(node.Contour);
}
}
}
//------------------------------------------------------------------------------
void ClipperOffset::Execute(Paths &solution, double delta) {
solution.clear();
FixOrientations();
DoOffset(delta);
// now clean up 'corners' ...
Clipper clpr;
clpr.AddPaths(m_destPolys, ptSubject, true);
if (delta > 0) {
clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
} else {
IntRect r = clpr.GetBounds();
Path outer(4);
outer[0] = IntPoint(r.left - 10, r.bottom + 10);
outer[1] = IntPoint(r.right + 10, r.bottom + 10);
outer[2] = IntPoint(r.right + 10, r.top - 10);
outer[3] = IntPoint(r.left - 10, r.top - 10);
clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
if (solution.size() > 0)
solution.erase(solution.begin());
}
}
//------------------------------------------------------------------------------
void ClipperOffset::Execute(PolyTree &solution, double delta) {
solution.Clear();
FixOrientations();
DoOffset(delta);
// now clean up 'corners' ...
Clipper clpr;
clpr.AddPaths(m_destPolys, ptSubject, true);
if (delta > 0) {
clpr.Execute(ctUnion, solution, pftPositive, pftPositive);
} else {
IntRect r = clpr.GetBounds();
Path outer(4);
outer[0] = IntPoint(r.left - 10, r.bottom + 10);
outer[1] = IntPoint(r.right + 10, r.bottom + 10);
outer[2] = IntPoint(r.right + 10, r.top - 10);
outer[3] = IntPoint(r.left - 10, r.top - 10);
clpr.AddPath(outer, ptSubject, true);
clpr.ReverseSolution(true);
clpr.Execute(ctUnion, solution, pftNegative, pftNegative);
// remove the outer PolyNode rectangle ...
if (solution.ChildCount() == 1 && solution.Childs[0]->ChildCount() > 0) {
PolyNode *outerNode = solution.Childs[0];
solution.Childs.reserve(outerNode->ChildCount());
solution.Childs[0] = outerNode->Childs[0];
solution.Childs[0]->Parent = outerNode->Parent;
for (int i = 1; i < outerNode->ChildCount(); ++i)
solution.AddChild(*outerNode->Childs[i]);
} else
solution.Clear();
}
}
//------------------------------------------------------------------------------
void ClipperOffset::DoOffset(double delta) {
m_destPolys.clear();
m_delta = delta;
// if Zero offset, just copy any CLOSED polygons to m_p and return ...
if (NEAR_ZERO(delta)) {
m_destPolys.reserve(m_polyNodes.ChildCount());
for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
PolyNode &node = *m_polyNodes.Childs[i];
if (node.m_endtype == etClosedPolygon)
m_destPolys.push_back(node.Contour);
}
return;
}
// see offset_triginometry3.svg in the documentation folder ...
if (MiterLimit > 2)
m_miterLim = 2 / (MiterLimit * MiterLimit);
else
m_miterLim = 0.5;
double y;
if (ArcTolerance <= 0.0)
y = def_arc_tolerance;
else if (ArcTolerance > std::fabs(delta) * def_arc_tolerance)
y = std::fabs(delta) * def_arc_tolerance;
else
y = ArcTolerance;
// see offset_triginometry2.svg in the documentation folder ...
double steps = pi / std::acos(1 - y / std::fabs(delta));
if (steps > std::fabs(delta) * pi)
steps = std::fabs(delta) * pi; // ie excessive precision check
m_sin = std::sin(two_pi / steps);
m_cos = std::cos(two_pi / steps);
m_StepsPerRad = steps / two_pi;
if (delta < 0.0)
m_sin = -m_sin;
m_destPolys.reserve(m_polyNodes.ChildCount() * 2);
for (int i = 0; i < m_polyNodes.ChildCount(); i++) {
PolyNode &node = *m_polyNodes.Childs[i];
m_srcPoly = node.Contour;
int len = (int)m_srcPoly.size();
if (len == 0 ||
(delta <= 0 && (len < 3 || node.m_endtype != etClosedPolygon)))
continue;
m_destPoly.clear();
if (len == 1) {
if (node.m_jointype == jtRound) {
double X = 1.0, Y = 0.0;
for (cInt j = 1; j <= steps; j++) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
Round(m_srcPoly[0].Y + Y * delta)));
double X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
}
} else {
double X = -1.0, Y = -1.0;
for (int j = 0; j < 4; ++j) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[0].X + X * delta),
Round(m_srcPoly[0].Y + Y * delta)));
if (X < 0)
X = 1;
else if (Y < 0)
Y = 1;
else
X = -1;
}
}
m_destPolys.push_back(m_destPoly);
continue;
}
// build m_normals ...
m_normals.clear();
m_normals.reserve(len);
for (int j = 0; j < len - 1; ++j)
m_normals.push_back(GetUnitNormal(m_srcPoly[j], m_srcPoly[j + 1]));
if (node.m_endtype == etClosedLine || node.m_endtype == etClosedPolygon)
m_normals.push_back(GetUnitNormal(m_srcPoly[len - 1], m_srcPoly[0]));
else
m_normals.push_back(DoublePoint(m_normals[len - 2]));
if (node.m_endtype == etClosedPolygon) {
int k = len - 1;
for (int j = 0; j < len; ++j)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
} else if (node.m_endtype == etClosedLine) {
int k = len - 1;
for (int j = 0; j < len; ++j)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
m_destPoly.clear();
// re-build m_normals ...
DoublePoint n = m_normals[len - 1];
for (int j = len - 1; j > 0; j--)
m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
m_normals[0] = DoublePoint(-n.X, -n.Y);
k = 0;
for (int j = len - 1; j >= 0; j--)
OffsetPoint(j, k, node.m_jointype);
m_destPolys.push_back(m_destPoly);
} else {
int k = 0;
for (int j = 1; j < len - 1; ++j)
OffsetPoint(j, k, node.m_jointype);
IntPoint pt1;
if (node.m_endtype == etOpenButt) {
int j = len - 1;
pt1 = IntPoint((cInt)Round(m_srcPoly[j].X + m_normals[j].X * delta),
(cInt)Round(m_srcPoly[j].Y + m_normals[j].Y * delta));
m_destPoly.push_back(pt1);
pt1 = IntPoint((cInt)Round(m_srcPoly[j].X - m_normals[j].X * delta),
(cInt)Round(m_srcPoly[j].Y - m_normals[j].Y * delta));
m_destPoly.push_back(pt1);
} else {
int j = len - 1;
k = len - 2;
m_sinA = 0;
m_normals[j] = DoublePoint(-m_normals[j].X, -m_normals[j].Y);
if (node.m_endtype == etOpenSquare)
DoSquare(j, k);
else
DoRound(j, k);
}
// re-build m_normals ...
for (int j = len - 1; j > 0; j--)
m_normals[j] = DoublePoint(-m_normals[j - 1].X, -m_normals[j - 1].Y);
m_normals[0] = DoublePoint(-m_normals[1].X, -m_normals[1].Y);
k = len - 1;
for (int j = k - 1; j > 0; --j)
OffsetPoint(j, k, node.m_jointype);
if (node.m_endtype == etOpenButt) {
pt1 = IntPoint((cInt)Round(m_srcPoly[0].X - m_normals[0].X * delta),
(cInt)Round(m_srcPoly[0].Y - m_normals[0].Y * delta));
m_destPoly.push_back(pt1);
pt1 = IntPoint((cInt)Round(m_srcPoly[0].X + m_normals[0].X * delta),
(cInt)Round(m_srcPoly[0].Y + m_normals[0].Y * delta));
m_destPoly.push_back(pt1);
} else {
k = 1;
m_sinA = 0;
if (node.m_endtype == etOpenSquare)
DoSquare(0, 1);
else
DoRound(0, 1);
}
m_destPolys.push_back(m_destPoly);
}
}
}
//------------------------------------------------------------------------------
void ClipperOffset::OffsetPoint(int j, int &k, JoinType jointype) {
// cross product ...
m_sinA = (m_normals[k].X * m_normals[j].Y - m_normals[j].X * m_normals[k].Y);
if (std::fabs(m_sinA * m_delta) < 1.0) {
// dot product ...
double cosA =
(m_normals[k].X * m_normals[j].X + m_normals[j].Y * m_normals[k].Y);
if (cosA > 0) // angle => 0 degrees
{
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
return;
}
// else angle => 180 degrees
} else if (m_sinA > 1.0)
m_sinA = 1.0;
else if (m_sinA < -1.0)
m_sinA = -1.0;
if (m_sinA * m_delta < 0) {
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[k].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[k].Y * m_delta)));
m_destPoly.push_back(m_srcPoly[j]);
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
} else
switch (jointype) {
case jtMiter: {
double r = 1 + (m_normals[j].X * m_normals[k].X +
m_normals[j].Y * m_normals[k].Y);
if (r >= m_miterLim)
DoMiter(j, k, r);
else
DoSquare(j, k);
break;
}
case jtSquare:
DoSquare(j, k);
break;
case jtRound:
DoRound(j, k);
break;
}
k = j;
}
//------------------------------------------------------------------------------
void ClipperOffset::DoSquare(int j, int k) {
double dx = std::tan(std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
m_normals[k].Y * m_normals[j].Y) /
4);
m_destPoly.push_back(IntPoint(
Round(m_srcPoly[j].X + m_delta * (m_normals[k].X - m_normals[k].Y * dx)),
Round(m_srcPoly[j].Y +
m_delta * (m_normals[k].Y + m_normals[k].X * dx))));
m_destPoly.push_back(IntPoint(
Round(m_srcPoly[j].X + m_delta * (m_normals[j].X + m_normals[j].Y * dx)),
Round(m_srcPoly[j].Y +
m_delta * (m_normals[j].Y - m_normals[j].X * dx))));
}
//------------------------------------------------------------------------------
void ClipperOffset::DoMiter(int j, int k, double r) {
double q = m_delta / r;
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + (m_normals[k].X + m_normals[j].X) * q),
Round(m_srcPoly[j].Y + (m_normals[k].Y + m_normals[j].Y) * q)));
}
//------------------------------------------------------------------------------
void ClipperOffset::DoRound(int j, int k) {
double a = std::atan2(m_sinA, m_normals[k].X * m_normals[j].X +
m_normals[k].Y * m_normals[j].Y);
int steps = std::max((int)Round(m_StepsPerRad * std::fabs(a)), 1);
double X = m_normals[k].X, Y = m_normals[k].Y, X2;
for (int i = 0; i < steps; ++i) {
m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].X + X * m_delta),
Round(m_srcPoly[j].Y + Y * m_delta)));
X2 = X;
X = X * m_cos - m_sin * Y;
Y = X2 * m_sin + Y * m_cos;
}
m_destPoly.push_back(
IntPoint(Round(m_srcPoly[j].X + m_normals[j].X * m_delta),
Round(m_srcPoly[j].Y + m_normals[j].Y * m_delta)));
}
//------------------------------------------------------------------------------
// Miscellaneous public functions
//------------------------------------------------------------------------------
void Clipper::DoSimplePolygons() {
PolyOutList::size_type i = 0;
while (i < m_PolyOuts.size()) {
OutRec *outrec = m_PolyOuts[i++];
OutPt *op = outrec->Pts;
if (!op || outrec->IsOpen)
continue;
do // for each Pt in Polygon until duplicate found do ...
{
OutPt *op2 = op->Next;
while (op2 != outrec->Pts) {
if ((op->Pt == op2->Pt) && op2->Next != op && op2->Prev != op) {
// split the polygon into two ...
OutPt *op3 = op->Prev;
OutPt *op4 = op2->Prev;
op->Prev = op4;
op4->Next = op;
op2->Prev = op3;
op3->Next = op2;
outrec->Pts = op;
OutRec *outrec2 = CreateOutRec();
outrec2->Pts = op2;
UpdateOutPtIdxs(*outrec2);
if (Poly2ContainsPoly1(outrec2->Pts, outrec->Pts)) {
// OutRec2 is contained by OutRec1 ...
outrec2->IsHole = !outrec->IsHole;
outrec2->FirstLeft = outrec;
if (m_UsingPolyTree)
FixupFirstLefts2(outrec2, outrec);
} else if (Poly2ContainsPoly1(outrec->Pts, outrec2->Pts)) {
// OutRec1 is contained by OutRec2 ...
outrec2->IsHole = outrec->IsHole;
outrec->IsHole = !outrec2->IsHole;
outrec2->FirstLeft = outrec->FirstLeft;
outrec->FirstLeft = outrec2;
if (m_UsingPolyTree)
FixupFirstLefts2(outrec, outrec2);
} else {
// the 2 polygons are separate ...
outrec2->IsHole = outrec->IsHole;
outrec2->FirstLeft = outrec->FirstLeft;
if (m_UsingPolyTree)
FixupFirstLefts1(outrec, outrec2);
}
op2 = op; // ie get ready for the Next iteration
}
op2 = op2->Next;
}
op = op->Next;
} while (op != outrec->Pts);
}
}
//------------------------------------------------------------------------------
void ReversePath(Path &p) { std::reverse(p.begin(), p.end()); }
//------------------------------------------------------------------------------
void ReversePaths(Paths &p) {
for (Paths::size_type i = 0; i < p.size(); ++i)
ReversePath(p[i]);
}
//------------------------------------------------------------------------------
void SimplifyPolygon(const Path &in_poly, Paths &out_polys,
PolyFillType fillType) {
Clipper c;
c.StrictlySimple(true);
c.AddPath(in_poly, ptSubject, true);
c.Execute(ctUnion, out_polys, fillType, fillType);
}
//------------------------------------------------------------------------------
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys,
PolyFillType fillType) {
Clipper c;
c.StrictlySimple(true);
c.AddPaths(in_polys, ptSubject, true);
c.Execute(ctUnion, out_polys, fillType, fillType);
}
//------------------------------------------------------------------------------
void SimplifyPolygons(Paths &polys, PolyFillType fillType) {
SimplifyPolygons(polys, polys, fillType);
}
//------------------------------------------------------------------------------
inline double DistanceSqrd(const IntPoint &pt1, const IntPoint &pt2) {
double Dx = ((double)pt1.X - pt2.X);
double dy = ((double)pt1.Y - pt2.Y);
return (Dx * Dx + dy * dy);
}
//------------------------------------------------------------------------------
double DistanceFromLineSqrd(const IntPoint &pt, const IntPoint &ln1,
const IntPoint &ln2) {
// The equation of a line in general form (Ax + By + C = 0)
// given 2 points (x�,y�) & (x�,y�) is ...
//(y� - y�)x + (x� - x�)y + (y� - y�)x� - (x� - x�)y� = 0
// A = (y� - y�); B = (x� - x�); C = (y� - y�)x� - (x� - x�)y�
// perpendicular distance of point (x�,y�) = (Ax� + By� + C)/Sqrt(A� + B�)
// see http://en.wikipedia.org/wiki/Perpendicular_distance
double A = double(ln1.Y - ln2.Y);
double B = double(ln2.X - ln1.X);
double C = A * ln1.X + B * ln1.Y;
C = A * pt.X + B * pt.Y - C;
return (C * C) / (A * A + B * B);
}
//---------------------------------------------------------------------------
bool SlopesNearCollinear(const IntPoint &pt1, const IntPoint &pt2,
const IntPoint &pt3, double distSqrd) {
// this function is more accurate when the point that's geometrically
// between the other 2 points is the one that's tested for distance.
// ie makes it more likely to pick up 'spikes' ...
if (Abs(pt1.X - pt2.X) > Abs(pt1.Y - pt2.Y)) {
if ((pt1.X > pt2.X) == (pt1.X < pt3.X))
return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
else if ((pt2.X > pt1.X) == (pt2.X < pt3.X))
return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
else
return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
} else {
if ((pt1.Y > pt2.Y) == (pt1.Y < pt3.Y))
return DistanceFromLineSqrd(pt1, pt2, pt3) < distSqrd;
else if ((pt2.Y > pt1.Y) == (pt2.Y < pt3.Y))
return DistanceFromLineSqrd(pt2, pt1, pt3) < distSqrd;
else
return DistanceFromLineSqrd(pt3, pt1, pt2) < distSqrd;
}
}
//------------------------------------------------------------------------------
bool PointsAreClose(IntPoint pt1, IntPoint pt2, double distSqrd) {
double Dx = (double)pt1.X - pt2.X;
double dy = (double)pt1.Y - pt2.Y;
return ((Dx * Dx) + (dy * dy) <= distSqrd);
}
//------------------------------------------------------------------------------
OutPt *ExcludeOp(OutPt *op) {
OutPt *result = op->Prev;
result->Next = op->Next;
op->Next->Prev = result;
result->Idx = 0;
return result;
}
//------------------------------------------------------------------------------
void CleanPolygon(const Path &in_poly, Path &out_poly, double distance) {
// distance = proximity in units/pixels below which vertices
// will be stripped. Default ~= sqrt(2).
size_t size = in_poly.size();
if (size == 0) {
out_poly.clear();
return;
}
OutPt *outPts = new OutPt[size];
for (size_t i = 0; i < size; ++i) {
outPts[i].Pt = in_poly[i];
outPts[i].Next = &outPts[(i + 1) % size];
outPts[i].Next->Prev = &outPts[i];
outPts[i].Idx = 0;
}
double distSqrd = distance * distance;
OutPt *op = &outPts[0];
while (op->Idx == 0 && op->Next != op->Prev) {
if (PointsAreClose(op->Pt, op->Prev->Pt, distSqrd)) {
op = ExcludeOp(op);
size--;
} else if (PointsAreClose(op->Prev->Pt, op->Next->Pt, distSqrd)) {
ExcludeOp(op->Next);
op = ExcludeOp(op);
size -= 2;
} else if (SlopesNearCollinear(op->Prev->Pt, op->Pt, op->Next->Pt,
distSqrd)) {
op = ExcludeOp(op);
size--;
} else {
op->Idx = 1;
op = op->Next;
}
}
if (size < 3)
size = 0;
out_poly.resize(size);
for (size_t i = 0; i < size; ++i) {
out_poly[i] = op->Pt;
op = op->Next;
}
delete[] outPts;
}
//------------------------------------------------------------------------------
void CleanPolygon(Path &poly, double distance) {
CleanPolygon(poly, poly, distance);
}
//------------------------------------------------------------------------------
void CleanPolygons(const Paths &in_polys, Paths &out_polys, double distance) {
out_polys.resize(in_polys.size());
for (Paths::size_type i = 0; i < in_polys.size(); ++i)
CleanPolygon(in_polys[i], out_polys[i], distance);
}
//------------------------------------------------------------------------------
void CleanPolygons(Paths &polys, double distance) {
CleanPolygons(polys, polys, distance);
}
//------------------------------------------------------------------------------
void Minkowski(const Path &poly, const Path &path, Paths &solution, bool isSum,
bool isClosed) {
int delta = (isClosed ? 1 : 0);
size_t polyCnt = poly.size();
size_t pathCnt = path.size();
Paths pp;
pp.reserve(pathCnt);
if (isSum)
for (size_t i = 0; i < pathCnt; ++i) {
Path p;
p.reserve(polyCnt);
for (size_t j = 0; j < poly.size(); ++j)
p.push_back(IntPoint(path[i].X + poly[j].X, path[i].Y + poly[j].Y));
pp.push_back(p);
}
else
for (size_t i = 0; i < pathCnt; ++i) {
Path p;
p.reserve(polyCnt);
for (size_t j = 0; j < poly.size(); ++j)
p.push_back(IntPoint(path[i].X - poly[j].X, path[i].Y - poly[j].Y));
pp.push_back(p);
}
solution.clear();
solution.reserve((pathCnt + delta) * (polyCnt + 1));
for (size_t i = 0; i < pathCnt - 1 + delta; ++i)
for (size_t j = 0; j < polyCnt; ++j) {
Path quad;
quad.reserve(4);
quad.push_back(pp[i % pathCnt][j % polyCnt]);
quad.push_back(pp[(i + 1) % pathCnt][j % polyCnt]);
quad.push_back(pp[(i + 1) % pathCnt][(j + 1) % polyCnt]);
quad.push_back(pp[i % pathCnt][(j + 1) % polyCnt]);
if (!Orientation(quad))
ReversePath(quad);
solution.push_back(quad);
}
}
//------------------------------------------------------------------------------
void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution,
bool pathIsClosed) {
Minkowski(pattern, path, solution, true, pathIsClosed);
Clipper c;
c.AddPaths(solution, ptSubject, true);
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
void TranslatePath(const Path &input, Path &output, const IntPoint delta) {
// precondition: input != output
output.resize(input.size());
for (size_t i = 0; i < input.size(); ++i)
output[i] = IntPoint(input[i].X + delta.X, input[i].Y + delta.Y);
}
//------------------------------------------------------------------------------
void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution,
bool pathIsClosed) {
Clipper c;
for (size_t i = 0; i < paths.size(); ++i) {
Paths tmp;
Minkowski(pattern, paths[i], tmp, true, pathIsClosed);
c.AddPaths(tmp, ptSubject, true);
if (pathIsClosed) {
Path tmp2;
TranslatePath(paths[i], tmp2, pattern[0]);
c.AddPath(tmp2, ptClip, true);
}
}
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution) {
Minkowski(poly1, poly2, solution, false, true);
Clipper c;
c.AddPaths(solution, ptSubject, true);
c.Execute(ctUnion, solution, pftNonZero, pftNonZero);
}
//------------------------------------------------------------------------------
enum NodeType { ntAny, ntOpen, ntClosed };
void AddPolyNodeToPaths(const PolyNode &polynode, NodeType nodetype,
Paths &paths) {
bool match = true;
if (nodetype == ntClosed)
match = !polynode.IsOpen();
else if (nodetype == ntOpen)
return;
if (!polynode.Contour.empty() && match)
paths.push_back(polynode.Contour);
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
}
//------------------------------------------------------------------------------
void PolyTreeToPaths(const PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths);
}
//------------------------------------------------------------------------------
void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths);
}
//------------------------------------------------------------------------------
void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths) {
paths.resize(0);
paths.reserve(polytree.Total());
// Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i)
if (polytree.Childs[i]->IsOpen())
paths.push_back(polytree.Childs[i]->Contour);
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const IntPoint &p) {
s << "(" << p.X << "," << p.Y << ")";
return s;
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const Path &p) {
if (p.empty())
return s;
Path::size_type last = p.size() - 1;
for (Path::size_type i = 0; i < last; i++)
s << "(" << p[i].X << "," << p[i].Y << "), ";
s << "(" << p[last].X << "," << p[last].Y << ")\n";
return s;
}
//------------------------------------------------------------------------------
std::ostream &operator<<(std::ostream &s, const Paths &p) {
for (Paths::size_type i = 0; i < p.size(); i++)
s << p[i];
s << "\n";
return s;
}
//------------------------------------------------------------------------------
} // ClipperLib namespace
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "glog/logging.h"
#include "omp.h"
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <chrono>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <numeric>
#include <glog/logging.h>
// #include <include/config.h>
#include <include/ocr_det.h>
#include <include/ocr_rec.h>
// #include <include/utility.h>
#include <sys/stat.h>
#include <gflags/gflags.h>
DEFINE_bool(use_gpu, false, "Infering with GPU or CPU.");
DEFINE_int32(gpu_id, 0, "Device id of GPU to execute.");
DEFINE_int32(gpu_mem, 4000, "GPU id when infering with GPU.");
DEFINE_int32(cpu_math_library_num_threads, 10, "Num of threads with CPU.");
DEFINE_bool(use_mkldnn, false, "Whether use mkldnn with CPU.");
DEFINE_string(image_dir, "", "Dir of input image.");
DEFINE_string(det_model_dir, "", "Path of det inference model.");
DEFINE_int32(max_side_len, 960, "max_side_len of input image.");
DEFINE_double(det_db_thresh, 0.3, "Threshold of det_db_thresh.");
DEFINE_double(det_db_box_thresh, 0.5, "Threshold of det_db_box_thresh.");
DEFINE_double(det_db_unclip_ratio, 1.6, "Threshold of det_db_unclip_ratio.");
DEFINE_bool(use_polygon_score, false, "Whether use polygon score.");
DEFINE_bool(visualize, true, "Whether show the detection results.");
DEFINE_bool(use_angle_cls, false, "Whether use use_angle_cls.");
DEFINE_string(cls_model_dir, "", "Path of cls inference model.");
DEFINE_double(cls_thresh, 0.9, "Threshold of cls_thresh.");
DEFINE_string(rec_model_dir, "", "Path of rec inference model.");
DEFINE_string(char_list_file, "../../ppocr/utils/ppocr_keys_v1.txt", "Path of dictionary.");
DEFINE_bool(use_tensorrt, false, "Whether use tensorrt.");
DEFINE_bool(use_fp16, false, "Whether use fp16 when use tensorrt.");
using namespace std;
using namespace cv;
using namespace PaddleOCR;
static bool PathExists(const std::string& path){
#ifdef _WIN32
struct _stat buffer;
return (_stat(path.c_str(), &buffer) == 0);
#else
struct stat buffer;
return (stat(path.c_str(), &buffer) == 0);
#endif // !_WIN32
}
cv::Mat GetRotateCropImage(const cv::Mat &srcimage,
std::vector<std::vector<int>> box) {
cv::Mat image;
srcimage.copyTo(image);
std::vector<std::vector<int>> points = box;
int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]};
int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]};
int left = int(*std::min_element(x_collect, x_collect + 4));
int right = int(*std::max_element(x_collect, x_collect + 4));
int top = int(*std::min_element(y_collect, y_collect + 4));
int bottom = int(*std::max_element(y_collect, y_collect + 4));
cv::Mat img_crop;
image(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop);
for (int i = 0; i < points.size(); i++) {
points[i][0] -= left;
points[i][1] -= top;
}
int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) +
pow(points[0][1] - points[1][1], 2)));
int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) +
pow(points[0][1] - points[3][1], 2)));
cv::Point2f pts_std[4];
pts_std[0] = cv::Point2f(0., 0.);
pts_std[1] = cv::Point2f(img_crop_width, 0.);
pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
pts_std[3] = cv::Point2f(0.f, img_crop_height);
cv::Point2f pointsf[4];
pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
pointsf[3] = cv::Point2f(points[3][0], points[3][1]);
cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);
cv::Mat dst_img;
cv::warpPerspective(img_crop, dst_img, M,
cv::Size(img_crop_width, img_crop_height),
cv::BORDER_REPLICATE);
if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) {
cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
cv::transpose(dst_img, srcCopy);
cv::flip(srcCopy, srcCopy, 0);
return srcCopy;
} else {
return dst_img;
}
}
int main(int argc, char **argv) {
// Parsing command-line
google::ParseCommandLineFlags(&argc, &argv, true);
if ((FLAGS_det_model_dir.empty() || FLAGS_rec_model_dir.empty() || FLAGS_image_dir.empty()) ||
(FLAGS_use_angle_cls && FLAGS_cls_model_dir.empty())) {
std::cout << "Usage[default]: ./ocr_system --det_model_dir=/PATH/TO/DET_INFERENCE_MODEL/ "
<< "--rec_model_dir=/PATH/TO/REC_INFERENCE_MODEL/ "
<< "--image_dir=/PATH/TO/INPUT/IMAGE/" << std::endl;
std::cout << "Usage[use angle cls]: ./ocr_system --det_model_dir=/PATH/TO/DET_INFERENCE_MODEL/ "
<< "--use_angle_cls=true "
<< "--cls_model_dir=/PATH/TO/CLS_INFERENCE_MODEL/ "
<< "--rec_model_dir=/PATH/TO/REC_INFERENCE_MODEL/ "
<< "--image_dir=/PATH/TO/INPUT/IMAGE/" << std::endl;
return -1;
}
if (!PathExists(FLAGS_image_dir)) {
std::cerr << "[ERROR] image path not exist! image_dir: " << FLAGS_image_dir << endl;
exit(1);
}
std::vector<cv::String> cv_all_img_names;
cv::glob(FLAGS_image_dir, cv_all_img_names);
std::cout << "total images num: " << cv_all_img_names.size() << endl;
DBDetector det(FLAGS_det_model_dir, FLAGS_use_gpu, FLAGS_gpu_id,
FLAGS_gpu_mem, FLAGS_cpu_math_library_num_threads,
FLAGS_use_mkldnn, FLAGS_max_side_len, FLAGS_det_db_thresh,
FLAGS_det_db_box_thresh, FLAGS_det_db_unclip_ratio,
FLAGS_use_polygon_score, FLAGS_visualize,
FLAGS_use_tensorrt, FLAGS_use_fp16);
Classifier *cls = nullptr;
if (FLAGS_use_angle_cls) {
cls = new Classifier(FLAGS_cls_model_dir, FLAGS_use_gpu, FLAGS_gpu_id,
FLAGS_gpu_mem, FLAGS_cpu_math_library_num_threads,
FLAGS_use_mkldnn, FLAGS_cls_thresh,
FLAGS_use_tensorrt, FLAGS_use_fp16);
}
CRNNRecognizer rec(FLAGS_rec_model_dir, FLAGS_use_gpu, FLAGS_gpu_id,
FLAGS_gpu_mem, FLAGS_cpu_math_library_num_threads,
FLAGS_use_mkldnn, FLAGS_char_list_file,
FLAGS_use_tensorrt, FLAGS_use_fp16);
auto start = std::chrono::system_clock::now();
for (int i = 0; i < cv_all_img_names.size(); ++i) {
LOG(INFO) << "The predict img: " << cv_all_img_names[i];
cv::Mat srcimg = cv::imread(FLAGS_image_dir, cv::IMREAD_COLOR);
if (!srcimg.data) {
std::cerr << "[ERROR] image read failed! image path: " << cv_all_img_names[i] << endl;
exit(1);
}
std::vector<std::vector<std::vector<int>>> boxes;
det.Run(srcimg, boxes);
cv::Mat crop_img;
for (int j = 0; j < boxes.size(); j++) {
crop_img = GetRotateCropImage(srcimg, boxes[j]);
if (cls != nullptr) {
crop_img = cls->Run(crop_img);
}
rec.Run(crop_img);
}
auto end = std::chrono::system_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "Cost "
<< double(duration.count()) *
std::chrono::microseconds::period::num /
std::chrono::microseconds::period::den
<< "s" << std::endl;
}
return 0;
}
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <include/ocr_det.h>
namespace PaddleOCR {
void DBDetector::LoadModel(const std::string &model_dir) {
// AnalysisConfig config;
paddle_infer::Config config;
config.SetModel(model_dir + "/inference.pdmodel",
model_dir + "/inference.pdiparams");
if (this->use_gpu_) {
config.EnableUseGpu(this->gpu_mem_, this->gpu_id_);
if (this->use_tensorrt_) {
config.EnableTensorRtEngine(
1 << 20, 10, 3,
this->use_fp16_ ? paddle_infer::Config::Precision::kHalf
: paddle_infer::Config::Precision::kFloat32,
false, false);
std::map<std::string, std::vector<int>> min_input_shape = {
{"x", {1, 3, 50, 50}},
{"conv2d_92.tmp_0", {1, 96, 20, 20}},
{"conv2d_91.tmp_0", {1, 96, 10, 10}},
{"nearest_interp_v2_1.tmp_0", {1, 96, 10, 10}},
{"nearest_interp_v2_2.tmp_0", {1, 96, 20, 20}},
{"nearest_interp_v2_3.tmp_0", {1, 24, 20, 20}},
{"nearest_interp_v2_4.tmp_0", {1, 24, 20, 20}},
{"nearest_interp_v2_5.tmp_0", {1, 24, 20, 20}},
{"elementwise_add_7", {1, 56, 2, 2}},
{"nearest_interp_v2_0.tmp_0", {1, 96, 2, 2}}};
std::map<std::string, std::vector<int>> max_input_shape = {
{"x", {1, 3, this->max_side_len_, this->max_side_len_}},
{"conv2d_92.tmp_0", {1, 96, 400, 400}},
{"conv2d_91.tmp_0", {1, 96, 200, 200}},
{"nearest_interp_v2_1.tmp_0", {1, 96, 200, 200}},
{"nearest_interp_v2_2.tmp_0", {1, 96, 400, 400}},
{"nearest_interp_v2_3.tmp_0", {1, 24, 400, 400}},
{"nearest_interp_v2_4.tmp_0", {1, 24, 400, 400}},
{"nearest_interp_v2_5.tmp_0", {1, 24, 400, 400}},
{"elementwise_add_7", {1, 56, 400, 400}},
{"nearest_interp_v2_0.tmp_0", {1, 96, 400, 400}}};
std::map<std::string, std::vector<int>> opt_input_shape = {
{"x", {1, 3, 640, 640}},
{"conv2d_92.tmp_0", {1, 96, 160, 160}},
{"conv2d_91.tmp_0", {1, 96, 80, 80}},
{"nearest_interp_v2_1.tmp_0", {1, 96, 80, 80}},
{"nearest_interp_v2_2.tmp_0", {1, 96, 160, 160}},
{"nearest_interp_v2_3.tmp_0", {1, 24, 160, 160}},
{"nearest_interp_v2_4.tmp_0", {1, 24, 160, 160}},
{"nearest_interp_v2_5.tmp_0", {1, 24, 160, 160}},
{"elementwise_add_7", {1, 56, 40, 40}},
{"nearest_interp_v2_0.tmp_0", {1, 96, 40, 40}}};
config.SetTRTDynamicShapeInfo(min_input_shape, max_input_shape,
opt_input_shape);
}
} else {
config.DisableGpu();
if (this->use_mkldnn_) {
config.EnableMKLDNN();
// cache 10 different shapes for mkldnn to avoid memory leak
config.SetMkldnnCacheCapacity(10);
}
config.SetCpuMathLibraryNumThreads(this->cpu_math_library_num_threads_);
}
// use zero_copy_run as default
config.SwitchUseFeedFetchOps(false);
// true for multiple input
config.SwitchSpecifyInputNames(true);
config.SwitchIrOptim(true);
config.EnableMemoryOptim();
// config.DisableGlogInfo();
this->predictor_ = CreatePredictor(config);
}
void DBDetector::Run(cv::Mat &img,
std::vector<std::vector<std::vector<int>>> &boxes) {
float ratio_h{};
float ratio_w{};
cv::Mat srcimg;
cv::Mat resize_img;
img.copyTo(srcimg);
this->resize_op_.Run(img, resize_img, this->max_side_len_, ratio_h, ratio_w,
this->use_tensorrt_);
this->normalize_op_.Run(&resize_img, this->mean_, this->scale_,
this->is_scale_);
std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);
this->permute_op_.Run(&resize_img, input.data());
// Inference.
auto input_names = this->predictor_->GetInputNames();
auto input_t = this->predictor_->GetInputHandle(input_names[0]);
input_t->Reshape({1, 3, resize_img.rows, resize_img.cols});
input_t->CopyFromCpu(input.data());
this->predictor_->Run();
std::vector<float> out_data;
auto output_names = this->predictor_->GetOutputNames();
auto output_t = this->predictor_->GetOutputHandle(output_names[0]);
std::vector<int> output_shape = output_t->shape();
int out_num = std::accumulate(output_shape.begin(), output_shape.end(), 1,
std::multiplies<int>());
out_data.resize(out_num);
output_t->CopyToCpu(out_data.data());
int n2 = output_shape[2];
int n3 = output_shape[3];
int n = n2 * n3;
std::vector<float> pred(n, 0.0);
std::vector<unsigned char> cbuf(n, ' ');
for (int i = 0; i < n; i++) {
pred[i] = float(out_data[i]);
cbuf[i] = (unsigned char)((out_data[i]) * 255);
}
cv::Mat cbuf_map(n2, n3, CV_8UC1, (unsigned char *)cbuf.data());
cv::Mat pred_map(n2, n3, CV_32F, (float *)pred.data());
const double threshold = this->det_db_thresh_ * 255;
const double maxvalue = 255;
cv::Mat bit_map;
cv::threshold(cbuf_map, bit_map, threshold, maxvalue, cv::THRESH_BINARY);
cv::Mat dilation_map;
cv::Mat dila_ele = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(2, 2));
cv::dilate(bit_map, dilation_map, dila_ele);
boxes = post_processor_.BoxesFromBitmap(
pred_map, dilation_map, this->det_db_box_thresh_,
this->det_db_unclip_ratio_, this->use_polygon_score_);
boxes = post_processor_.FilterTagDetRes(boxes, ratio_h, ratio_w, srcimg);
std::cout << "Detected boxes num: " << boxes.size() << endl;
//// visualization
if (this->visualize_) {
Utility::VisualizeBboxes(srcimg, boxes);
}
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <include/ocr_rec.h>
namespace PaddleOCR {
void CRNNRecognizer::Run(cv::Mat &img) {
cv::Mat srcimg;
img.copyTo(srcimg);
cv::Mat resize_img;
float wh_ratio = float(srcimg.cols) / float(srcimg.rows);
this->resize_op_.Run(srcimg, resize_img, wh_ratio, this->use_tensorrt_);
this->normalize_op_.Run(&resize_img, this->mean_, this->scale_,
this->is_scale_);
std::vector<float> input(1 * 3 * resize_img.rows * resize_img.cols, 0.0f);
this->permute_op_.Run(&resize_img, input.data());
// Inference.
auto input_names = this->predictor_->GetInputNames();
auto input_t = this->predictor_->GetInputHandle(input_names[0]);
input_t->Reshape({1, 3, resize_img.rows, resize_img.cols});
input_t->CopyFromCpu(input.data());
this->predictor_->Run();
std::vector<float> predict_batch;
auto output_names = this->predictor_->GetOutputNames();
auto output_t = this->predictor_->GetOutputHandle(output_names[0]);
auto predict_shape = output_t->shape();
int out_num = std::accumulate(predict_shape.begin(), predict_shape.end(), 1,
std::multiplies<int>());
predict_batch.resize(out_num);
output_t->CopyToCpu(predict_batch.data());
// ctc decode
std::vector<std::string> str_res;
int argmax_idx;
int last_index = 0;
float score = 0.f;
int count = 0;
float max_value = 0.0f;
for (int n = 0; n < predict_shape[1]; n++) {
argmax_idx =
int(Utility::argmax(&predict_batch[n * predict_shape[2]],
&predict_batch[(n + 1) * predict_shape[2]]));
max_value =
float(*std::max_element(&predict_batch[n * predict_shape[2]],
&predict_batch[(n + 1) * predict_shape[2]]));
if (argmax_idx > 0 && (!(n > 0 && argmax_idx == last_index))) {
score += max_value;
count += 1;
str_res.push_back(label_list_[argmax_idx]);
}
last_index = argmax_idx;
}
score /= count;
for (int i = 0; i < str_res.size(); i++) {
std::cout << str_res[i];
}
std::cout << "\tscore: " << score << std::endl;
}
void CRNNRecognizer::LoadModel(const std::string &model_dir) {
// AnalysisConfig config;
paddle_infer::Config config;
config.SetModel(model_dir + "/inference.pdmodel",
model_dir + "/inference.pdiparams");
if (this->use_gpu_) {
config.EnableUseGpu(this->gpu_mem_, this->gpu_id_);
if (this->use_tensorrt_) {
config.EnableTensorRtEngine(
1 << 20, 10, 3,
this->use_fp16_ ? paddle_infer::Config::Precision::kHalf
: paddle_infer::Config::Precision::kFloat32,
false, false);
std::map<std::string, std::vector<int>> min_input_shape = {
{"x", {1, 3, 32, 10}}};
std::map<std::string, std::vector<int>> max_input_shape = {
{"x", {1, 3, 32, 2000}}};
std::map<std::string, std::vector<int>> opt_input_shape = {
{"x", {1, 3, 32, 320}}};
config.SetTRTDynamicShapeInfo(min_input_shape, max_input_shape,
opt_input_shape);
}
} else {
config.DisableGpu();
if (this->use_mkldnn_) {
config.EnableMKLDNN();
// cache 10 different shapes for mkldnn to avoid memory leak
config.SetMkldnnCacheCapacity(10);
}
config.SetCpuMathLibraryNumThreads(this->cpu_math_library_num_threads_);
}
config.SwitchUseFeedFetchOps(false);
// true for multiple input
config.SwitchSpecifyInputNames(true);
config.SwitchIrOptim(true);
config.EnableMemoryOptim();
config.DisableGlogInfo();
this->predictor_ = CreatePredictor(config);
}
cv::Mat CRNNRecognizer::GetRotateCropImage(const cv::Mat &srcimage,
std::vector<std::vector<int>> box) {
cv::Mat image;
srcimage.copyTo(image);
std::vector<std::vector<int>> points = box;
int x_collect[4] = {box[0][0], box[1][0], box[2][0], box[3][0]};
int y_collect[4] = {box[0][1], box[1][1], box[2][1], box[3][1]};
int left = int(*std::min_element(x_collect, x_collect + 4));
int right = int(*std::max_element(x_collect, x_collect + 4));
int top = int(*std::min_element(y_collect, y_collect + 4));
int bottom = int(*std::max_element(y_collect, y_collect + 4));
cv::Mat img_crop;
image(cv::Rect(left, top, right - left, bottom - top)).copyTo(img_crop);
for (int i = 0; i < points.size(); i++) {
points[i][0] -= left;
points[i][1] -= top;
}
int img_crop_width = int(sqrt(pow(points[0][0] - points[1][0], 2) +
pow(points[0][1] - points[1][1], 2)));
int img_crop_height = int(sqrt(pow(points[0][0] - points[3][0], 2) +
pow(points[0][1] - points[3][1], 2)));
cv::Point2f pts_std[4];
pts_std[0] = cv::Point2f(0., 0.);
pts_std[1] = cv::Point2f(img_crop_width, 0.);
pts_std[2] = cv::Point2f(img_crop_width, img_crop_height);
pts_std[3] = cv::Point2f(0.f, img_crop_height);
cv::Point2f pointsf[4];
pointsf[0] = cv::Point2f(points[0][0], points[0][1]);
pointsf[1] = cv::Point2f(points[1][0], points[1][1]);
pointsf[2] = cv::Point2f(points[2][0], points[2][1]);
pointsf[3] = cv::Point2f(points[3][0], points[3][1]);
cv::Mat M = cv::getPerspectiveTransform(pointsf, pts_std);
cv::Mat dst_img;
cv::warpPerspective(img_crop, dst_img, M,
cv::Size(img_crop_width, img_crop_height),
cv::BORDER_REPLICATE);
if (float(dst_img.rows) >= float(dst_img.cols) * 1.5) {
cv::Mat srcCopy = cv::Mat(dst_img.rows, dst_img.cols, dst_img.depth());
cv::transpose(dst_img, srcCopy);
cv::flip(srcCopy, srcCopy, 0);
return srcCopy;
} else {
return dst_img;
}
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <include/postprocess_op.h>
namespace PaddleOCR {
void PostProcessor::GetContourArea(const std::vector<std::vector<float>> &box,
float unclip_ratio, float &distance) {
int pts_num = 4;
float area = 0.0f;
float dist = 0.0f;
for (int i = 0; i < pts_num; i++) {
area += box[i][0] * box[(i + 1) % pts_num][1] -
box[i][1] * box[(i + 1) % pts_num][0];
dist += sqrtf((box[i][0] - box[(i + 1) % pts_num][0]) *
(box[i][0] - box[(i + 1) % pts_num][0]) +
(box[i][1] - box[(i + 1) % pts_num][1]) *
(box[i][1] - box[(i + 1) % pts_num][1]));
}
area = fabs(float(area / 2.0));
distance = area * unclip_ratio / dist;
}
cv::RotatedRect PostProcessor::UnClip(std::vector<std::vector<float>> box,
const float &unclip_ratio) {
float distance = 1.0;
GetContourArea(box, unclip_ratio, distance);
ClipperLib::ClipperOffset offset;
ClipperLib::Path p;
p << ClipperLib::IntPoint(int(box[0][0]), int(box[0][1]))
<< ClipperLib::IntPoint(int(box[1][0]), int(box[1][1]))
<< ClipperLib::IntPoint(int(box[2][0]), int(box[2][1]))
<< ClipperLib::IntPoint(int(box[3][0]), int(box[3][1]));
offset.AddPath(p, ClipperLib::jtRound, ClipperLib::etClosedPolygon);
ClipperLib::Paths soln;
offset.Execute(soln, distance);
std::vector<cv::Point2f> points;
for (int j = 0; j < soln.size(); j++) {
for (int i = 0; i < soln[soln.size() - 1].size(); i++) {
points.emplace_back(soln[j][i].X, soln[j][i].Y);
}
}
cv::RotatedRect res;
if (points.size() <= 0) {
res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
} else {
res = cv::minAreaRect(points);
}
return res;
}
float **PostProcessor::Mat2Vec(cv::Mat mat) {
auto **array = new float *[mat.rows];
for (int i = 0; i < mat.rows; ++i)
array[i] = new float[mat.cols];
for (int i = 0; i < mat.rows; ++i) {
for (int j = 0; j < mat.cols; ++j) {
array[i][j] = mat.at<float>(i, j);
}
}
return array;
}
std::vector<std::vector<int>>
PostProcessor::OrderPointsClockwise(std::vector<std::vector<int>> pts) {
std::vector<std::vector<int>> box = pts;
std::sort(box.begin(), box.end(), XsortInt);
std::vector<std::vector<int>> leftmost = {box[0], box[1]};
std::vector<std::vector<int>> rightmost = {box[2], box[3]};
if (leftmost[0][1] > leftmost[1][1])
std::swap(leftmost[0], leftmost[1]);
if (rightmost[0][1] > rightmost[1][1])
std::swap(rightmost[0], rightmost[1]);
std::vector<std::vector<int>> rect = {leftmost[0], rightmost[0], rightmost[1],
leftmost[1]};
return rect;
}
std::vector<std::vector<float>> PostProcessor::Mat2Vector(cv::Mat mat) {
std::vector<std::vector<float>> img_vec;
std::vector<float> tmp;
for (int i = 0; i < mat.rows; ++i) {
tmp.clear();
for (int j = 0; j < mat.cols; ++j) {
tmp.push_back(mat.at<float>(i, j));
}
img_vec.push_back(tmp);
}
return img_vec;
}
bool PostProcessor::XsortFp32(std::vector<float> a, std::vector<float> b) {
if (a[0] != b[0])
return a[0] < b[0];
return false;
}
bool PostProcessor::XsortInt(std::vector<int> a, std::vector<int> b) {
if (a[0] != b[0])
return a[0] < b[0];
return false;
}
std::vector<std::vector<float>> PostProcessor::GetMiniBoxes(cv::RotatedRect box,
float &ssid) {
ssid = std::max(box.size.width, box.size.height);
cv::Mat points;
cv::boxPoints(box, points);
auto array = Mat2Vector(points);
std::sort(array.begin(), array.end(), XsortFp32);
std::vector<float> idx1 = array[0], idx2 = array[1], idx3 = array[2],
idx4 = array[3];
if (array[3][1] <= array[2][1]) {
idx2 = array[3];
idx3 = array[2];
} else {
idx2 = array[2];
idx3 = array[3];
}
if (array[1][1] <= array[0][1]) {
idx1 = array[1];
idx4 = array[0];
} else {
idx1 = array[0];
idx4 = array[1];
}
array[0] = idx1;
array[1] = idx2;
array[2] = idx3;
array[3] = idx4;
return array;
}
float PostProcessor::PolygonScoreAcc(std::vector<cv::Point> contour,
cv::Mat pred) {
int width = pred.cols;
int height = pred.rows;
std::vector<float> box_x;
std::vector<float> box_y;
for (int i = 0; i < contour.size(); ++i) {
box_x.push_back(contour[i].x);
box_y.push_back(contour[i].y);
}
int xmin =
clamp(int(std::floor(*(std::min_element(box_x.begin(), box_x.end())))), 0,
width - 1);
int xmax =
clamp(int(std::ceil(*(std::max_element(box_x.begin(), box_x.end())))), 0,
width - 1);
int ymin =
clamp(int(std::floor(*(std::min_element(box_y.begin(), box_y.end())))), 0,
height - 1);
int ymax =
clamp(int(std::ceil(*(std::max_element(box_y.begin(), box_y.end())))), 0,
height - 1);
cv::Mat mask;
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
cv::Point* rook_point = new cv::Point[contour.size()];
for (int i = 0; i < contour.size(); ++i) {
rook_point[i] = cv::Point(int(box_x[i]) - xmin, int(box_y[i]) - ymin);
}
const cv::Point *ppt[1] = {rook_point};
int npt[] = {int(contour.size())};
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
cv::Mat croppedImg;
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1)).copyTo(croppedImg);
float score = cv::mean(croppedImg, mask)[0];
delete []rook_point;
return score;
}
float PostProcessor::BoxScoreFast(std::vector<std::vector<float>> box_array,
cv::Mat pred) {
auto array = box_array;
int width = pred.cols;
int height = pred.rows;
float box_x[4] = {array[0][0], array[1][0], array[2][0], array[3][0]};
float box_y[4] = {array[0][1], array[1][1], array[2][1], array[3][1]};
int xmin = clamp(int(std::floor(*(std::min_element(box_x, box_x + 4)))), 0,
width - 1);
int xmax = clamp(int(std::ceil(*(std::max_element(box_x, box_x + 4)))), 0,
width - 1);
int ymin = clamp(int(std::floor(*(std::min_element(box_y, box_y + 4)))), 0,
height - 1);
int ymax = clamp(int(std::ceil(*(std::max_element(box_y, box_y + 4)))), 0,
height - 1);
cv::Mat mask;
mask = cv::Mat::zeros(ymax - ymin + 1, xmax - xmin + 1, CV_8UC1);
cv::Point root_point[4];
root_point[0] = cv::Point(int(array[0][0]) - xmin, int(array[0][1]) - ymin);
root_point[1] = cv::Point(int(array[1][0]) - xmin, int(array[1][1]) - ymin);
root_point[2] = cv::Point(int(array[2][0]) - xmin, int(array[2][1]) - ymin);
root_point[3] = cv::Point(int(array[3][0]) - xmin, int(array[3][1]) - ymin);
const cv::Point *ppt[1] = {root_point};
int npt[] = {4};
cv::fillPoly(mask, ppt, npt, 1, cv::Scalar(1));
cv::Mat croppedImg;
pred(cv::Rect(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1))
.copyTo(croppedImg);
auto score = cv::mean(croppedImg, mask)[0];
return score;
}
std::vector<std::vector<std::vector<int>>> PostProcessor::BoxesFromBitmap(
const cv::Mat pred, const cv::Mat bitmap, const float &box_thresh,
const float &det_db_unclip_ratio, const bool &use_polygon_score) {
const int min_size = 3;
const int max_candidates = 1000;
int width = bitmap.cols;
int height = bitmap.rows;
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(bitmap, contours, hierarchy, cv::RETR_LIST,
cv::CHAIN_APPROX_SIMPLE);
int num_contours =
contours.size() >= max_candidates ? max_candidates : contours.size();
std::vector<std::vector<std::vector<int>>> boxes;
for (int _i = 0; _i < num_contours; _i++) {
if (contours[_i].size() <= 2) {
continue;
}
float ssid;
cv::RotatedRect box = cv::minAreaRect(contours[_i]);
auto array = GetMiniBoxes(box, ssid);
auto box_for_unclip = array;
// end get_mini_box
if (ssid < min_size) {
continue;
}
float score;
if (use_polygon_score)
/* compute using polygon*/
score = PolygonScoreAcc(contours[_i], pred);
else
score = BoxScoreFast(array, pred);
if (score < box_thresh)
continue;
// start for unclip
cv::RotatedRect points = UnClip(box_for_unclip, det_db_unclip_ratio);
if (points.size.height < 1.001 && points.size.width < 1.001) {
continue;
}
// end for unclip
cv::RotatedRect clipbox = points;
auto cliparray = GetMiniBoxes(clipbox, ssid);
if (ssid < min_size + 2)
continue;
int dest_width = pred.cols;
int dest_height = pred.rows;
std::vector<std::vector<int>> intcliparray;
for (int num_pt = 0; num_pt < 4; num_pt++) {
std::vector<int> a{int(clampf(roundf(cliparray[num_pt][0] / float(width) *
float(dest_width)),
0, float(dest_width))),
int(clampf(roundf(cliparray[num_pt][1] /
float(height) * float(dest_height)),
0, float(dest_height)))};
intcliparray.push_back(a);
}
boxes.push_back(intcliparray);
} // end for
return boxes;
}
std::vector<std::vector<std::vector<int>>>
PostProcessor::FilterTagDetRes(std::vector<std::vector<std::vector<int>>> boxes,
float ratio_h, float ratio_w, cv::Mat srcimg) {
int oriimg_h = srcimg.rows;
int oriimg_w = srcimg.cols;
std::vector<std::vector<std::vector<int>>> root_points;
for (int n = 0; n < boxes.size(); n++) {
boxes[n] = OrderPointsClockwise(boxes[n]);
for (int m = 0; m < boxes[0].size(); m++) {
boxes[n][m][0] /= ratio_w;
boxes[n][m][1] /= ratio_h;
boxes[n][m][0] = int(_min(_max(boxes[n][m][0], 0), oriimg_w - 1));
boxes[n][m][1] = int(_min(_max(boxes[n][m][1], 0), oriimg_h - 1));
}
}
for (int n = 0; n < boxes.size(); n++) {
int rect_width, rect_height;
rect_width = int(sqrt(pow(boxes[n][0][0] - boxes[n][1][0], 2) +
pow(boxes[n][0][1] - boxes[n][1][1], 2)));
rect_height = int(sqrt(pow(boxes[n][0][0] - boxes[n][3][0], 2) +
pow(boxes[n][0][1] - boxes[n][3][1], 2)));
if (rect_width <= 4 || rect_height <= 4)
continue;
root_points.push_back(boxes[n]);
}
return root_points;
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "opencv2/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "paddle_api.h"
#include "paddle_inference_api.h"
#include <chrono>
#include <iomanip>
#include <iostream>
#include <ostream>
#include <vector>
#include <cstring>
#include <fstream>
#include <numeric>
#include <include/preprocess_op.h>
namespace PaddleOCR {
void Permute::Run(const cv::Mat *im, float *data) {
int rh = im->rows;
int rw = im->cols;
int rc = im->channels();
for (int i = 0; i < rc; ++i) {
cv::extractChannel(*im, cv::Mat(rh, rw, CV_32FC1, data + i * rh * rw), i);
}
}
void Normalize::Run(cv::Mat *im, const std::vector<float> &mean,
const std::vector<float> &scale, const bool is_scale) {
double e = 1.0;
if (is_scale) {
e /= 255.0;
}
(*im).convertTo(*im, CV_32FC3, e);
std::vector<cv::Mat> bgr_channels(3);
cv::split(*im, bgr_channels);
for (auto i = 0; i < bgr_channels.size(); i++) {
bgr_channels[i].convertTo(bgr_channels[i], CV_32FC1, 1.0 * scale[i],
(0.0 - mean[i]) * scale[i]);
}
cv::merge(bgr_channels, *im);
}
void ResizeImgType0::Run(const cv::Mat &img, cv::Mat &resize_img,
int max_size_len, float &ratio_h, float &ratio_w,
bool use_tensorrt) {
int w = img.cols;
int h = img.rows;
float ratio = 1.f;
int max_wh = w >= h ? w : h;
if (max_wh > max_size_len) {
if (h > w) {
ratio = float(max_size_len) / float(h);
} else {
ratio = float(max_size_len) / float(w);
}
}
int resize_h = int(float(h) * ratio);
int resize_w = int(float(w) * ratio);
resize_h = max(int(round(float(resize_h) / 32) * 32), 32);
resize_w = max(int(round(float(resize_w) / 32) * 32), 32);
cv::resize(img, resize_img, cv::Size(resize_w, resize_h));
ratio_h = float(resize_h) / float(h);
ratio_w = float(resize_w) / float(w);
}
void CrnnResizeImg::Run(const cv::Mat &img, cv::Mat &resize_img, float wh_ratio,
bool use_tensorrt,
const std::vector<int> &rec_image_shape) {
int imgC, imgH, imgW;
imgC = rec_image_shape[0];
imgH = rec_image_shape[1];
imgW = rec_image_shape[2];
imgW = int(32 * wh_ratio);
float ratio = float(img.cols) / float(img.rows);
int resize_w, resize_h;
if (ceilf(imgH * ratio) > imgW)
resize_w = imgW;
else
resize_w = int(ceilf(imgH * ratio));
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
cv::INTER_LINEAR);
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0,
int(imgW - resize_img.cols), cv::BORDER_CONSTANT,
{127, 127, 127});
}
void ClsResizeImg::Run(const cv::Mat &img, cv::Mat &resize_img,
bool use_tensorrt,
const std::vector<int> &rec_image_shape) {
int imgC, imgH, imgW;
imgC = rec_image_shape[0];
imgH = rec_image_shape[1];
imgW = rec_image_shape[2];
float ratio = float(img.cols) / float(img.rows);
int resize_w, resize_h;
if (ceilf(imgH * ratio) > imgW)
resize_w = imgW;
else
resize_w = int(ceilf(imgH * ratio));
cv::resize(img, resize_img, cv::Size(resize_w, imgH), 0.f, 0.f,
cv::INTER_LINEAR);
if (resize_w < imgW) {
cv::copyMakeBorder(resize_img, resize_img, 0, 0, 0, imgW - resize_w,
cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0));
}
}
} // namespace PaddleOCR
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <dirent.h>
#include <include/utility.h>
#include <iostream>
#include <ostream>
#include <sys/stat.h>
#include <sys/types.h>
#include <vector>
namespace PaddleOCR {
std::vector<std::string> Utility::ReadDict(const std::string &path) {
std::ifstream in(path);
std::string line;
std::vector<std::string> m_vec;
if (in) {
while (getline(in, line)) {
m_vec.push_back(line);
}
} else {
std::cout << "no such label file: " << path << ", exit the program..."
<< std::endl;
exit(1);
}
return m_vec;
}
void Utility::VisualizeBboxes(
const cv::Mat &srcimg,
const std::vector<std::vector<std::vector<int>>> &boxes) {
cv::Mat img_vis;
srcimg.copyTo(img_vis);
for (int n = 0; n < boxes.size(); n++) {
cv::Point rook_points[4];
for (int m = 0; m < boxes[n].size(); m++) {
rook_points[m] = cv::Point(int(boxes[n][m][0]), int(boxes[n][m][1]));
}
const cv::Point *ppt[1] = {rook_points};
int npt[] = {4};
cv::polylines(img_vis, ppt, npt, 1, 1, CV_RGB(0, 255, 0), 2, 8, 0);
}
cv::imwrite("./ocr_vis.png", img_vis);
std::cout << "The detection visualized image saved in ./ocr_vis.png"
<< std::endl;
}
// list all files under a directory
void Utility::GetAllFiles(const char *dir_name,
std::vector<std::string> &all_inputs) {
if (NULL == dir_name) {
std::cout << " dir_name is null ! " << std::endl;
return;
}
struct stat s;
lstat(dir_name, &s);
if (!S_ISDIR(s.st_mode)) {
std::cout << "dir_name is not a valid directory !" << std::endl;
all_inputs.push_back(dir_name);
return;
} else {
struct dirent *filename; // return value for readdir()
DIR *dir; // return value for opendir()
dir = opendir(dir_name);
if (NULL == dir) {
std::cout << "Can not open dir " << dir_name << std::endl;
return;
}
std::cout << "Successfully opened the dir !" << std::endl;
while ((filename = readdir(dir)) != NULL) {
if (strcmp(filename->d_name, ".") == 0 ||
strcmp(filename->d_name, "..") == 0)
continue;
// img_dir + std::string("/") + all_inputs[0];
all_inputs.push_back(dir_name + std::string("/") +
std::string(filename->d_name));
}
}
}
} // namespace PaddleOCR
\ No newline at end of file
OPENCV_DIR=your_opencv_dir set -o errexit
LIB_DIR=your_paddle_inference_dir
CUDA_LIB_DIR=your_cuda_lib_dir if [ $# != 1 ] ; then
CUDNN_LIB_DIR=your_cudnn_lib_dir echo "USAGE: $0 MODE (one of ['det', 'rec', 'system'])"
echo " e.g.: $0 system"
exit 1;
fi
# MODE be one of ['det', 'rec', 'system']
MODE=$1
cp CMakeLists_$MODE.txt CMakeLists.txt
OPENCV_DIR=/paddle/git/new/PaddleOCR/deploy/cpp_infer/opencv-3.4.7/opencv3/
LIB_DIR=/paddle/git/new/PaddleOCR/deploy/cpp_infer/paddle_inference/
CUDA_LIB_DIR=/usr/local/cuda/lib64/
CUDNN_LIB_DIR=/usr/lib/x86_64-linux-gnu/
BUILD_DIR=build BUILD_DIR=build
rm -rf ${BUILD_DIR} rm -rf ${BUILD_DIR}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册