diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..14cc903 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +dependency diff --git a/README.md b/README.md index 0472e14..0b1345c 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ git clone https://github.com/Amlogic-NN/amlnn-toolkit.git ../amlnn-toolkit ## Android Complication -Android compilation requires the NDK toolchain. The build scripts look for the NDK path via the environment variables +Android compilation requires the NDK toolchain. The build scripts look for the NDK path via the environment variables Set environment variables before building, for example: @@ -101,10 +101,12 @@ export ANDROID_NDK_PATH=/path/to/android-ndk-r25c To build **all examples at once**, use the top-level batch script: ```bash -cd examples -./build-android-all.sh # auto-detects amlnn-toolkit +./examples/build-android-all.sh # auto-detects amlnn-toolkit # or explicitly: AMLNN_HOME=/path/to/amlnn-toolkit ./build-android-all.sh + +#clean build files +./examples/build-android-all.sh ``` The script automatically cleans the previous build, resolves the AMLNN SDK via the priority rules above, and prints a build summary at the end. @@ -122,35 +124,36 @@ export YOCTO_SDK_ROOT=/path/to/poky/sdk The toolchain file is shared across all demos at `examples/cmake/yocto-toolchain.cmake`. -**Build a single demo:** -```bash -cd examples/yolox/cpp - -# 64-bit (default) -./build-linux.sh -m yocto -s /path/to/poky/sdk - -# 32-bit -./build-linux.sh -m yocto -b 32 -s /path/to/poky/32bit-sdk -``` **Build all demos at once:** ```bash -cd examples # 64-bit -./build-linux-all.sh -m yocto -s /path/to/poky/sdk +./examples/build-linux-all.sh -m yocto -s /path/to/poky/sdk # 32-bit -./build-linux-all.sh -m yocto -b 32 -s /path/to/poky/32bit-sdk +./examples/build-linux-all.sh -m yocto -b 32 -s /path/to/poky/32bit-sdk # Clean yocto build artifacts -./clean-linux-all.sh -m yocto +./examples/clean-linux-all.sh -m yocto ``` > **Note:** The `LLMs` demo is automatically excluded from the batch build scripts. + +**Build a single demo:** + +```bash + +# 64-bit (default) +./examples/yolox/cpp/build-linux.sh -m yocto -s /path/to/poky/sdk + +# 32-bit +./examples/yolox/cpp/build-linux.sh -m yocto -b 32 -s /path/to/poky/32bit-sdk +``` + # **Release Notes** | Version | Description | diff --git a/common/.gitkeep b/common/.gitkeep old mode 100644 new mode 100755 diff --git a/common/model_loader.cpp b/common/model_loader.cpp index 78aa4ac..a6898ac 100755 --- a/common/model_loader.cpp +++ b/common/model_loader.cpp @@ -2,6 +2,22 @@ // Exposed Functions // ------------------------------------------------------------------------- +/* + * Copyright (C) 2026 Amlogic, Inc. 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 "model_loader.h" #include #include @@ -33,7 +49,7 @@ void* init_network(const char* model_path) { config.forward_ctrl.softop_info.set_openmp_opt_flag = true; config.forward_ctrl.softop_info.openmp_opt_num = sizeof(openmp_opt) / sizeof(aml_openmp_opt_t); config.forward_ctrl.softop_info.openmp_opt = openmp_opt; - + /* set neon */ aml_neon_opt_t neon_opt[] = { @@ -42,12 +58,12 @@ void* init_network(const char* model_path) { .enable_neon = true, .involve_all_ops = true, }, - + }; config.forward_ctrl.softop_info.set_neon_opt_flag = true; config.forward_ctrl.softop_info.neon_opt_num = sizeof(neon_opt) / sizeof(aml_neon_opt_t); config.forward_ctrl.softop_info.neon_opt = neon_opt; - + qcontext = aml_module_create(&config); if (NULL == qcontext) { LOGE("aml_module_create fail for %s", model_path); @@ -61,7 +77,7 @@ int uninit_network(void* qcontext) { if (ret) { LOGE("aml_module_destroy fail."); return -1; - } + } return ret; } diff --git a/common/model_loader.h b/common/model_loader.h index 217878d..3de9b66 100755 --- a/common/model_loader.h +++ b/common/model_loader.h @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2026 Amlogic, Inc. 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. + */ + #ifndef _AMLNN_MODEL_LOADER_H_ #define _AMLNN_MODEL_LOADER_H_ diff --git a/dependency/.gitkeep b/dependency/.gitkeep old mode 100644 new mode 100755 diff --git a/docs/Platform_compile_guide.md b/docs/Platform_compile_guide.md index e289aba..41433c4 100755 --- a/docs/Platform_compile_guide.md +++ b/docs/Platform_compile_guide.md @@ -1,6 +1,6 @@ -# 1. Android Platform +# 1、Android Platform ​ **Android compilation** depends on the NDK toolchain. Currently, version r25c is recommended. Download link: https://github.com/android/ndk/wiki/Unsupported-Downloads @@ -34,7 +34,7 @@ -# 2. Linux Platform +# 2、Linux Platform **Linux compilation** toolchain dependency: **gcc-arm-10.3-2021.07-x86_64-arm-none-linux-gnueabihf**, download link: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-a/downloads/ diff --git a/examples/ECAPA-TDNN/cpp/.gitkeep b/examples/ECAPA-TDNN/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/ECAPA-TDNN/model/.gitkeep b/examples/ECAPA-TDNN/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/ECAPA-TDNN/py/.gitkeep b/examples/ECAPA-TDNN/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/LLMs/README.md b/examples/LLMs/README.md index 045a757..0b62756 100644 --- a/examples/LLMs/README.md +++ b/examples/LLMs/README.md @@ -94,7 +94,7 @@ To compile the CPP project using Android NDK, please follow these steps: **Hardware Requirements**: - SOC: A311D2 -- DDR: ≥ 4GB +- DDR: = 4GB **System Requirements**: - OS: Ubuntu 22.04 diff --git a/examples/LLMs/cpp/Android.mk b/examples/LLMs/cpp/Android.mk index 903d5d2..0417932 100644 --- a/examples/LLMs/cpp/Android.mk +++ b/examples/LLMs/cpp/Android.mk @@ -1,3 +1,19 @@ +# +# Copyright (C) 2024–2025 Amlogic, Inc. 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. +# + LOCAL_PATH := $(call my-dir) LLM_SDK_PATH := $(LOCAL_PATH)/../../../../amlnn-toolkit/nn_runtime/llmsdk 3RDPARTY_PATH := $(LOCAL_PATH)/../../../dependency @@ -17,4 +33,15 @@ LOCAL_LDLIBS := -llog -ldl -lm -fuse-ld=ld LOCAL_MODULE := demo_llm_main +LOCAL_LICENSE_KINDS := SPDX-License-Identifier: Apache-2.0 + +LOCAL_LICENSE_CONDITIONS := notice + +LOCAL_LICENSE_COMMENTS := Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + +LOCAL_LICENSE_URL := https://www.apache.org/licenses/LICENSE-2.0 + +LOCAL_LICENSE_FILE := LICENSE + + include $(BUILD_EXECUTABLE) diff --git a/examples/LLMs/cpp/CMakeLists.txt b/examples/LLMs/cpp/CMakeLists.txt index dc16b29..cdfddab 100644 --- a/examples/LLMs/cpp/CMakeLists.txt +++ b/examples/LLMs/cpp/CMakeLists.txt @@ -3,30 +3,6 @@ cmake_minimum_required(VERSION 3.5.1) set(CMAKE_SYSTEM_NAME Linux) project(AML_LLM_NNSDK) -# xinxin, when building the .so with Yocto using CMake, you can remove the sysroot settings -# from these CMakeLists.txt files and use the officially recommended approach instead: -# after sourcing the environment script, many environment variables will be set (check with `export`), -# and CMake will configure itself automatically based on them without needing explicit settings here. -# source /mnt/fileroot/xinxin.he/environment/new-yocto/64/environment-setup-armv8a-poky-linux -# export CXXFLAGS=$(echo "$CXXFLAGS" | sed 's/-g//g') -# export CFLAGS=$(echo "$CXXFLAGS" | sed 's/-g//g') -# cmake -DCMAKE_TOOLCHAIN_FILE=${OE_CMAKE_TOOLCHAIN_FILE} .. - -# # Set Yocto cross-compilation environment -# set(SYSROOT_PATH /mnt/fileroot/xinxin.he/environment/new-yocto/64/sysroots/x86_64-pokysdk-linux) -# set(CMAKE_SYSROOT "${SYSROOT_PATH}") -# message(STATUS "Using sysroot path as ${SYSROOT_PATH}") - -# include(CMakeForceCompiler) -# cmake_force_c_compiler("${SYSROOT_PATH}/usr/bin/aarch64-poky-linux/aarch64-poky-linux-gcc" GNU) -# cmake_force_cxx_compiler("${SYSROOT_PATH}/usr/bin/aarch64-poky-linux/aarch64-poky-linux-g++" GNU) - -# # Set the sysroot for the actual target board -# set(MYSYSROOT "/mnt/fileroot/xinxin.he/environment/new-yocto/64/sysroots/armv8a-poky-linux") -# add_definitions("--sysroot=${MYSYSROOT}") -# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --sysroot=${MYSYSROOT}" CACHE INTERNAL "" FORCE) -# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --sysroot=${MYSYSROOT}" CACHE INTERNAL "" FORCE) -# set(CMAKE_FIND_ROOT_PATH "${MYSYSROOT}") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/examples/LLMs/model/llm_result.png b/examples/LLMs/model/llm_result.png new file mode 100755 index 0000000..ef730b6 Binary files /dev/null and b/examples/LLMs/model/llm_result.png differ diff --git a/examples/LLMs/py/simple_chat.py b/examples/LLMs/py/simple_chat.py index 86c82b7..fa71634 100755 --- a/examples/LLMs/py/simple_chat.py +++ b/examples/LLMs/py/simple_chat.py @@ -1,5 +1,21 @@ # -*- coding: utf-8 -*- +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + import argparse import sys from datetime import datetime diff --git a/examples/blazepose_detect/README.md b/examples/blazepose_detect/README.md index e69de29..239b3a6 100644 --- a/examples/blazepose_detect/README.md +++ b/examples/blazepose_detect/README.md @@ -0,0 +1,104 @@ +# blazepose_detect + +## 1.Overview + +​ BlazePose Detection was introduced by Google as part of the MediaPipe framework, providing fast and lightweight person detection optimized for real-time performance on mobile and edge devices. The detector identifies the human region of interest (ROI) in an image, ensuring stable and efficient pose tracking in subsequent stages. + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** https://github.com/google-ai-edge/mediapipe/tree/master + + - **Download weights** + + wget https://storage.googleapis.com/mediapipe-assets/pose_detection.tflite + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2: PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** + +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/blazepose_detect/cpp +./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/blazepose_detect_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/blazepose_detect_demo /data/local/tmp/ +adb push model/blazepose_detect_int8_A311D2.adla /data/local/tmp/ +adb push test_image.jpg /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x blazepose_detect_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./blazepose_detect_demo +./blazepose_detect_demo blazepose_detect_int8_A311D2.adla test_image.jpg" +``` + +**Note:** Replace `blazepose_detect_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python blazepose_detect.py --model-path ./blazepose_detect_int8_A311D2.adla +``` + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results +The program will print the detection count and inference time. The result image with bounding boxes will be saved to the specified output path (`result.jpg` by default). + + +You can pull the result image back to view it: +```bash +adb pull result.jpg. +``` +![alt text](result.jpg) + diff --git a/examples/blazepose_detect/cpp/.gitkeep b/examples/blazepose_detect/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_detect/cpp/build-android.sh b/examples/blazepose_detect/cpp/build-android.sh new file mode 100755 index 0000000..b325ee2 --- /dev/null +++ b/examples/blazepose_detect/cpp/build-android.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +# +# Copyright (C) 2024–2025 Amlogic, Inc. 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. +# + +usage() { + echo "Usage: $0 [-a ]" + echo " -a : Target ABI (default: arm64-v8a)" + echo " -h : Show this help message" + exit 1 +} + +# Default values +TARGET_ABI=arm64-v8a + +# Parse arguments +while getopts 'a:h' opt; do + case "$opt" in + a) + TARGET_ABI=$OPTARG + ;; + h) + usage + ;; + *) + usage + ;; + esac +done + +if [ -z "${ANDROID_NDK_PATH}" ]; then + if [ -n "${ANDROID_NDK}" ]; then + ANDROID_NDK_PATH=${ANDROID_NDK} + elif [ -n "${ANDROID_NDK_HOME}" ]; then + ANDROID_NDK_PATH=${ANDROID_NDK_HOME} + else + echo "Error: ANDROID_NDK_PATH is not set." + echo "Please set ANDROID_NDK_PATH to your Android NDK directory." + exit 1 + fi +fi + +ROOT_PWD=$(cd "$(dirname $0)" && pwd) +BUILD_DIR=${ROOT_PWD}/build/android + +echo "Building for Android..." +echo "NDK_PATH: ${ANDROID_NDK_PATH}" +echo "TARGET_ABI: ${TARGET_ABI}" +echo "BUILD_DIR: ${BUILD_DIR}" + +mkdir -p ${BUILD_DIR} +cd ${BUILD_DIR} + +cmake ../../src \ + -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_PATH}/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=${TARGET_ABI} \ + -DANDROID_PLATFORM=android-24 \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR=${ROOT_PWD}/../../../dependency/opencv/opencv-android-sdk-build/sdk/native/jni/abi-${TARGET_ABI} + +make -j4 + +echo "Build complete. Executable in ${BUILD_DIR}/blazepose_detect_demo" diff --git a/examples/blazepose_detect/cpp/build-linux.sh b/examples/blazepose_detect/cpp/build-linux.sh new file mode 100755 index 0000000..6b30728 --- /dev/null +++ b/examples/blazepose_detect/cpp/build-linux.sh @@ -0,0 +1,168 @@ +#!/bin/bash +set -e + +# +# Copyright (C) 2024–2025 Amlogic, Inc. 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. +# + +usage() { + echo "Usage: $0 [-m ] [-a ] [-b ] [-s ] [-t ]" + echo " -m : Build mode: 'linux' or 'yocto' (default: linux)" + echo " -a : Target arch for linux mode (default: aarch64)" + echo " -b : Arch bits for yocto mode: 32 or 64 (default: 64)" + echo " -s : Yocto SDK root path (overrides YOCTO_SDK_ROOT env var)" + echo " -t : CMake toolchain file (overrides TOOLCHAIN_FILE env var)" + echo " -h : Show this help message" + exit 1 +} + +# Default values +BUILD_MODE=linux +TARGET_ARCH=aarch64 +ARCH_BITS=64 +CLI_SDK_ROOT="" +CLI_TOOLCHAIN_FILE="" + +# Parse arguments +while getopts 'm:a:b:s:t:h' opt; do + case "$opt" in + m) + BUILD_MODE=$OPTARG + ;; + a) + TARGET_ARCH=$OPTARG + ;; + b) + ARCH_BITS=$OPTARG + ;; + s) + CLI_SDK_ROOT=$OPTARG + ;; + t) + CLI_TOOLCHAIN_FILE=$OPTARG + ;; + h) + usage + ;; + *) + usage + ;; + esac +done + +ROOT_PWD=$(cd "$(dirname $0)" && pwd) + +# =========================================================================== +# Yocto build +# =========================================================================== +if [[ "${BUILD_MODE}" == "yocto" ]]; then + + if [[ "${ARCH_BITS}" != "32" && "${ARCH_BITS}" != "64" ]]; then + echo "Unsupported ARCH_BITS \"${ARCH_BITS}\". Must be 32 or 64." >&2 + exit 1 + fi + + # Configurable via environment variables (CLI args > env vars > defaults) + CMAKE_BIN="${CMAKE_BIN:-cmake}" + YOCTO_SDK_ROOT="${CLI_SDK_ROOT:-${YOCTO_SDK_ROOT:-/data/yuandian/tools/poky/4.0.20}}" + TOOLCHAIN_FILE="${CLI_TOOLCHAIN_FILE:-${TOOLCHAIN_FILE:-${ROOT_PWD}/../../cmake/yocto-toolchain.cmake}}" + + # Export variables for CMake + export YOCTO_SDK_ROOT + export ARCH_BITS + + BUILD_DIR="${ROOT_PWD}/build/yocto/${ARCH_BITS}" + + echo "==> Building Yocto ${ARCH_BITS}-bit" + echo " toolchain : ${TOOLCHAIN_FILE}" + echo " SDK root : ${YOCTO_SDK_ROOT}" + echo " BUILD_DIR : ${BUILD_DIR}" + + mkdir -p "${BUILD_DIR}" + rm -rf "${BUILD_DIR}" + + # Select OpenCV based on target architecture + if [[ "${ARCH_BITS}" == "32" ]]; then + OPENCV_DIR="${ROOT_PWD}/../../../dependency/opencv/opencv-linux-armhf/share/OpenCV" + else + OPENCV_DIR="${ROOT_PWD}/../../../dependency/opencv/opencv-linux-aarch64/share/OpenCV" + fi + + "${CMAKE_BIN}" \ + -S "${ROOT_PWD}/src" \ + -B "${BUILD_DIR}" \ + -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" \ + -DYOCTO_SDK_ROOT="${YOCTO_SDK_ROOT}" \ + -DARCH_BITS="${ARCH_BITS}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR="${OPENCV_DIR}" + + "${CMAKE_BIN}" --build "${BUILD_DIR}" --config Release + + # Strip (best-effort) + HOST_SYSROOT="${YOCTO_SDK_ROOT}/sysroots/x86_64-pokysdk-linux" + if [[ "${ARCH_BITS}" == "32" ]]; then + CROSS_TRIPLE="arm-poky-linux-gnueabi" + else + CROSS_TRIPLE="aarch64-poky-linux" + fi + STRIP_TOOL="${HOST_SYSROOT}/usr/bin/${CROSS_TRIPLE}/${CROSS_TRIPLE}-strip" + if [[ -x "${STRIP_TOOL}" ]]; then + "${STRIP_TOOL}" --strip-unneeded "${BUILD_DIR}/blazepose_detect_demo" + else + echo "warning: strip tool not found; keeping debug info." >&2 + fi + + echo "Build complete. Executable in ${BUILD_DIR}/blazepose_detect_demo" + exit 0 +fi + +# =========================================================================== +# Standard Linux cross-compile build +# =========================================================================== + +# Default to aarch64-linux-gnu if GCC_COMPILER is not set +GCC_COMPILER=${GCC_COMPILER:-aarch64-linux-gnu} + +# Set compilers +export CC=${GCC_COMPILER}-gcc +export CXX=${GCC_COMPILER}-g++ + +# Validate compiler +if ! command -v ${CC} &> /dev/null; then + echo "Error: Compiler ${CC} not found." + echo "Please set GCC_COMPILER environment variable to your cross-compiler path prefix." + echo "Example: export GCC_COMPILER=/path/to/toolchain/bin/aarch64-linux-gnu" + exit 1 +fi + +BUILD_DIR=${ROOT_PWD}/build/linux + +echo "Building for Linux..." +echo "COMPILER: ${CC}" +echo "TARGET_ARCH: ${TARGET_ARCH}" +echo "BUILD_DIR: ${BUILD_DIR}" + +mkdir -p ${BUILD_DIR} +cd ${BUILD_DIR} + +cmake ../../src \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=${TARGET_ARCH} \ + -DCMAKE_BUILD_TYPE=Release + +make -j4 + +echo "Build complete. Executable in ${BUILD_DIR}/blazepose_detect_demo" diff --git a/examples/blazepose_detect/cpp/src/CMakeLists.txt b/examples/blazepose_detect/cpp/src/CMakeLists.txt new file mode 100755 index 0000000..7836079 --- /dev/null +++ b/examples/blazepose_detect/cpp/src/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.10...3.27) +project(blazepose_detect_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(blazepose_detect_demo + main.cpp + postprocess.cpp + postprocess.h + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(blazepose_detect_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} +) diff --git a/examples/blazepose_detect/cpp/src/anchors.h b/examples/blazepose_detect/cpp/src/anchors.h new file mode 100755 index 0000000..09c9776 --- /dev/null +++ b/examples/blazepose_detect/cpp/src/anchors.h @@ -0,0 +1,2278 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef BLAZEPOSE_DETECT_ANCHORS_H +#define BLAZEPOSE_DETECT_ANCHORS_H + +const int NUM_ANCHORS = 2254; +const float anchors[NUM_ANCHORS * 4] = { + 0.01785714f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.01785714f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.05357143f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.08928572f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.12500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.16071428f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.19642857f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.23214285f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.26785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.30357143f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.33928570f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.37500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.41071430f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.44642857f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.48214287f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.51785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.55357140f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.58928573f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.62500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.66071427f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.69642860f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.73214287f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.76785713f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.80357140f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.83928573f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.87500000f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.91071427f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.94642860f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.01785714f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.05357143f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.08928572f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.12500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.16071428f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.19642857f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.23214285f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.26785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.30357143f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.33928570f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.37500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.41071430f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.44642857f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.48214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.51785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.55357140f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.58928573f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.62500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.66071427f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.69642860f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.73214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.76785713f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.80357140f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.83928573f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.87500000f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.91071427f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.94642860f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.98214287f, 0.98214287f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.03571429f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.10714286f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.17857143f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.25000000f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.32142857f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.39285713f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.46428570f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.53571427f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.60714287f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.67857140f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.75000000f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.82142860f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.89285713f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.03571429f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.10714286f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.17857143f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.25000000f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.32142857f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.39285713f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.46428570f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.53571427f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.60714287f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.67857140f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.75000000f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.82142860f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.89285713f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.96428573f, 0.96428573f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.07142857f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.21428572f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.35714287f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.50000000f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.64285713f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.78571427f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.07142857f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.21428572f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.35714287f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.50000000f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.64285713f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.78571427f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, + 0.92857140f, 0.92857140f, 1.00000000f, 1.00000000f, +}; + +#endif // BLAZEPOSE_DETECT_ANCHORS_H diff --git a/examples/blazepose_detect/cpp/src/main.cpp b/examples/blazepose_detect/cpp/src/main.cpp new file mode 100755 index 0000000..c0d3835 --- /dev/null +++ b/examples/blazepose_detect/cpp/src/main.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "postprocess.h" +#include "model_loader.h" + +const std::string DEFAULT_OUTPUT_PATH = "./result.jpg"; +const int MODEL_INPUT_WIDTH = 224; +const int MODEL_INPUT_HEIGHT = 224; +const float SCORE_THRESHOLD = 0.5f; +const float NMS_THRESHOLD = 0.3f; + +int main(int argc, char **argv) +{ + std::string model_path; + std::string image_path; + + if (argc != 3) + { + printf("%s \n", argv[0]); + return -1; + } + + if (argc > 1) + model_path = argv[1]; + if (argc > 2) + image_path = argv[2]; + + std::cout << "Blazepose Detect Demo" << std::endl; + std::cout << "Model: " << model_path << std::endl; + std::cout << "Image: " << image_path << std::endl; + std::cout << "Output: " << DEFAULT_OUTPUT_PATH << std::endl; + + // 1. Load Image + cv::Mat img = cv::imread(image_path); + if (img.empty()) + { + std::cerr << "Failed to load image from " << image_path << std::endl; + return -1; + } + + // 2. Initialize Network + void *context = init_network(model_path.c_str()); + if (!context) + { + std::cerr << "Failed to initialize network." << std::endl; + return -1; + } + + // 3. Preprocess + auto start_time = std::chrono::high_resolution_clock::now(); + + auto [preprocessed, scale, pad] = preprocess(img, std::make_tuple(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH)); + std::cout << "scale" << scale << std::endl; + std::cout << "pad: (" + << std::get<0>(pad) << ", " + << std::get<1>(pad) << ")" + << std::endl; + // Quantize to int8 (model expects quantized input) + cv::Mat quantized_img = quantize_input(preprocessed, 0.007843137718737125, -1); + + // 4. Set input and run inference + nn_input inData; + memset(&inData, 0, sizeof(nn_input)); + inData.input_type = BINARY_RAW_DATA; + inData.input = quantized_img.data; + inData.input_index = 0; + inData.size = quantized_img.total() * quantized_img.elemSize(); + + if (aml_module_input_set(context, &inData) != 0) + { + std::cerr << "Failed to set input." << std::endl; + uninit_network(context); + return -1; + } + + aml_output_config_t outconfig; + memset(&outconfig, 0, sizeof(aml_output_config_t)); + outconfig.typeSize = sizeof(aml_output_config_t); + outconfig.format = AML_OUTDATA_FLOAT32; + + nn_output *outdata = (nn_output *)aml_module_output_get(context, outconfig); + if (!outdata) + { + std::cerr << "Failed to run network." << std::endl; + uninit_network(context); + return -1; + } + + // 5. Postprocess + float *ori_boxes = (float *)outdata->out[0].buf; // 2254 * 12 + float *raw_scores = (float *)outdata->out[1].buf; // 2254 * 1 + + std::vector detections = postprocess( + ori_boxes, + raw_scores, + std::make_tuple(preprocessed, scale, pad), + SCORE_THRESHOLD, + NMS_THRESHOLD); + + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration inference_time = end_time - start_time; + + std::cout << "Inference time: " << inference_time.count() << " ms" << std::endl; + std::cout << "Detections: " << detections.size() << std::endl; + + // 6. Draw and Save + cv::Mat result_img = draw_detections(img, detections); + cv::imwrite(DEFAULT_OUTPUT_PATH, result_img); + std::cout << "Result saved to " << DEFAULT_OUTPUT_PATH << std::endl; + + // image_path -> txt_path + std::string txt_path = image_path.substr(0, image_path.find_last_of('.')); + txt_path += ".txt"; + std::ofstream ofs(txt_path); + if (ofs.is_open()) + { + for (const auto &det : detections) + { + for (int i = 0; i < NUM_COORDS + 1; ++i) + ofs << det.coords[i] << (i < NUM_COORDS ? " " : "\n"); + } + } + std::cout << "Detections saved to " << txt_path << std::endl; + + // 7. Cleanup + uninit_network(context); + + return 0; +} diff --git a/examples/blazepose_detect/cpp/src/postprocess.cpp b/examples/blazepose_detect/cpp/src/postprocess.cpp new file mode 100755 index 0000000..1474ce4 --- /dev/null +++ b/examples/blazepose_detect/cpp/src/postprocess.cpp @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" +#include +#include +#include +#include + +#define LOGI(...) \ + do \ + { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } while (0) +#define LOGE(...) \ + do \ + { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) + +// SHOW class names (1 classes) +const char *SHOW_CLASSES[1] = {"pose"}; + +inline float sigmoid(float x) +{ + return 1.0f / (1.0f + std::exp(-x)); +} + +void decode_boxes(const float *ori_boxes, std::vector> &boxes) +{ + const float x_scale = 224.0f; + const float y_scale = 224.0f; + const float h_scale = 224.0f; + const float w_scale = 224.0f; + boxes.resize(NUM_ANCHORS, std::vector(NUM_COORDS, 0.0f)); + for (int i = 0; i < NUM_ANCHORS; ++i) + { + float x_center = ori_boxes[i * NUM_COORDS + 0] / x_scale * anchors[i * 4 + 2] + anchors[i * 4 + 0]; + float y_center = ori_boxes[i * NUM_COORDS + 1] / y_scale * anchors[i * 4 + 3] + anchors[i * 4 + 1]; + float w = ori_boxes[i * NUM_COORDS + 2] / w_scale * anchors[i * 4 + 2]; + float h = ori_boxes[i * NUM_COORDS + 3] / h_scale * anchors[i * 4 + 3]; + boxes[i][0] = y_center - h / 2.0f; + boxes[i][1] = x_center - w / 2.0f; + boxes[i][2] = y_center + h / 2.0f; + boxes[i][3] = x_center + w / 2.0f; + for (int k = 0; k < 4; ++k) + { + int offset = 4 + k * 2; + float keypoint_x = ori_boxes[i * NUM_COORDS + offset] / x_scale * anchors[i * 4 + 2] + anchors[i * 4 + 0]; + float keypoint_y = ori_boxes[i * NUM_COORDS + offset + 1] / y_scale * anchors[i * 4 + 3] + anchors[i * 4 + 1]; + boxes[i][offset] = keypoint_x; + boxes[i][offset + 1] = keypoint_y; + } + } +} + +void convert_output_to_detections(const float *ori_boxes, const float *ori_scores, std::vector &detections, float min_score_thresh = 0.3f) +{ + std::vector> decoded_boxes; + decode_boxes(ori_boxes, decoded_boxes); + detections.clear(); + for (int i = 0; i < NUM_ANCHORS; ++i) + { + float s = sigmoid(std::min(std::max(ori_scores[i], -100.0f), 100.0f)); + if (s < min_score_thresh) + continue; + BlazePoseDetection det; + for (int j = 0; j < NUM_COORDS; ++j) + det.coords[j] = decoded_boxes[i][j]; + det.coords[NUM_COORDS] = s; + detections.push_back(det); + } +} + +static inline float iou(const float *a, const float *b) +{ + float xA = std::max(a[1], b[1]); + float yA = std::max(a[0], b[0]); + float xB = std::min(a[3], b[3]); + float yB = std::min(a[2], b[2]); + float interW = std::max(0.0f, xB - xA); + float interH = std::max(0.0f, yB - yA); + float inter = interW * interH; + float areaA = (a[3] - a[1]) * (a[2] - a[0]); + float areaB = (b[3] - b[1]) * (b[2] - b[0]); + float unionAB = areaA + areaB - inter; + if (unionAB <= 0.0f) + return 0.0f; + return inter / unionAB; +} + +void weighted_nms( + std::vector &detections, std::vector &output, float iou_threshold = 0.3f) +{ + output.clear(); + if (detections.empty()) + return; + std::sort(detections.begin(), detections.end(), + [](const BlazePoseDetection &a, const BlazePoseDetection &b) + { + return a.coords[NUM_COORDS] > b.coords[NUM_COORDS]; + }); + std::vector removed(detections.size(), false); + for (size_t i = 0; i < detections.size(); ++i) + { + if (removed[i]) + continue; + std::vector overlap_indices; + overlap_indices.push_back(i); + for (size_t j = i + 1; j < detections.size(); ++j) + { + if (removed[j]) + continue; + if (iou(detections[i].coords, detections[j].coords) > iou_threshold) + overlap_indices.push_back(j); + } + float total_score = 0.0f; + std::vector weighted(NUM_COORDS, 0.0f); + for (size_t idx : overlap_indices) + { + float score = detections[idx].coords[NUM_COORDS]; + total_score += score; + for (int k = 0; k < NUM_COORDS; ++k) + weighted[k] += detections[idx].coords[k] * score; + removed[idx] = true; + } + BlazePoseDetection wdet; + for (int k = 0; k < NUM_COORDS; ++k) + wdet.coords[k] = weighted[k] / total_score; + wdet.coords[NUM_COORDS] = total_score / overlap_indices.size(); + output.push_back(wdet); + } +} + +std::tuple> preprocess(cv::Mat img, std::tuple new_shape) +{ + cv::Mat img_rgb; + + if (img.empty()) + { + LOGE("Preprocess received empty image"); + return {}; + } + + // Convert to RGB + if (img.channels() == 4) + cv::cvtColor(img, img_rgb, cv::COLOR_RGBA2RGB); + else if (img.channels() == 3) + cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB); + else + img_rgb = img.clone(); + + int orig_h = img.rows; + int orig_w = img.cols; + float scale = std::min(static_cast(std::get<0>(new_shape)) / orig_h, + static_cast(std::get<1>(new_shape)) / orig_w); + int new_h = static_cast(round(orig_h * scale)); + int new_w = static_cast(round(orig_w * scale)); + + cv::Mat img_resized; + cv::resize(img_rgb, img_resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); + + int pad_h = std::get<0>(new_shape) - new_h; + int pad_w = std::get<1>(new_shape) - new_w; + int pad_left = static_cast(round(pad_w / 2.0 - 0.1)); + int pad_right = static_cast(round(pad_w / 2.0 + 0.1)); + int pad_top = static_cast(round(pad_h / 2.0 - 0.1)); + int pad_bottom = static_cast(round(pad_h / 2.0 + 0.1)); + + cv::Mat img_padded; + cv::copyMakeBorder(img_resized, img_padded, pad_top, pad_bottom, pad_left, pad_right, cv::BORDER_CONSTANT, cv::Scalar(0, 0, 0)); + + cv::Mat img_float; + img_padded.convertTo(img_float, CV_32F, 1.0 / 127.5, -1.0); + + scale = 1.0f / scale; + int pad_orig_h = static_cast(pad_top * scale); + int pad_orig_w = static_cast(pad_left * scale); + + return std::make_tuple(img_float, scale, std::make_tuple(pad_orig_h, pad_orig_w)); +} + +cv::Mat quantize_input(const cv::Mat &float_img, float scale, int8_t zero_point) +{ + if (float_img.empty() || float_img.type() != CV_32FC3) + { + LOGE("quantize_input: Invalid input image (must be CV_32FC3)"); + return cv::Mat(); + } + + cv::Mat quantized_img(float_img.rows, float_img.cols, CV_8SC3); + const float *src_ptr = (const float *)float_img.data; + int8_t *dst_ptr = (int8_t *)quantized_img.data; + + int total_elements = float_img.total() * float_img.channels(); + for (int i = 0; i < total_elements; ++i) + { + dst_ptr[i] = static_cast(std::round(src_ptr[i] / scale + zero_point)); + } + + return quantized_img; +} + +void denorm_detections(std::vector &detection, float scale, const float pad[2]) +{ + detection[0] = detection[0] * scale * 224.0f - pad[0]; + detection[1] = detection[1] * scale * 224.0f - pad[1]; + detection[2] = detection[2] * scale * 224.0f - pad[0]; + detection[3] = detection[3] * scale * 224.0f - pad[1]; + + for (size_t k = 4; k + 1 < detection.size(); k += 2) + { + detection[k] = detection[k] * scale * 224.0f - pad[1]; + detection[k + 1] = detection[k + 1] * scale * 224.0f - pad[0]; + } +} + +std::vector postprocess(float *ori_boxes, float *ori_scores, + std::tuple> input_tuple, + float conf_threshold, float iou_threshold) +{ + float scale = std::get<1>(input_tuple); + int pad_left = std::get<0>(std::get<2>(input_tuple)); + int pad_top = std::get<1>(std::get<2>(input_tuple)); + float pad[2] = {static_cast(pad_left), static_cast(pad_top)}; + + std::vector detections; + convert_output_to_detections(ori_boxes, ori_scores, detections, conf_threshold); + + std::vector filtered; + weighted_nms(detections, filtered, iou_threshold); + + int pose_num = filtered.size(); + for (size_t b = 0; b < pose_num; ++b) + { + std::vector coords(filtered[b].coords, filtered[b].coords + NUM_COORDS + 1); + // mapping to original size + denorm_detections(coords, scale, pad); + for (size_t i = 0; i < NUM_COORDS + 1; ++i) + filtered[b].coords[i] = coords[i]; + } + + return filtered; +} + +cv::Mat draw_detections(cv::Mat image, const std::vector &detections) +{ + cv::Mat drawn_image = image.clone(); + int class_id = 0; + for (const auto &det : detections) + { + // Generate color based on class_id using HSV + float hue = fmod(class_id * 137.508f, 360.0f); + cv::Mat hsv(1, 1, CV_8UC3, cv::Scalar(hue / 2.0f, 204, 230)); + cv::Mat rgb; + cv::cvtColor(hsv, rgb, cv::COLOR_HSV2BGR); + cv::Scalar color(rgb.at(0, 0)[0], rgb.at(0, 0)[1], rgb.at(0, 0)[2]); + + // Draw bounding box + int x1 = static_cast(det.coords[1]); + int y1 = static_cast(det.coords[0]); + int x2 = static_cast(det.coords[3]); + int y2 = static_cast(det.coords[2]); + cv::rectangle(drawn_image, cv::Point(x1, y1), cv::Point(x2, y2), color, 2); + + // Draw label + std::string label = std::string(SHOW_CLASSES[class_id]) + ": " + cv::format("%.2f", det.coords[12]); + int baseline = 0; + cv::Size text_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.6, 1, &baseline); + + int label_x = x1; + int label_y = y1 - 5; + if (label_y < text_size.height) + label_y = x1 + text_size.height + 5; + + // Draw label background + cv::rectangle(drawn_image, + cv::Point(label_x, label_y - text_size.height - baseline), + cv::Point(label_x + text_size.width, label_y + baseline), + color, cv::FILLED); + + // Determine text color based on background brightness + int brightness = (color[0] + color[1] + color[2]) / 3; + cv::Scalar text_color = brightness < 128 ? cv::Scalar(255, 255, 255) : cv::Scalar(0, 0, 0); + + cv::putText(drawn_image, label, + cv::Point(label_x, label_y), + cv::FONT_HERSHEY_SIMPLEX, 0.6, text_color, 1, cv::LINE_AA); + } + return drawn_image; +} diff --git a/examples/blazepose_detect/cpp/src/postprocess.h b/examples/blazepose_detect/cpp/src/postprocess.h new file mode 100755 index 0000000..2be16cd --- /dev/null +++ b/examples/blazepose_detect/cpp/src/postprocess.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef _AMLNN_BLAZEPOSE_DETECT_POSTPROCESS_H_ +#define _AMLNN_BLAZEPOSE_DETECT_POSTPROCESS_H_ + +#include +#include +#include +#include + +#include "anchors.h" + +#define NUM_COORDS 12 + +// BlazePoseDetection result structure +struct BlazePoseDetection +{ + float coords[NUM_COORDS + 1]; // 12 coords + 1 score +}; + +// COCO class names (80 classes) +extern const char *COCO_CLASSES[80]; + +// Preprocess image with letterbox resizing +std::tuple> preprocess(cv::Mat img, std::tuple new_shape); + +// Quantize float32 image to int8 for model input +cv::Mat quantize_input(const cv::Mat &float_img, float scale = 0.007843137718737125, int8_t zero_point = -1); + +// Postprocess blazepose_detect outputs with DFL decoding +std::vector postprocess(float *raw_boxes, float *raw_scores, + std::tuple> input_tuple, + float conf_threshold, float iou_threshold); + +// Draw detections on image +cv::Mat draw_detections(cv::Mat image, const std::vector &detections); + +#endif // _AMLNN_BLAZEPOSE_DETECT_POSTPROCESS_H_ diff --git a/examples/blazepose_detect/model/.gitkeep b/examples/blazepose_detect/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_detect/model/adla_convert.sh b/examples/blazepose_detect/model/adla_convert.sh new file mode 100755 index 0000000..bf89606 --- /dev/null +++ b/examples/blazepose_detect/model/adla_convert.sh @@ -0,0 +1,40 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + +# 1. $1: set ADLA_TOOL_PATH +# 2. $2: set target-platform +# for A311D2 target-platform is PRODUCT_PID0XA003 +# for S905X5 target-platform is PRODUCT_PID0XA005 +# Usage: ./adla_convert.sh pose_detection.tflite /XXX/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + +model_path=$1 +ADLA_TOOL_PATH=$2 +target_platform=$3 + +echo "model_path:[$model_path]" +echo "ADLA_TOOL_PATH:[$ADLA_TOOL_PATH]" +echo "target-platform:[$target_platform]" + +adla_convert=${ADLA_TOOL_PATH}/bin/adla_convert + +$adla_convert --model-type tflite \ + --model $model_path \ + --inputs input_1 --input-shapes "224,224,3" \ + --quantize-dtype int8 \ + --source-file dataset_coco.txt \ + --channel-mean-value "127.5,127.5,127.5,127.5" \ + --target-platform $target_platform \ + --disable-per-channel false diff --git a/examples/blazepose_detect/model/dataset_coco.txt b/examples/blazepose_detect/model/dataset_coco.txt new file mode 100755 index 0000000..5e990aa --- /dev/null +++ b/examples/blazepose_detect/model/dataset_coco.txt @@ -0,0 +1,50 @@ +../../../resource/coco_dataset/000000000139.jpg +../../../resource/coco_dataset/000000000285.jpg +../../../resource/coco_dataset/000000000632.jpg +../../../resource/coco_dataset/000000000724.jpg +../../../resource/coco_dataset/000000000776.jpg +../../../resource/coco_dataset/000000000785.jpg +../../../resource/coco_dataset/000000000802.jpg +../../../resource/coco_dataset/000000000872.jpg +../../../resource/coco_dataset/000000000885.jpg +../../../resource/coco_dataset/000000001000.jpg +../../../resource/coco_dataset/000000001268.jpg +../../../resource/coco_dataset/000000001296.jpg +../../../resource/coco_dataset/000000001353.jpg +../../../resource/coco_dataset/000000001425.jpg +../../../resource/coco_dataset/000000001490.jpg +../../../resource/coco_dataset/000000001503.jpg +../../../resource/coco_dataset/000000001532.jpg +../../../resource/coco_dataset/000000001584.jpg +../../../resource/coco_dataset/000000001675.jpg +../../../resource/coco_dataset/000000001761.jpg +../../../resource/coco_dataset/000000001818.jpg +../../../resource/coco_dataset/000000001993.jpg +../../../resource/coco_dataset/000000002006.jpg +../../../resource/coco_dataset/000000002149.jpg +../../../resource/coco_dataset/000000002153.jpg +../../../resource/coco_dataset/000000002157.jpg +../../../resource/coco_dataset/000000002261.jpg +../../../resource/coco_dataset/000000002299.jpg +../../../resource/coco_dataset/000000002431.jpg +../../../resource/coco_dataset/000000002473.jpg +../../../resource/coco_dataset/000000002532.jpg +../../../resource/coco_dataset/000000002587.jpg +../../../resource/coco_dataset/000000002592.jpg +../../../resource/coco_dataset/000000002685.jpg +../../../resource/coco_dataset/000000002923.jpg +../../../resource/coco_dataset/000000003156.jpg +../../../resource/coco_dataset/000000003255.jpg +../../../resource/coco_dataset/000000003501.jpg +../../../resource/coco_dataset/000000003553.jpg +../../../resource/coco_dataset/000000003661.jpg +../../../resource/coco_dataset/000000003845.jpg +../../../resource/coco_dataset/000000003934.jpg +../../../resource/coco_dataset/000000004134.jpg +../../../resource/coco_dataset/000000004395.jpg +../../../resource/coco_dataset/000000004495.jpg +../../../resource/coco_dataset/000000004765.jpg +../../../resource/coco_dataset/000000004795.jpg +../../../resource/coco_dataset/000000005001.jpg +../../../resource/coco_dataset/000000005037.jpg +../../../resource/coco_dataset/000000005060.jpg diff --git a/examples/blazepose_detect/model/test.png b/examples/blazepose_detect/model/test.png new file mode 100755 index 0000000..eee4554 Binary files /dev/null and b/examples/blazepose_detect/model/test.png differ diff --git a/examples/blazepose_detect/py/.gitkeep b/examples/blazepose_detect/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_detect/py/balzepose_detect.py b/examples/blazepose_detect/py/balzepose_detect.py new file mode 100755 index 0000000..aa8592f --- /dev/null +++ b/examples/blazepose_detect/py/balzepose_detect.py @@ -0,0 +1,266 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + +import numpy as np +import os +import glob +import argparse +import cv2 +from pathlib import Path +from amlnnlite.api import AMLNNLite + +def letterbox(img, new_shape=(224, 224), color=(0, 0, 0)): + shape = img.shape[:2] # [height, width] + scale = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) + new_unpad = (int(round(shape[1] * scale)), int(round(shape[0] * scale))) + pad_w = (new_shape[1] - new_unpad[0]) / 2 + pad_h = (new_shape[0] - new_unpad[1]) / 2 + + if shape[::-1] != new_unpad: + img = cv2.resize(img, new_unpad, interpolation=cv2.INTER_LINEAR) + + top, bottom = int(round(pad_h - 0.1)), int(round(pad_h + 0.1)) + left, right = int(round(pad_w - 0.1)), int(round(pad_w + 0.1)) + img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) + + scale = 1. / scale + ori_left = left * scale + ori_top = top * scale + return img, scale, (ori_left, ori_top) + +def preprocess(img_path, new_shape=(224, 224), data_format='NCHW', s=0.003921568859368563, zp=-128): + original_img = cv2.imread(str(img_path)) + if original_img is None: + raise ValueError(f"can't read image: {img_path}") + + processed_img, scale, pad = letterbox(original_img, new_shape) + rgb_img = cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB) + normalized_img = rgb_img.astype(np.float32) / 127.5 - 1. + + if data_format == 'NCHW': + # HWC -> CHW -> BCHW (ONNX default format) + input_tensor = np.transpose(normalized_img, (2, 0, 1)) + input_tensor = np.expand_dims(input_tensor, axis=0) + elif data_format == 'NHWC': + # HWC -> BHWC (TFLITE default format) + input_tensor = np.expand_dims(normalized_img, axis=0) + else: + raise ValueError(f"Unsupported data format: {data_format}. Only 'NCHW' and 'NHWC' are supported.") + + # Quantize to int8 + input_tensor = np.round(input_tensor / s + zp).astype(np.int8) + + return input_tensor, original_img, scale, pad + + +def postprocess(outputs, scale, pad, data_format='NCHW', anchor_path='anchors.npy', score_threshold=0.5, nms_threshold=0.3): + all_boxes = [] + all_scores = [] + + raw_box = outputs[0] # (1, 2254, 12) + raw_score = outputs[1] # (1, 2254, 1) + + anchors = np.load(anchor_path).astype("float32") + + # all_boxes = decode_boxes(raw_box, anchors) + # anchors: [N, 4] -> x, y, w, h + anc_x, anc_y, anc_w, anc_h = anchors.T + # raw_box shape: [..., K] + all_boxes = np.zeros_like(raw_box) + # box center & size + x_center = raw_box[..., 0] / 224.0 * anc_w + anc_x + y_center = raw_box[..., 1] / 224.0 * anc_h + anc_y + w = raw_box[..., 2] / 224.0 * anc_w + h = raw_box[..., 3] / 224.0 * anc_h + # bbox: ymin, xmin, ymax, xmax + all_boxes[..., 0] = y_center - 0.5 * h + all_boxes[..., 1] = x_center - 0.5 * w + all_boxes[..., 2] = y_center + 0.5 * h + all_boxes[..., 3] = x_center + 0.5 * w + # keypoints (4 points, each has x/y) + for k in range(4): + idx = 4 + k * 2 + all_boxes[..., idx] = raw_box[..., idx] / 224.0 * anc_w + anc_x + all_boxes[..., idx + 1] = raw_box[..., idx + 1] / 224.0 * anc_h + anc_y + + + thresh = 100.0 + raw_score = raw_score.clip(-thresh, thresh) + # Apply sigmoid activation to class scores + all_scores = 1.0 / (1.0 + np.exp(-raw_score)).squeeze(axis=-1) + print(f"all_scores {all_scores}") + print(f"max(all_scores) {max(all_scores[0])}") + + mask = all_scores >= score_threshold + + # Merge all scales + final_boxes = np.concatenate(all_boxes, axis=0) + final_scores = np.concatenate(all_scores, axis=0) + + # Filter by confidence threshold + valid_mask = final_scores > score_threshold + if not np.any(valid_mask): + return [] + + valid_boxes = final_boxes[valid_mask] + valid_scores = final_scores[valid_mask] + + # Map coordinates back to original image + pad_x, pad_y = pad + s = scale * 224 + valid_boxes[:, [0, 2]] = valid_boxes[:, [0, 2]] * s - pad_x + valid_boxes[:, [1, 3]] = valid_boxes[:, [1, 3]] * s - pad_y + valid_boxes[:, 4::2] = valid_boxes[:, 4::2] * s - pad_y + valid_boxes[:, 5::2] = valid_boxes[:, 5::2] * s - pad_x + valid_boxes = np.maximum(valid_boxes, 0) + + # NMS + if len(valid_boxes) > 0: + nms_indices = cv2.dnn.NMSBoxes( + valid_boxes.tolist(), valid_scores.tolist(), score_threshold, nms_threshold + ) + + if len(nms_indices) > 0: + nms_indices = nms_indices.flatten() + detections = [] + + for idx in nms_indices: + x1, y1, x2, y2 = valid_boxes[idx, :4] + confidence = valid_scores[idx] + + # x_center = (valid_boxes[:,1] + valid_boxes[:,3]) / 2 + # y_center = (valid_boxes[:,0] + valid_boxes[:,2]) / 2 + # scale = (valid_boxes[:,3] - valid_boxes[:,1]) # assumes square boxes + + detections.append({ + 'bbox': [float(x1), float(y1), float(x2), float(y2)], + 'confidence': float(confidence) + }) + + return detections + + return [] + +def get_class_color(class_id): + import colorsys + hue = (class_id * 137.508) % 360 + rgb = colorsys.hsv_to_rgb(hue/360.0, 0.8, 0.9) + bgr = (int(rgb[2]*255), int(rgb[1]*255), int(rgb[0]*255)) + return bgr + +def draw_detections(img, detections, save_path): + result_img = img.copy() + + for det in detections: + x1, y1, x2, y2 = [int(coord) for coord in det['bbox']] + confidence = det['confidence'] + class_name = det['class_name'] + class_id = det['class_id'] + + color = get_class_color(class_id) + + cv2.rectangle(result_img, (x1, y1), (x2, y2), color, 2) + + label = f"{class_name}: {confidence:.2f}" + (label_w, label_h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 1) + cv2.rectangle(result_img, (x1, y1 - label_h - 10), (x1 + label_w, y1), color, -1) + text_color = (255, 255, 255) if sum(color) < 400 else (0, 0, 0) + cv2.putText(result_img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, text_color, 1) + + cv2.imwrite(save_path, result_img) + return result_img + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--model-path', default='./blazepose_detect_int8_A311D2.adla') + parser.add_argument('--run-cycles', default= 1, type=int) + args = parser.parse_args() + + # Initialize AMLNNLite + amlnn = AMLNNLite() + amlnn.config( + model_path=args.model_path, # Model file path, Support ADLA and quantized TFlite models + run_cycles=args.run_cycles + ) + amlnn.init() + + # Find all image files in the 01_export_model directory + image_dir = "./" + image_extensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp"] + image_files = [] + for ext in image_extensions: + image_files.extend(glob.glob(os.path.join(image_dir, ext))) + image_files.extend(glob.glob(os.path.join(image_dir, ext.upper()))) + + if not image_files: + print("No image files found in", image_dir) + amlnn.uninit() + return + + print(f"Found {len(image_files)} image files to process:") + for img_file in image_files: + print(f" - {os.path.basename(img_file)}") + print() + + # Process each image + for i, image_path in enumerate(image_files, 1): + print(f"=" * 60) + print(f"Processing image {i}/{len(image_files)}: {os.path.basename(image_path)}") + print(f"=" * 60) + + try: + # Preprocess input + input_tensor, original_img, scale, pad = preprocess(image_path, new_shape=(224, 224), data_format='NHWC', s=0.007843137718737125, zp=-1) + + # Run inference + outputs = amlnn.inference(inputs=[input_tensor]) + + # Postprocess results + detections = postprocess(outputs, scale, pad, data_format='NHWC', score_threshold=0.5, nms_threshold=0.3) + + # Print detection results + if detections: + print(f" Detected {len(detections)} objects:") + for i, det in enumerate(detections, 1): + print(f" {i}. {det['class_name']} ({det['confidence']:.2f})") + else: + print(" No objects detected") + + # Save result image + model_name = Path(args.model_path).stem + result_dir = f"{model_name}_result" + os.makedirs(result_dir, exist_ok=True) + img_name = Path(image_path).stem + save_path = os.path.join(result_dir, f"{img_name}_result.jpg") + draw_detections(original_img, detections, str(save_path)) + print(f" Result saved to: {save_path}") + + except Exception as e: + print(f"Error processing {os.path.basename(image_path)}: {e}") + + print() + + # Optional visualization + amlnn.visualize() + + # Release resources + amlnn.uninit() + + +if __name__ == "__main__": + main() + diff --git a/examples/blazepose_detect/result.jpg b/examples/blazepose_detect/result.jpg new file mode 100755 index 0000000..0ab3d74 Binary files /dev/null and b/examples/blazepose_detect/result.jpg differ diff --git a/examples/blazepose_landmark/README.md b/examples/blazepose_landmark/README.md index e69de29..050e8e1 100644 --- a/examples/blazepose_landmark/README.md +++ b/examples/blazepose_landmark/README.md @@ -0,0 +1,104 @@ +# blazepose_landmark + +## 1.Overview + +​ BlazePose Landmark builds upon the detected ROI to predict 33 precise body keypoints, enabling full-body pose estimation with high accuracy and temporal stability. Its efficient design makes it suitable for applications such as fitness tracking, motion analysis, augmented reality, and real-time human–computer interaction. + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** https://github.com/google-ai-edge/mediapipe/tree/master + + - **Download weights** + + wget https://storage.googleapis.com/mediapipe-assets/pose_landmark_full.tflite + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh pose_detection.tflite /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2: PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** + +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/blazepose_landmark/cpp +./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/blazepose_landmark_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/blazepose_landmark_demo /data/local/tmp/ +adb push model/blazepose_landmark_full_int16_A311D2.adla /data/local/tmp/ +adb push test_image.jpg /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x blazepose_landmark_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./blazepose_landmark_demo +./blazepose_landmark_demo blazepose_landmark_full_int16_A311D2.adla test_image.jpg" +``` + +**Note:** Replace `blazepose_landmark_full_int16_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python blazepose_landmark.py --model-path ./blazepose_landmark_full_int16_A311D2.adla +``` + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results +The program will print the detection count and inference time. The result image with bounding boxes will be saved to the specified output path (`result.jpg` by default). + + +You can pull the result image back to view it: +```bash +adb pull result.jpg. +``` +![alt text](result.jpg) + diff --git a/examples/blazepose_landmark/cpp/.gitkeep b/examples/blazepose_landmark/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_landmark/cpp/build-android.sh b/examples/blazepose_landmark/cpp/build-android.sh new file mode 100755 index 0000000..4afda74 --- /dev/null +++ b/examples/blazepose_landmark/cpp/build-android.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -e + +# +# Copyright (C) 2024–2025 Amlogic, Inc. 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. +# + +usage() { + echo "Usage: $0 [-a ]" + echo " -a : Target ABI (default: arm64-v8a)" + echo " -h : Show this help message" + exit 1 +} + +# Default values +TARGET_ABI=arm64-v8a + +# Parse arguments +while getopts 'a:h' opt; do + case "$opt" in + a) + TARGET_ABI=$OPTARG + ;; + h) + usage + ;; + *) + usage + ;; + esac +done + +if [ -z "${ANDROID_NDK_PATH}" ]; then + if [ -n "${ANDROID_NDK}" ]; then + ANDROID_NDK_PATH=${ANDROID_NDK} + elif [ -n "${ANDROID_NDK_HOME}" ]; then + ANDROID_NDK_PATH=${ANDROID_NDK_HOME} + else + echo "Error: ANDROID_NDK_PATH is not set." + echo "Please set ANDROID_NDK_PATH to your Android NDK directory." + exit 1 + fi +fi + +ROOT_PWD=$(cd "$(dirname $0)" && pwd) +BUILD_DIR=${ROOT_PWD}/build/android + +echo "Building for Android..." +echo "NDK_PATH: ${ANDROID_NDK_PATH}" +echo "TARGET_ABI: ${TARGET_ABI}" +echo "BUILD_DIR: ${BUILD_DIR}" + +mkdir -p ${BUILD_DIR} +cd ${BUILD_DIR} + +cmake ../../src \ + -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_PATH}/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=${TARGET_ABI} \ + -DANDROID_PLATFORM=android-24 \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR=${ROOT_PWD}/../../../dependency/opencv/opencv-android-sdk-build/sdk/native/jni/abi-${TARGET_ABI} + +make -j4 + +echo "Build complete. Executable in ${BUILD_DIR}/blazepose_landmark_demo" diff --git a/examples/blazepose_landmark/cpp/build-linux.sh b/examples/blazepose_landmark/cpp/build-linux.sh new file mode 100755 index 0000000..cf69e45 --- /dev/null +++ b/examples/blazepose_landmark/cpp/build-linux.sh @@ -0,0 +1,168 @@ +#!/bin/bash +set -e + +# +# Copyright (C) 2024–2025 Amlogic, Inc. 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. +# + +usage() { + echo "Usage: $0 [-m ] [-a ] [-b ] [-s ] [-t ]" + echo " -m : Build mode: 'linux' or 'yocto' (default: linux)" + echo " -a : Target arch for linux mode (default: aarch64)" + echo " -b : Arch bits for yocto mode: 32 or 64 (default: 64)" + echo " -s : Yocto SDK root path (overrides YOCTO_SDK_ROOT env var)" + echo " -t : CMake toolchain file (overrides TOOLCHAIN_FILE env var)" + echo " -h : Show this help message" + exit 1 +} + +# Default values +BUILD_MODE=linux +TARGET_ARCH=aarch64 +ARCH_BITS=64 +CLI_SDK_ROOT="" +CLI_TOOLCHAIN_FILE="" + +# Parse arguments +while getopts 'm:a:b:s:t:h' opt; do + case "$opt" in + m) + BUILD_MODE=$OPTARG + ;; + a) + TARGET_ARCH=$OPTARG + ;; + b) + ARCH_BITS=$OPTARG + ;; + s) + CLI_SDK_ROOT=$OPTARG + ;; + t) + CLI_TOOLCHAIN_FILE=$OPTARG + ;; + h) + usage + ;; + *) + usage + ;; + esac +done + +ROOT_PWD=$(cd "$(dirname $0)" && pwd) + +# =========================================================================== +# Yocto build +# =========================================================================== +if [[ "${BUILD_MODE}" == "yocto" ]]; then + + if [[ "${ARCH_BITS}" != "32" && "${ARCH_BITS}" != "64" ]]; then + echo "Unsupported ARCH_BITS \"${ARCH_BITS}\". Must be 32 or 64." >&2 + exit 1 + fi + + # Configurable via environment variables (CLI args > env vars > defaults) + CMAKE_BIN="${CMAKE_BIN:-cmake}" + YOCTO_SDK_ROOT="${CLI_SDK_ROOT:-${YOCTO_SDK_ROOT:-/data/yuandian/tools/poky/4.0.20}}" + TOOLCHAIN_FILE="${CLI_TOOLCHAIN_FILE:-${TOOLCHAIN_FILE:-${ROOT_PWD}/../../cmake/yocto-toolchain.cmake}}" + + # Export variables for CMake + export YOCTO_SDK_ROOT + export ARCH_BITS + + BUILD_DIR="${ROOT_PWD}/build/yocto/${ARCH_BITS}" + + echo "==> Building Yocto ${ARCH_BITS}-bit" + echo " toolchain : ${TOOLCHAIN_FILE}" + echo " SDK root : ${YOCTO_SDK_ROOT}" + echo " BUILD_DIR : ${BUILD_DIR}" + + mkdir -p "${BUILD_DIR}" + rm -rf "${BUILD_DIR}" + + # Select OpenCV based on target architecture + if [[ "${ARCH_BITS}" == "32" ]]; then + OPENCV_DIR="${ROOT_PWD}/../../../dependency/opencv/opencv-linux-armhf/share/OpenCV" + else + OPENCV_DIR="${ROOT_PWD}/../../../dependency/opencv/opencv-linux-aarch64/share/OpenCV" + fi + + "${CMAKE_BIN}" \ + -S "${ROOT_PWD}/src" \ + -B "${BUILD_DIR}" \ + -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}" \ + -DYOCTO_SDK_ROOT="${YOCTO_SDK_ROOT}" \ + -DARCH_BITS="${ARCH_BITS}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DOpenCV_DIR="${OPENCV_DIR}" + + "${CMAKE_BIN}" --build "${BUILD_DIR}" --config Release + + # Strip (best-effort) + HOST_SYSROOT="${YOCTO_SDK_ROOT}/sysroots/x86_64-pokysdk-linux" + if [[ "${ARCH_BITS}" == "32" ]]; then + CROSS_TRIPLE="arm-poky-linux-gnueabi" + else + CROSS_TRIPLE="aarch64-poky-linux" + fi + STRIP_TOOL="${HOST_SYSROOT}/usr/bin/${CROSS_TRIPLE}/${CROSS_TRIPLE}-strip" + if [[ -x "${STRIP_TOOL}" ]]; then + "${STRIP_TOOL}" --strip-unneeded "${BUILD_DIR}/blazepose_landmark_demo" + else + echo "warning: strip tool not found; keeping debug info." >&2 + fi + + echo "Build complete. Executable in ${BUILD_DIR}/blazepose_landmark_demo" + exit 0 +fi + +# =========================================================================== +# Standard Linux cross-compile build +# =========================================================================== + +# Default to aarch64-linux-gnu if GCC_COMPILER is not set +GCC_COMPILER=${GCC_COMPILER:-aarch64-linux-gnu} + +# Set compilers +export CC=${GCC_COMPILER}-gcc +export CXX=${GCC_COMPILER}-g++ + +# Validate compiler +if ! command -v ${CC} &> /dev/null; then + echo "Error: Compiler ${CC} not found." + echo "Please set GCC_COMPILER environment variable to your cross-compiler path prefix." + echo "Example: export GCC_COMPILER=/path/to/toolchain/bin/aarch64-linux-gnu" + exit 1 +fi + +BUILD_DIR=${ROOT_PWD}/build/linux + +echo "Building for Linux..." +echo "COMPILER: ${CC}" +echo "TARGET_ARCH: ${TARGET_ARCH}" +echo "BUILD_DIR: ${BUILD_DIR}" + +mkdir -p ${BUILD_DIR} +cd ${BUILD_DIR} + +cmake ../../src \ + -DCMAKE_SYSTEM_NAME=Linux \ + -DCMAKE_SYSTEM_PROCESSOR=${TARGET_ARCH} \ + -DCMAKE_BUILD_TYPE=Release + +make -j4 + +echo "Build complete. Executable in ${BUILD_DIR}/blazepose_landmark_demo" diff --git a/examples/blazepose_landmark/cpp/src/CMakeLists.txt b/examples/blazepose_landmark/cpp/src/CMakeLists.txt new file mode 100755 index 0000000..f2a7752 --- /dev/null +++ b/examples/blazepose_landmark/cpp/src/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.10...3.27) +project(blazepose_landmark_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(blazepose_landmark_demo + main.cpp + postprocess.cpp + postprocess.h + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(blazepose_landmark_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} +) diff --git a/examples/blazepose_landmark/cpp/src/main.cpp b/examples/blazepose_landmark/cpp/src/main.cpp new file mode 100755 index 0000000..47cf518 --- /dev/null +++ b/examples/blazepose_landmark/cpp/src/main.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "postprocess.h" +#include "model_loader.h" + +const std::string DEFAULT_OUTPUT_PATH = "./result.jpg"; +const int MODEL_INPUT_WIDTH = 256; +const int MODEL_INPUT_HEIGHT = 256; +const float SCORE_THRESHOLD = 0.5f; + +int main(int argc, char **argv) +{ + std::string model_path; + std::string image_path; + + if (argc != 3) + { + printf("%s \n", argv[0]); + return -1; + } + + if (argc > 1) + model_path = argv[1]; + if (argc > 2) + image_path = argv[2]; + + std::cout << "Blazepose Detect Demo" << std::endl; + std::cout << "Model: " << model_path << std::endl; + std::cout << "Image: " << image_path << std::endl; + std::cout << "Output: " << DEFAULT_OUTPUT_PATH << std::endl; + + // 1. Load Image + cv::Mat img = cv::imread(image_path); + if (img.empty()) + { + std::cerr << "Failed to load image from " << image_path << std::endl; + return -1; + } + // Load detections + // n * 13 detections + // image_path -> txt_path + std::vector> detections; + + std::string txt_path = image_path.substr(0, image_path.find_last_of('.')); + txt_path += ".txt"; + std::ifstream ifs(txt_path); + for (std::string line; std::getline(ifs, line);) + { + std::istringstream iss(line); + std::vector det; + float val; + while (iss >> val) + det.push_back(val); + if (!det.empty()) + detections.push_back(det); + } + + // 2. Initialize Network + void *context = init_network(model_path.c_str()); + if (!context) + { + std::cerr << "Failed to initialize network." << std::endl; + return -1; + } + + // 3. Preprocess + auto start_time = std::chrono::high_resolution_clock::now(); + + auto [preprocessed, affine] = preprocess(img, detections, std::make_tuple(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH)); + + // Quantize to int16 (model expects quantized input) + cv::Mat quantized_img = quantize_input(preprocessed, 0.000030518509447574615f); + + // 4. Set input and run inference + nn_input inData; + memset(&inData, 0, sizeof(nn_input)); + inData.input_type = BINARY_RAW_DATA; + inData.input = quantized_img.data; + inData.input_index = 0; + inData.size = quantized_img.total() * quantized_img.elemSize(); + + if (aml_module_input_set(context, &inData) != 0) + { + std::cerr << "Failed to set input." << std::endl; + uninit_network(context); + return -1; + } + + aml_output_config_t outconfig; + memset(&outconfig, 0, sizeof(aml_output_config_t)); + outconfig.typeSize = sizeof(aml_output_config_t); + outconfig.format = AML_OUTDATA_FLOAT32; + + nn_output *outdata = (nn_output *)aml_module_output_get(context, outconfig); + if (!outdata) + { + std::cerr << "Failed to run network." << std::endl; + uninit_network(context); + return -1; + } + + // 5. Postprocess + std::vector landmarks = postprocess(outdata, affine); + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration inference_time = end_time - start_time; + + std::cout << "Inference time: " << inference_time.count() << " ms" << std::endl; + std::cout << "Landmarks: " << landmarks.size() << std::endl; + + // 6. Draw and Save + cv::Mat result_img = draw_landmarks(img, landmarks, SCORE_THRESHOLD); + cv::imwrite(DEFAULT_OUTPUT_PATH, result_img); + std::cout << "Result saved to " << DEFAULT_OUTPUT_PATH << std::endl; + + // 7. Cleanup + uninit_network(context); + + return 0; +} diff --git a/examples/blazepose_landmark/cpp/src/postprocess.cpp b/examples/blazepose_landmark/cpp/src/postprocess.cpp new file mode 100755 index 0000000..ee67d09 --- /dev/null +++ b/examples/blazepose_landmark/cpp/src/postprocess.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" +#include +#include +#include +#include + +#define LOGI(...) \ + do \ + { \ + printf(__VA_ARGS__); \ + printf("\n"); \ + } while (0) +#define LOGE(...) \ + do \ + { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) + +// SHOW class names (1 classes) +const char *SHOW_CLASSES[1] = {"lm"}; + +inline float sigmoid(float x) +{ + return 1.0f / (1.0f + std::exp(-x)); +} + +struct ROI +{ + float x_center; + float y_center; + float scale; + float theta; +}; + +ROI detection_to_roi(const std::vector &detection, int kp1 = 0, int kp2 = 1) +{ + float theta0 = 90.f * M_PI / 180.f; + float dscale = 1.1f; // 1.0 * 256 / 224; // 1.1f; + float dy = 0.f; + + float x_center = detection[4 + 2 * kp1]; + float y_center = detection[4 + 2 * kp1 + 1]; + float x1 = detection[4 + 2 * kp2]; + float y1 = detection[4 + 2 * kp2 + 1]; + + float roi_scale = std::sqrt((x_center - x1) * (x_center - x1) + (y_center - y1) * (y_center - y1)) * 2.f; + y_center += dy * roi_scale; + roi_scale *= dscale; + + float theta = std::atan2(detection[4 + 2 * kp1 + 1] - detection[4 + 2 * kp2 + 1], detection[4 + 2 * kp1] - detection[4 + 2 * kp2]) - theta0; + + return {x_center, y_center, roi_scale, theta}; +} + +cv::Mat extract_roi(cv::Mat &frame, const ROI &roi, int resolution, cv::Mat &affine) +{ + cv::Point2f src_pts[3]; + src_pts[0] = cv::Point2f(-roi.scale / 2.f, -roi.scale / 2.f); // will map to (0,0) + src_pts[1] = cv::Point2f(-roi.scale / 2.f, roi.scale / 2.f); // will map to (0,res-1) + src_pts[2] = cv::Point2f(roi.scale / 2.f, -roi.scale / 2.f); // will map to (res-1,0) + float cos_theta = std::cos(roi.theta); + float sin_theta = std::sin(roi.theta); + for (int i = 0; i < 3; i++) + { + float x = src_pts[i].x; + float y = src_pts[i].y; + src_pts[i].x = roi.x_center + x * cos_theta - y * sin_theta; + src_pts[i].y = roi.y_center + x * sin_theta + y * cos_theta; + } + cv::Point2f dst_pts[3] = { + cv::Point2f(0.f, 0.f), + cv::Point2f(0.f, resolution - 1.f), + cv::Point2f(resolution - 1.f, 0.f)}; + + cv::Mat roi_img; + cv::Mat M = cv::getAffineTransform(src_pts, dst_pts); + cv::invertAffineTransform(M, affine); + cv::warpAffine(frame, roi_img, M, cv::Size(resolution, resolution)); + + return roi_img; +} + +std::tuple preprocess(cv::Mat img, std::vector> &detections, std::tuple new_shape) +{ + cv::Mat img_rgb; + + if (img.empty()) + { + LOGE("Preprocess received empty image"); + return {}; + } + + // Convert to RGB + if (img.channels() == 4) + cv::cvtColor(img, img_rgb, cv::COLOR_RGBA2RGB); + else if (img.channels() == 3) + cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB); + else + img_rgb = img.clone(); + + ROI roi = detection_to_roi(detections[0]); // get the first bounding box + cv::Mat affine; + cv::Mat roi_img = extract_roi(img_rgb, roi, IMAGE_SIZE, affine); + + cv::Mat img_float; + roi_img.convertTo(img_float, CV_32F, 1.0 / 255.0); + + return std::make_tuple(img_float, affine); +} + +cv::Mat quantize_input(const cv::Mat &float_img, float scale, int16_t zero_point) +{ + if (float_img.empty() || float_img.type() != CV_32FC3) + { + LOGE("quantize_input: Invalid input image (must be CV_32FC3)"); + return cv::Mat(); + } + + cv::Mat quantized_img(float_img.rows, float_img.cols, CV_16SC3); + const float *src_ptr = (const float *)float_img.data; + int16_t *dst_ptr = (int16_t *)quantized_img.data; + + int total_elements = float_img.total() * float_img.channels(); + // for (int i = 0; i < total_elements; ++i) + // { + // dst_ptr[i] = static_cast(std::round(src_ptr[i] / scale + zero_point)); + // } + for (int i = 0; i < total_elements; ++i) + { + int32_t q = static_cast(std::round(src_ptr[i] / scale)); + q = std::max(-32768, std::min(32767, q)); + dst_ptr[i] = static_cast(q); + } + + return quantized_img; +} + +void blazepose_postprocess(const float *landmarks, float *normalized_landmarks) +{ + if (!landmarks || !normalized_landmarks) + return; + + for (int j = 0; j < NUM_LANDMARKS; j++) + { + float x = landmarks[j * LANDMARK_FEATURE_DIM + 0] / IMAGE_SIZE; + float y = landmarks[j * LANDMARK_FEATURE_DIM + 1] / IMAGE_SIZE; + float z = landmarks[j * LANDMARK_FEATURE_DIM + 2] / IMAGE_SIZE; + float visibility = landmarks[j * LANDMARK_FEATURE_DIM + 3]; + float presence = landmarks[j * LANDMARK_FEATURE_DIM + 4]; + + float score = sigmoid(fminf(visibility, presence)); + normalized_landmarks[j * LANDMARK_OUT_DIM + 0] = x; + normalized_landmarks[j * LANDMARK_OUT_DIM + 1] = y; + normalized_landmarks[j * LANDMARK_OUT_DIM + 2] = z; + normalized_landmarks[j * LANDMARK_OUT_DIM + 3] = score; + } +} + +/** + * Denormalize landmarks: map normalized coordinates back to original image using affine + * @param landmarks Input/Output: [NUM_LANDMARKS * LANDMARK_OUT_DIM], first three dimensions are x, y, z + * @param affine Input: [2 x 3] affine matrix (CV_32F) + */ +void blazepose_denorm_landmarks(float *landmarks, const cv::Mat &affine) +{ + if (!landmarks || affine.empty() || affine.rows != 2 || affine.cols != 3) + { + return; + } + + const double *a = affine.ptr(); + double a00 = a[0], a01 = a[1], a02 = a[2]; + double a10 = a[3], a11 = a[4], a12 = a[5]; + for (int j = 0; j < NUM_LANDMARKS; j++) + { + float *p = landmarks + j * LANDMARK_OUT_DIM; + // scale to input resolution + float x = p[0] * IMAGE_SIZE; + float y = p[1] * IMAGE_SIZE; + float z = p[2] * IMAGE_SIZE; + + // apply affine transform + float new_x = a00 * x + a01 * y + a02; + float new_y = a10 * x + a11 * y + a12; + + p[0] = new_x; + p[1] = new_y; + p[2] = z; + + } +} + +std::vector postprocess(nn_output *outdata, const cv::Mat &affine) +{ + // keep all outputs, even if unused + float *world_landmarks = (float *)outdata->out[0].buf; + float *heatmap = (float *)outdata->out[1].buf; + float *flags = (float *)outdata->out[2].buf; + float *landmarks = (float *)outdata->out[4].buf; + + float *normalized_landmarks = + new float[NUM_LANDMARKS * LANDMARK_OUT_DIM](); + + blazepose_postprocess(landmarks, normalized_landmarks); + + // refine_landmark_from_heatmap(normalized_landmarks, 39, heatmap, 64, 64); + + blazepose_denorm_landmarks(normalized_landmarks, affine); + + std::vector pose_res; + pose_res.reserve(1); + + BlazePoseLandmark pose; + pose.landmarks.resize(NUM_LANDMARKS); + + for (int i = 0; i < NUM_LANDMARKS; ++i) + { + int base = i * LANDMARK_OUT_DIM; + + double x = normalized_landmarks[base + 0]; // x + double y = normalized_landmarks[base + 1]; // y + double z = normalized_landmarks[base + 2]; // z + double score = normalized_landmarks[base + 3]; // score + + pose.landmarks[i] = {x, y, z, score}; + } + + pose_res.push_back(pose); + + delete[] normalized_landmarks; + normalized_landmarks = nullptr; + + return pose_res; +} + +static const std::vector> POSE_CONNECTIONS = { + // Face + {0, 1}, + {1, 2}, + {2, 3}, + {3, 7}, + {0, 4}, + {4, 5}, + {5, 6}, + {6, 8}, + // Mouth + {9, 10}, + // Shoulders + {11, 12}, + // Right arm + {11, 13}, + {13, 15}, + {15, 17}, + {15, 19}, + {15, 21}, + {17, 19}, + // Left arm + {12, 14}, + {14, 16}, + {16, 18}, + {16, 20}, + {16, 22}, + {18, 20}, + // Torso + {11, 23}, + {12, 24}, + {23, 24}, + // Right leg + {23, 25}, + {25, 27}, + {27, 29}, + {27, 31}, + {29, 31}, + // Left leg + {24, 26}, + {26, 28}, + {28, 30}, + {28, 32}, + {30, 32}}; + +cv::Mat draw_landmarks(cv::Mat image, const std::vector &landmarks, float score_threshold) +{ + cv::Mat out = image.clone(); + + for (const auto &lm : landmarks) + { + const auto &lms = lm.landmarks; + + for (size_t i = 0; i < lms.size(); ++i) + { + int x = static_cast(lms[i][0]); + int y = static_cast(lms[i][1]); + double v = lms[i][3]; + + if (v < score_threshold) + continue; + + cv::circle(out, cv::Point(x, y), 3, cv::Scalar(0, 255, 0), -1); + } + + for (const auto &conn : POSE_CONNECTIONS) + { + int i0 = conn.first; + int i1 = conn.second; + + if (i0 >= lms.size() || i1 >= lms.size()) + continue; + + if (lms[i0][3] < score_threshold || lms[i1][3] < score_threshold) + continue; + + cv::Point p0(static_cast(lms[i0][0]), static_cast(lms[i0][1])); + cv::Point p1(static_cast(lms[i1][0]), static_cast(lms[i1][1])); + + cv::line(out, p0, p1, cv::Scalar(255, 0, 0), 2); + } + } + + return out; +} diff --git a/examples/blazepose_landmark/cpp/src/postprocess.h b/examples/blazepose_landmark/cpp/src/postprocess.h new file mode 100755 index 0000000..3d8d1e6 --- /dev/null +++ b/examples/blazepose_landmark/cpp/src/postprocess.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef _AMLNN_BLAZEPOSE_LANDMARK_POSTPROCESS_H_ +#define _AMLNN_BLAZEPOSE_LANDMARK_POSTPROCESS_H_ + +#include +#include +#include +#include + +#include "model_loader.h" + +#define NUM_LANDMARKS 33 +#define LANDMARK_OUT_DIM 4 +#define LANDMARK_FEATURE_DIM 5 +#define IMAGE_SIZE 256 +// BlazePoseLandmark result structure + +struct BlazePoseLandmark +{ + std::vector> landmarks; // [N][x,y,z,v] +}; + +// Preprocess image with letterbox resizing +std::tuple preprocess(cv::Mat img, std::vector> &detections, std::tuple new_shape); + +// Quantize float32 image to int8 for model input +cv::Mat quantize_input(const cv::Mat &float_img, float scale = 0.000030518509447574615f, int16_t zero_point = 0); + +// Postprocess blazepose_landmark outputs with DFL decoding +std::vector postprocess(nn_output *outdata, const cv::Mat &affine); + +// Draw detections on image +cv::Mat draw_landmarks(cv::Mat image, const std::vector &landmarks, float score_threshold = 0.5); + +#endif // _AMLNN_BLAZEPOSE_LANDMARK_POSTPROCESS_H_ diff --git a/examples/blazepose_landmark/model/.gitkeep b/examples/blazepose_landmark/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_landmark/model/adla_convert.sh b/examples/blazepose_landmark/model/adla_convert.sh new file mode 100755 index 0000000..957c1e8 --- /dev/null +++ b/examples/blazepose_landmark/model/adla_convert.sh @@ -0,0 +1,40 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + +# 1. $1: set ADLA_TOOL_PATH +# 2. $2: set target-platform +# for A311D2 target-platform is PRODUCT_PID0XA003 +# for S905X5 target-platform is PRODUCT_PID0XA005 +# Usage: ./adla_convert.sh pose_landmark_full.tflite /XXX/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + +model_path=$1 +ADLA_TOOL_PATH=$2 +target_platform=$3 + +echo "model_path:[$model_path]" +echo "ADLA_TOOL_PATH:[$ADLA_TOOL_PATH]" +echo "target-platform:[$target_platform]" + +adla_convert=${ADLA_TOOL_PATH}/bin/adla_convert + +$adla_convert --model-type tflite \ + --model $model_path \ + --inputs input_1 --input-shapes "256,256,3" \ + --quantize-dtype int16 \ + --source-file dataset_coco.txt \ + --channel-mean-value "0,0,0,255" \ + --target-platform $target_platform \ + --disable-per-channel false diff --git a/examples/blazepose_landmark/model/dataset_coco.txt b/examples/blazepose_landmark/model/dataset_coco.txt new file mode 100755 index 0000000..5e990aa --- /dev/null +++ b/examples/blazepose_landmark/model/dataset_coco.txt @@ -0,0 +1,50 @@ +../../../resource/coco_dataset/000000000139.jpg +../../../resource/coco_dataset/000000000285.jpg +../../../resource/coco_dataset/000000000632.jpg +../../../resource/coco_dataset/000000000724.jpg +../../../resource/coco_dataset/000000000776.jpg +../../../resource/coco_dataset/000000000785.jpg +../../../resource/coco_dataset/000000000802.jpg +../../../resource/coco_dataset/000000000872.jpg +../../../resource/coco_dataset/000000000885.jpg +../../../resource/coco_dataset/000000001000.jpg +../../../resource/coco_dataset/000000001268.jpg +../../../resource/coco_dataset/000000001296.jpg +../../../resource/coco_dataset/000000001353.jpg +../../../resource/coco_dataset/000000001425.jpg +../../../resource/coco_dataset/000000001490.jpg +../../../resource/coco_dataset/000000001503.jpg +../../../resource/coco_dataset/000000001532.jpg +../../../resource/coco_dataset/000000001584.jpg +../../../resource/coco_dataset/000000001675.jpg +../../../resource/coco_dataset/000000001761.jpg +../../../resource/coco_dataset/000000001818.jpg +../../../resource/coco_dataset/000000001993.jpg +../../../resource/coco_dataset/000000002006.jpg +../../../resource/coco_dataset/000000002149.jpg +../../../resource/coco_dataset/000000002153.jpg +../../../resource/coco_dataset/000000002157.jpg +../../../resource/coco_dataset/000000002261.jpg +../../../resource/coco_dataset/000000002299.jpg +../../../resource/coco_dataset/000000002431.jpg +../../../resource/coco_dataset/000000002473.jpg +../../../resource/coco_dataset/000000002532.jpg +../../../resource/coco_dataset/000000002587.jpg +../../../resource/coco_dataset/000000002592.jpg +../../../resource/coco_dataset/000000002685.jpg +../../../resource/coco_dataset/000000002923.jpg +../../../resource/coco_dataset/000000003156.jpg +../../../resource/coco_dataset/000000003255.jpg +../../../resource/coco_dataset/000000003501.jpg +../../../resource/coco_dataset/000000003553.jpg +../../../resource/coco_dataset/000000003661.jpg +../../../resource/coco_dataset/000000003845.jpg +../../../resource/coco_dataset/000000003934.jpg +../../../resource/coco_dataset/000000004134.jpg +../../../resource/coco_dataset/000000004395.jpg +../../../resource/coco_dataset/000000004495.jpg +../../../resource/coco_dataset/000000004765.jpg +../../../resource/coco_dataset/000000004795.jpg +../../../resource/coco_dataset/000000005001.jpg +../../../resource/coco_dataset/000000005037.jpg +../../../resource/coco_dataset/000000005060.jpg diff --git a/examples/blazepose_landmark/model/test.png b/examples/blazepose_landmark/model/test.png new file mode 100755 index 0000000..eee4554 Binary files /dev/null and b/examples/blazepose_landmark/model/test.png differ diff --git a/examples/blazepose_landmark/py/.gitkeep b/examples/blazepose_landmark/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/blazepose_landmark/py/balzepose_landmark.py b/examples/blazepose_landmark/py/balzepose_landmark.py new file mode 100755 index 0000000..163139d --- /dev/null +++ b/examples/blazepose_landmark/py/balzepose_landmark.py @@ -0,0 +1,301 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + +import numpy as np +import os +import glob +import argparse +import cv2 +from pathlib import Path +from amlnnlite.api import AMLNNLite +import math + +def preprocess(img_path, detections, new_shape=(256, 256), data_format='NCHW', s=0.003921568859368563, zp=-128): + original_img = cv2.imread(str(img_path)) + if original_img is None: + raise ValueError(f"can't read image: {img_path}") + + im_h, im_w, _ = original_img.shape + if detections.shape[0] > 0: + detections = detections[:1, :] + else: + raise ValueError("No detections input, please run blazepose_detect and generate the detections first.") + + x_center, y_center = detections[4:6] + x_scale, y_scale = detections[6:8] + print(f"---------center {x_center}, {y_center}, x_scale {x_scale}, y_scale {y_scale}") + + box_size = (((x_scale - x_center) ** 2 + (y_scale - y_center) ** 2) ** 0.5) * 2 + box_size *= 1.25 + + angle = (np.pi * 90 / 180) - math.atan2(-(y_scale - y_center), x_scale - x_center) + rotation = angle - 2 * np.pi * np.floor((angle - (-np.pi)) / (2 * np.pi)) + + rotated_rect = ((x_center, y_center), (box_size, box_size), rotation * 180. / np.pi) + pts1 = cv2.boxPoints(rotated_rect) + + h, w = new_shape + pts2 = np.float32([[0, h], [0, 0], [w, 0], [w, h]]) + M = cv2.getPerspectiveTransform(pts1, pts2) + processed_img = cv2.warpPerspective(original_img, M, (w, h), flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE) + + rgb_img = cv2.cvtColor(processed_img, cv2.COLOR_BGR2RGB) + normalized_img = rgb_img.astype(np.float32) / 255.0 + + if data_format == 'NCHW': + # HWC -> CHW -> BCHW (ONNX default format) + input_tensor = np.transpose(normalized_img, (2, 0, 1)) + input_tensor = np.expand_dims(input_tensor, axis=0) + elif data_format == 'NHWC': + # HWC -> BHWC (TFLITE default format) + input_tensor = np.expand_dims(normalized_img, axis=0) + else: + raise ValueError(f"Unsupported data format: {data_format}. Only 'NCHW' and 'NHWC' are supported.") + + # Quantize to int16 + input_tensor = np.round(input_tensor / s + zp).astype(np.int16) + + return input_tensor, original_img, [x_center, y_center, rotation, box_size] + +def tensor_to_landmark(landmarks): + num_landmarks = 39 + num_dimensions = landmarks.shape[1] # num_landmarks + output = landmarks.reshape(-1, num_landmarks, num_dimensions).copy() + if num_dimensions > 3: + output[..., 3:5] = 1.0 / (1.0 + np.exp(-output[..., 3:5])) + return output + +def refine_landmark(landmarks, heatmap):#39*5, 64*64*39 + min_confidence = 0.5 + kernel_size = 9 + offset = kernel_size + + hm_h, hm_w, _ = heatmap.shape + + for i, lm in enumerate(landmarks): + col = int(lm[0] * hm_w) + row = int(lm[1] * hm_h) + + if not (0 <= col < hm_w and 0 <= row < hm_h): + continue + + c0 = max(0, col - offset) + c1 = min(hm_w, col + offset + 1) + r0 = max(0, row - offset) + r1 = min(hm_h, row + offset + 1) + + val_sum = 0.0 + weighted_col = 0.0 + weighted_row = 0.0 + max_conf = 0.0 + + for r in range(r0, r1): + for c in range(c0, c1): + conf = 1.0 / (1.0 + np.exp(-heatmap[r, c, i])) + val_sum += conf + max_conf = max(max_conf, conf) + weighted_col += c * conf + weighted_row += r * conf + + if max_conf >= min_confidence and val_sum > 0: + lm[0] = weighted_col / (hm_w * val_sum) + lm[1] = weighted_row / (hm_h * val_sum) + + return landmarks + +def postprocess(outputs, params, data_format='NCHW'): + x_center, y_center, rotation, box_size = params + flag, landmark_tensor, world_landmark_tensor, segment, heatmap_tensor = [], [], [], [], [] + for out in outputs: + if len(out.shape) == 2: + if out.shape == (1, 1) or out.shape == (1,): + flag = out + elif out.shape[1] == 195: + landmark_tensor = out + elif out.shape[1] == 117: + world_landmark_tensor = out + elif len(out.shape) == 4 and out.shape[3] == 1 and out.shape[1] == 256: + segment = out + elif len(out.shape) == 4 and out.shape[1] == 64 and out.shape[3] == 39: + heatmap_tensor = out + + raw_landmarks = tensor_to_landmark(landmark_tensor) + all_world_landmarks = tensor_to_landmark(world_landmark_tensor) + + h = w = 256 + raw_landmarks[:, :, 0] = raw_landmarks[:, :, 0] / w + raw_landmarks[:, :, 1] = raw_landmarks[:, :, 1] / h + raw_landmarks[:, :, 2] = raw_landmarks[:, :, 2] / w + + # Refines landmarks with the heatmap tensor. + all_landmarks = refine_landmark(raw_landmarks[0], heatmap_tensor[0]) + all_world_landmarks = all_world_landmarks[0] + + print(f"rotation {rotation}") + cosa = math.cos(rotation) + sina = math.sin(rotation) + for landmark in all_landmarks: + x = landmark[0] - 0.5 + y = landmark[1] - 0.5 + landmark[0] = ((cosa * x - sina * y) * box_size + x_center) + landmark[1] = ((sina * x + cosa * y) * box_size + y_center) + landmark[2] = landmark[2] * box_size + + # Projects the world landmarks from the letterboxed ROI to the full image. + for landmark in all_world_landmarks: + x = landmark[0] + y = landmark[1] + landmark[0] = cosa * x - sina * y + landmark[1] = sina * x + cosa * y + + return all_landmarks + +def get_class_color(class_id): + import colorsys + hue = (class_id * 137.508) % 360 + rgb = colorsys.hsv_to_rgb(hue/360.0, 0.8, 0.9) + bgr = (int(rgb[2]*255), int(rgb[1]*255), int(rgb[0]*255)) + return bgr + +POSE_CONNECTIONS = [ + # Face + (0, 1),(1, 2),(2, 3),(3, 7), + (0, 4),(4, 5),(5, 6),(6, 8), + # Mouth + (9, 10), + # Shoulders + (11, 12), + # Right arm + (11, 13), (13, 15), (15, 17), (15, 19), (15, 21), (17, 19), + # Left arm + (12, 14), (14, 16), (16, 18), (16, 20), (16, 22), (18, 20), + # Torso + (11, 23), (12, 24), (23, 24), + # Right leg + (23, 25), (25, 27), (27, 29), (27, 31), (29, 31), + # Left leg + (24, 26), (26, 28), (28, 30), (28, 32), (30, 32) +] + +def draw_landmarks(img, landmarks, save_path, score_threshold=0.5): + result_img = img.copy() + for lm in landmarks: + lms = lm.landmarks + + for point in lms: + x, y, score = int(point[0]), int(point[1]), point[3] + if score < score_threshold: + continue + cv2.circle(result_img, (x, y), 3, (0, 255, 0), -1) + + for i0, i1 in POSE_CONNECTIONS: + if i0 >= len(lms) or i1 >= len(lms): + continue + if lms[i0][3] < score_threshold or lms[i1][3] < score_threshold: + continue + p0 = (int(lms[i0][0]), int(lms[i0][1])) + p1 = (int(lms[i1][0]), int(lms[i1][1])) + cv2.line(result_img, p0, p1, (255, 0, 0), 2) + + cv2.imwrite(save_path, result_img) + return result_img + +def read_detections_from_txt(txt_path): + with open(txt_path, "r") as f: + detections = [[float(x) for x in line.split()] for line in f if line.strip()] + return np.array(detections, dtype=np.float32) + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--model-path', default='./blazepose_landmark_int8_A311D2.adla') + parser.add_argument('--run-cycles', default= 1, type=int) + args = parser.parse_args() + + # Initialize AMLNNLite + amlnn = AMLNNLite() + amlnn.config( + model_path=args.model_path, # Model file path, Support ADLA and quantized TFlite models + run_cycles=args.run_cycles + ) + amlnn.init() + + # Find all image files in the 01_export_model directory + image_dir = "./" + image_extensions = ["*.jpg", "*.jpeg", "*.png", "*.bmp"] + image_files = [] + for ext in image_extensions: + image_files.extend(glob.glob(os.path.join(image_dir, ext))) + image_files.extend(glob.glob(os.path.join(image_dir, ext.upper()))) + + if not image_files: + print("No image files found in", image_dir) + amlnn.uninit() + return + + print(f"Found {len(image_files)} image files to process:") + for img_file in image_files: + print(f" - {os.path.basename(img_file)}") + print() + + # Process each image + for i, image_path in enumerate(image_files, 1): + txt_path = os.path.splitext(image_path)[0] + ".txt" + detections = read_detections_from_txt(txt_path=txt_path) + print(f"=" * 60) + print(f"Processing image {i}/{len(image_files)}: {os.path.basename(image_path)}") + print(f"=" * 60) + + try: + # Preprocess input + input_tensor, original_img, params = preprocess(image_path, detections, new_shape=(256, 256), data_format='NHWC', s=0.000030518509447574615, zp=0) + + # Run inference + outputs = amlnn.inference(inputs=[input_tensor]) + + # Postprocess results + landmarks = postprocess(outputs, params, data_format='NHWC') + + # Print detection results + if landmarks: + print(f" Detected {len(landmarks)} objects:") + for i, lm in enumerate(landmarks, 1): + print(f" {i}. {lm['class_name']} ({lm['confidence']:.2f})") + else: + print(" No objects detected") + + # Save result image + model_name = Path(args.model_path).stem + result_dir = f"{model_name}_result" + os.makedirs(result_dir, exist_ok=True) + img_name = Path(image_path).stem + save_path = os.path.join(result_dir, f"{img_name}_result.jpg") + draw_landmarks(original_img, landmarks, str(save_path), score_threshold=0.5) + print(f" Result saved to: {save_path}") + + except Exception as e: + print(f"Error processing {os.path.basename(image_path)}: {e}") + + print() + + # Optional visualization + amlnn.visualize() + + # Release resources + amlnn.uninit() + +if __name__ == "__main__": + main() + diff --git a/examples/blazepose_landmark/result.jpg b/examples/blazepose_landmark/result.jpg new file mode 100755 index 0000000..78c3d81 Binary files /dev/null and b/examples/blazepose_landmark/result.jpg differ diff --git a/examples/clip/README.md b/examples/clip/README.md index f8f5ebd..c966455 100644 --- a/examples/clip/README.md +++ b/examples/clip/README.md @@ -1,159 +1,159 @@ -# CLIP - -## 1. Overview - -This demo demonstrates how to run CLIP (Contrastive Language-Image Pre-Training) image-text matching using AMLNNLite. The CLIP model consists of two parts: a vision encoder and a text encoder, which work together to compute similarity between images and text descriptions. - -## 2. Model Download - -TO DO - -## 3. Model Conversion - -TO DO - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/clip/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android_arm64-v8a/clip_demo`. - -#### 2. Run - -```bash -# Push executable and resources to device -adb push build/android_arm64-v8a/clip_demo /data/local/tmp/ -adb push model/vision_model_int8_S905X5.adla /data/local/tmp/ -adb push model/text_model_int8_S905X5.adla /data/local/tmp/ -adb push tokenizer_path/ /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x clip_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./clip_demo [--profiling] -./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path/ -``` - -The program will prompt for image paths and text descriptions interactively. Enter the path to an image file, then enter comma-separated text descriptions (or `skip` to use defaults). Type `exit` to quit. - -**Argument Descriptions:** - -| Argument | Description | -| -------------- | ------------------------------------------------------------ | -| vision_model | Path to vision encoder .adla model (required) | -| text_model | Path to text encoder .adla model (required) | -| tokenizer_path | Path to directory containing `vocab.json` and `merges.txt` (required) | -| --profiling | Enable performance profiling output (optional) | - -**Note:** The `tokenizer_path` should contain `vocab.json` and `merges.txt` files from the CLIP tokenizer (e.g., from `openai/clip-vit-base-patch32`). - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `Pillow`, `transformers`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy Pillow transformers amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -python clip.py \ - --vision-model ./vision_model_int8_S905X5.adla \ - --text-model ./text_model_int8_S905X5.adla \ - --tokenizer-dir ./tokenizer_path \ - --image-path ./000000004505.jpg \ - --texts "a red handbag" "a blue jacket" "a red bus" -``` - -**Interactive Mode (Recommended):** - -If you don't provide `--image-path`, the program will run in interactive mode: - -```bash -python clip.py \ - --vision-model ./vision_model_int8_S905X5.adla \ - --text-model ./text_model_int8_S905X5.adla \ - --tokenizer-dir ./tokenizer_path -``` - -The program will prompt for image paths and text descriptions. Enter an image path to process, then enter comma-separated texts to compare. Type `exit` to quit. - -**Argument Descriptions:** - -| Argument | Description | -| ---------------- | ------------------------------------------------------------ | -| --vision-model | Path to vision encoder .adla model (required) | -| --text-model | Path to text encoder .adla model (required) | -| --tokenizer-dir | Path to CLIPTokenizer directory (required) | -| --image-path | Path to input image (.jpg, .png) - optional, will prompt if not provided | -| --texts | List of text descriptions to compare (space-separated) | -| --max-len | Maximum token sequence length, default is 64 | -| --logit-scale | Logit scale factor, default is 100.0 | - -**Note:** The `--tokenizer-dir` should point to the directory containing the CLIPTokenizer files. You can use a Hugging Face model ID (e.g., `openai/clip-vit-base-patch32`) or a local directory. - -## 5. Results - -**Performance Feedback** - -By using the `--profiling` flag (C++) or setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: -- Hardware Information: System and ADLA library versions. -- Model Overview: Basic input/output configurations. -- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. - -**Interactive Mode Example:** - -```bash -$ ./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path - -[Info] Models initialized successfully. - -============================================================ -[Info] Image Path (or 'exit' to quit): -000000004505.jpg -[Info] Enter text descriptions (comma-separated, or 'skip' for defaults): -a red handbag, a blue jacket, a red bus - -[Info] Processing image: 000000004505.jpg -[Info] Image embedding size: 512 -[Info] Processing 3 text(s)... -[Info] Text embeddings size: 3 x 512 - -============================================================ -CLIP Image-Text Matching Results -============================================================ -Image: 000000004505.jpg -logit_scale: 100.000000 ------------------------------------------------------------- -[1] prob=0.999975 sim=0.327895 text='a red bus' -[2] prob=0.000016 sim=0.217690 text='a red handbag' -[3] prob=0.000008 sim=0.211029 text='a blue jacket' -============================================================ - -============================================================ -[Info] Image Path (or 'exit' to quit): -exit -[Info] Exiting... -Free vision model memory. -Free text model memory. -[Info] Done. -``` +# CLIP + +## 1. Overview + +This demo demonstrates how to run CLIP (Contrastive Language-Image Pre-Training) image-text matching using AMLNNLite. The CLIP model consists of two parts: a vision encoder and a text encoder, which work together to compute similarity between images and text descriptions. + +## 2. Model Download + +TO DO + +## 3. Model Conversion + +TO DO + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/clip/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android_arm64-v8a/clip_demo`. + +#### 2. Run + +```bash +# Push executable and resources to device +adb push build/android_arm64-v8a/clip_demo /data/local/tmp/ +adb push model/vision_model_int8_S905X5.adla /data/local/tmp/ +adb push model/text_model_int8_S905X5.adla /data/local/tmp/ +adb push tokenizer_path/ /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x clip_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./clip_demo [--profiling] +./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path/ +``` + +The program will prompt for image paths and text descriptions interactively. Enter the path to an image file, then enter comma-separated text descriptions (or `skip` to use defaults). Type `exit` to quit. + +**Argument Descriptions:** + +| Argument | Description | +| -------------- | ------------------------------------------------------------ | +| vision_model | Path to vision encoder .adla model (required) | +| text_model | Path to text encoder .adla model (required) | +| tokenizer_path | Path to directory containing `vocab.json` and `merges.txt` (required) | +| --profiling | Enable performance profiling output (optional) | + +**Note:** The `tokenizer_path` should contain `vocab.json` and `merges.txt` files from the CLIP tokenizer (e.g., from `openai/clip-vit-base-patch32`). + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `Pillow`, `transformers`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy Pillow transformers amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python clip.py \ + --vision-model ./vision_model_int8_S905X5.adla \ + --text-model ./text_model_int8_S905X5.adla \ + --tokenizer-dir ./tokenizer_path \ + --image-path ./000000004505.jpg \ + --texts "a red handbag" "a blue jacket" "a red bus" +``` + +**Interactive Mode (Recommended):** + +If you don't provide `--image-path`, the program will run in interactive mode: + +```bash +python clip.py \ + --vision-model ./vision_model_int8_S905X5.adla \ + --text-model ./text_model_int8_S905X5.adla \ + --tokenizer-dir ./tokenizer_path +``` + +The program will prompt for image paths and text descriptions. Enter an image path to process, then enter comma-separated texts to compare. Type `exit` to quit. + +**Argument Descriptions:** + +| Argument | Description | +| ---------------- | ------------------------------------------------------------ | +| --vision-model | Path to vision encoder .adla model (required) | +| --text-model | Path to text encoder .adla model (required) | +| --tokenizer-dir | Path to CLIPTokenizer directory (required) | +| --image-path | Path to input image (.jpg, .png) - optional, will prompt if not provided | +| --texts | List of text descriptions to compare (space-separated) | +| --max-len | Maximum token sequence length, default is 64 | +| --logit-scale | Logit scale factor, default is 100.0 | + +**Note:** The `--tokenizer-dir` should point to the directory containing the CLIPTokenizer files. You can use a Hugging Face model ID (e.g., `openai/clip-vit-base-patch32`) or a local directory. + +## 5. Results + +**Performance Feedback** + +By using the `--profiling` flag (C++) or setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: +- Hardware Information: System and ADLA library versions. +- Model Overview: Basic input/output configurations. +- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. + +**Interactive Mode Example:** + +```bash +$ ./clip_demo vision_model_int8_S905X5.adla text_model_int8_S905X5.adla ./tokenizer_path + +[Info] Models initialized successfully. + +============================================================ +[Info] Image Path (or 'exit' to quit): +000000004505.jpg +[Info] Enter text descriptions (comma-separated, or 'skip' for defaults): +a red handbag, a blue jacket, a red bus + +[Info] Processing image: 000000004505.jpg +[Info] Image embedding size: 512 +[Info] Processing 3 text(s)... +[Info] Text embeddings size: 3 x 512 + +============================================================ +CLIP Image-Text Matching Results +============================================================ +Image: 000000004505.jpg +logit_scale: 100.000000 +------------------------------------------------------------ +[1] prob=0.999975 sim=0.327895 text='a red bus' +[2] prob=0.000016 sim=0.217690 text='a red handbag' +[3] prob=0.000008 sim=0.211029 text='a blue jacket' +============================================================ + +============================================================ +[Info] Image Path (or 'exit' to quit): +exit +[Info] Exiting... +Free vision model memory. +Free text model memory. +[Info] Done. +``` diff --git a/examples/clip/cpp/.gitkeep b/examples/clip/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/clip/cpp/src/CMakeLists.txt b/examples/clip/cpp/src/CMakeLists.txt index e0c5c0b..06ee703 100755 --- a/examples/clip/cpp/src/CMakeLists.txt +++ b/examples/clip/cpp/src/CMakeLists.txt @@ -1,38 +1,38 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(clip_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set 3rdparty path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -# Include directories for stb_image and json -# Note: code uses #include "stb_image.h" and #include "json.hpp" -include_directories(${3RDPARTY_DIR}/stb_image) -include_directories(${3RDPARTY_DIR}/json) - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -add_executable(${PROJECT_NAME} - main.cpp - model_invoke.cpp - pre_postprocess.cpp - clip_tokenizer.cpp -) - -target_link_libraries(${PROJECT_NAME} - ${AMLNN_LIBRARY} - dl - m -) - +cmake_minimum_required(VERSION 3.10...3.27) +project(clip_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +# Include directories for stb_image and json +# Note: code uses #include "stb_image.h" and #include "json.hpp" +include_directories(${3RDPARTY_DIR}/stb_image) +include_directories(${3RDPARTY_DIR}/json) + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +add_executable(${PROJECT_NAME} + main.cpp + model_invoke.cpp + pre_postprocess.cpp + clip_tokenizer.cpp +) + +target_link_libraries(${PROJECT_NAME} + ${AMLNN_LIBRARY} + dl + m +) + diff --git a/examples/clip/cpp/src/clip_process.h b/examples/clip/cpp/src/clip_process.h index 0d8c84c..7616d36 100755 --- a/examples/clip/cpp/src/clip_process.h +++ b/examples/clip/cpp/src/clip_process.h @@ -26,8 +26,8 @@ // Initialize network from file void* init_network_file(const char *model_path); -// Run vision model inference -std::vector run_vision_model(void* context, const std::vector& input_data); +// Run image model inference +std::vector run_image_model(void* context, const std::vector& input_data); // Run text model inference std::vector run_text_model(void* context, const std::vector& input_ids); diff --git a/examples/clip/cpp/src/main.cpp b/examples/clip/cpp/src/main.cpp index fd41793..d7c814b 100755 --- a/examples/clip/cpp/src/main.cpp +++ b/examples/clip/cpp/src/main.cpp @@ -33,7 +33,7 @@ struct ProfilingTimer { uint64_t init_start, init_end; uint64_t preprocess_start, preprocess_end; - uint64_t vision_infer_start, vision_infer_end; + uint64_t image_infer_start, image_infer_end; uint64_t text_infer_start, text_infer_end; }; @@ -71,10 +71,10 @@ std::vector parse_texts(const std::string& input) void print_usage(const char* prog_name) { - printf("Usage: %s [--profiling]\n", prog_name); + printf("Usage: %s [--profiling]\n", prog_name); printf("\n"); printf("Arguments:\n"); - printf(" vision_model: Path to vision model (.adla)\n"); + printf(" image_model: Path to image model (.adla)\n"); printf(" text_model: Path to text model (.adla)\n"); printf(" tokenizer_dir: Path to directory containing vocab.json and merges.txt\n"); printf(" --profiling: Enable performance profiling output (optional)\n"); @@ -96,7 +96,7 @@ int main(int argc, char ** argv) return -1; } - const char* vision_model_path = argv[1]; + const char* image_model_path = argv[1]; const char* text_model_path = argv[2]; const char* tokenizer_dir = argv[3]; @@ -119,11 +119,11 @@ int main(int argc, char ** argv) } // Initialize models - printf("[Info] Initializing vision model: %s\n", vision_model_path); + printf("[Info] Initializing image model: %s\n", image_model_path); timer.init_start = get_time_count(); - void* vision_context = init_network_file(vision_model_path); - if (vision_context == NULL) { - printf("[Error] Failed to initialize vision model.\n"); + void* image_context = init_network_file(image_model_path); + if (image_context == NULL) { + printf("[Error] Failed to initialize image model.\n"); return -1; } @@ -131,7 +131,7 @@ int main(int argc, char ** argv) void* text_context = init_network_file(text_model_path); if (text_context == NULL) { printf("[Error] Failed to initialize text model.\n"); - destroy_network(vision_context); + destroy_network(image_context); return -1; } timer.init_end = get_time_count(); @@ -218,14 +218,14 @@ int main(int argc, char ** argv) } timer.preprocess_end = get_time_count(); - // Run vision model - timer.vision_infer_start = get_time_count(); - std::vector image_embedding = run_vision_model(vision_context, image_input); + // Run image model + timer.image_infer_start = get_time_count(); + std::vector image_embedding = run_image_model(image_context, image_input); if (image_embedding.empty()) { - printf("[Error] Vision model inference failed.\n"); + printf("[Error] Image model inference failed.\n"); continue; } - timer.vision_infer_end = get_time_count(); + timer.image_infer_end = get_time_count(); // L2 normalize image embedding image_embedding = l2_normalize(image_embedding); @@ -264,7 +264,8 @@ int main(int argc, char ** argv) continue; } - printf("[Info] Text embeddings size: %zu x %zu\n", text_embeddings.size(), + printf("[Info] Text embeddings size: %zu x %zu\n", + text_embeddings.size(), text_embeddings.empty() ? 0 : text_embeddings[0].size()); // ==================== Compute Similarity ==================== @@ -302,11 +303,11 @@ int main(int argc, char ** argv) if (profiling) { uint64_t preprocess_time = (timer.preprocess_end - timer.preprocess_start) / 1000000; - uint64_t vision_time = (timer.vision_infer_end - timer.vision_infer_start) / 1000000; + uint64_t image_time = (timer.image_infer_end - timer.image_infer_start) / 1000000; uint64_t text_total_time = (timer.text_infer_end - timer.text_infer_start) / 1000000; printf("\n[Profiling]\n"); printf(" Image preprocess: %lums\n", preprocess_time); - printf(" Vision inference: %lums\n", vision_time); + printf(" Image inference: %lums\n", image_time); for (size_t i = 0; i < texts.size() && i < text_infer_times.size(); ++i) { printf(" Text inference[%zu]: %lums '%s'\n", i, text_infer_times[i], texts[i].c_str()); } @@ -316,9 +317,9 @@ int main(int argc, char ** argv) } // Cleanup - ret = destroy_network(vision_context); + ret = destroy_network(image_context); if (ret != 0) { - printf("[Error] Failed to destroy vision model.\n"); + printf("[Error] Failed to destroy image model.\n"); } ret = destroy_network(text_context); @@ -328,4 +329,4 @@ int main(int argc, char ** argv) printf("[Info] Done.\n"); return 0; -} +} \ No newline at end of file diff --git a/examples/clip/cpp/src/model_invoke.cpp b/examples/clip/cpp/src/model_invoke.cpp index 93e673c..1f441fb 100755 --- a/examples/clip/cpp/src/model_invoke.cpp +++ b/examples/clip/cpp/src/model_invoke.cpp @@ -27,9 +27,9 @@ #include "nn_sdk.h" // Global DMA config for models -static aml_memory_config_t vision_mem_config; -static aml_memory_data_t vision_mem_data; -static void* vision_context_flag = nullptr; +static aml_memory_config_t image_mem_config; +static aml_memory_data_t image_mem_data; +static void* image_context_flag = nullptr; static aml_memory_config_t text_mem_config; static aml_memory_data_t text_mem_data; @@ -48,7 +48,7 @@ void* init_network_file(const char *model_path) /* set omp, If you are considering high CPU usage during operation, you can turn off this api, set_openmp_opt_flag = false */ - aml_openmp_opt_t openmp_opt[] = + aml_openmp_opt_t openmp_opt[] = { { .operator_type = AML_Unknown, @@ -62,7 +62,7 @@ void* init_network_file(const char *model_path) config.forward_ctrl.softop_info.openmp_opt = openmp_opt; /* set neon */ - aml_neon_opt_t neon_opt[] = + aml_neon_opt_t neon_opt[] = { { .operator_type = AML_Unknown, @@ -84,10 +84,11 @@ void* init_network_file(const char *model_path) return qcontext; } -std::vector run_vision_model(void* qcontext, const std::vector& input_data) +std::vector run_image_model(void* qcontext, const std::vector& input_data) { int ret = 0; nn_input inData; + nn_output *outdata = NULL; aml_output_config_t outconfig; @@ -96,19 +97,19 @@ std::vector run_vision_model(void* qcontext, const std::vector& in inData.size = input_data.size() * sizeof(float); // Use DMA - if (!vision_context_flag) { - vision_mem_config.cache_type = AML_WITH_CACHE; - vision_mem_config.memory_type = AML_VIRTUAL_ADDR; - vision_mem_config.direction = AML_MEM_DIRECTION_READ_WRITE; - vision_mem_config.index = 0; - vision_mem_config.mem_size = inData.size; - aml_util_mallocBuffer(qcontext, &vision_mem_config, &vision_mem_data); - aml_util_swapExternalInputBuffer(qcontext, &vision_mem_config, &vision_mem_data); - vision_context_flag = qcontext; + if (!image_context_flag) { + image_mem_config.cache_type = AML_WITH_CACHE; + image_mem_config.memory_type = AML_VIRTUAL_ADDR; + image_mem_config.direction = AML_MEM_DIRECTION_READ_WRITE; + image_mem_config.index = 0; + image_mem_config.mem_size = inData.size; + aml_util_mallocBuffer(qcontext, &image_mem_config, &image_mem_data); + aml_util_swapExternalInputBuffer(qcontext, &image_mem_config, &image_mem_data); + image_context_flag = qcontext; } inData.input_type = INPUT_DMA_DATA; - memcpy(vision_mem_data.viraddr, input_data.data(), vision_mem_config.mem_size); + memcpy(image_mem_data.viraddr, input_data.data(), image_mem_config.mem_size); inData.input = NULL; memset(&outconfig, 0, sizeof(aml_output_config_t)); @@ -117,7 +118,7 @@ std::vector run_vision_model(void* qcontext, const std::vector& in outdata = (nn_output*)aml_module_output_get(qcontext, outconfig); if (outdata == NULL || outdata->out[0].buf == NULL) { - printf("Vision model inference failed.\n"); + printf("Image model inference failed.\n"); return {}; } @@ -178,10 +179,10 @@ int destroy_network(void *qcontext) { int ret = 0; - if (vision_context_flag == qcontext) { - printf("Free vision model memory.\n"); - aml_util_freeBuffer(qcontext, &vision_mem_config, &vision_mem_data); - vision_context_flag = nullptr; + if (image_context_flag == qcontext) { + printf("Free image model memory.\n"); + aml_util_freeBuffer(qcontext, &image_mem_config, &image_mem_data); + image_context_flag = nullptr; } else if (text_context_flag == qcontext) { printf("Free text model memory.\n"); aml_util_freeBuffer(qcontext, &text_mem_config, &text_mem_data); @@ -199,4 +200,4 @@ int destroy_network(void *qcontext) } return ret; -} +} \ No newline at end of file diff --git a/examples/clip/cpp/src/model_invoke.h b/examples/clip/cpp/src/model_invoke.h index d46bc69..e83b59a 100755 --- a/examples/clip/cpp/src/model_invoke.h +++ b/examples/clip/cpp/src/model_invoke.h @@ -1,13 +1,29 @@ -#ifndef MODEL_INVOKE_H -#define MODEL_INVOKE_H - -#include -#include -#include - -void* init_network_file(const char *model_path); -std::vector process_image_dir(void *context_model, const std::string& json_path, const std::string& base_dir = "", const std::string& json_filename = ""); -int destroy_network(void *qcontext); - -#endif // MODEL_INVOKE_H - +/* + * Copyright (C) 2026 Amlogic, Inc. 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. + */ + +#ifndef MODEL_INVOKE_H +#define MODEL_INVOKE_H + +#include +#include +#include + +void* init_network_file(const char *model_path); +std::vector process_image_dir(void *context_model, const std::string& json_path, const std::string& base_dir = "", const std::string& json_filename = ""); +int destroy_network(void *qcontext); + +#endif // MODEL_INVOKE_H + diff --git a/examples/clip/cpp/src/pre_postprocess.cpp b/examples/clip/cpp/src/pre_postprocess.cpp index 5f43119..a9b39ab 100755 --- a/examples/clip/cpp/src/pre_postprocess.cpp +++ b/examples/clip/cpp/src/pre_postprocess.cpp @@ -102,7 +102,7 @@ std::vector preprocess_image(const std::string& image_path) { } } - // Return NHWC format (batch dimension will be added in caller) + // get NHWC return cropped; } diff --git a/examples/clip/model/.gitkeep b/examples/clip/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/clip/py/.gitkeep b/examples/clip/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/clip/py/clip.py b/examples/clip/py/clip.py index 0e4870c..2a424ed 100755 --- a/examples/clip/py/clip.py +++ b/examples/clip/py/clip.py @@ -1,339 +1,336 @@ -# -*- coding: utf-8 -*- -""" -Copyright (C) 2024–2025 Amlogic, Inc. 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. -""" - -# This inference script is designed for CLIP model using AMLNNLite. - -import os -import argparse -import numpy as np -from PIL import Image -from transformers import CLIPTokenizer -from amlnnlite.api import AMLNNLite - -# ==================== Utility Functions ==================== - -def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray: - """Compute softmax values for array x.""" - x = x - np.max(x, axis=axis, keepdims=True) - e = np.exp(x) - return e / np.sum(e, axis=axis, keepdims=True) - - -def l2_normalize(x: np.ndarray, axis: int = -1, eps: float = 1e-12) -> np.ndarray: - """L2 normalize array x along specified axis.""" - return x / (np.linalg.norm(x, axis=axis, keepdims=True) + eps) - -# ==================== Vision Preprocessing ==================== - -def preprocess_image(image_path: str, target_size: int = 224) -> np.ndarray: - """ - Preprocess image for CLIP model. - - Args: - image_path (str): Path to input image - target_size (int): Target image size (default: 224) - - Returns: - np.ndarray: Preprocessed image data with shape (1, target_size, target_size, 3) in NHWC format - """ - image = Image.open(image_path).convert("RGB") - width, height = image.size - - # Scale the shorter side - scale = target_size / min(width, height) - new_width = int(width * scale) - new_height = int(height * scale) - image_resized = image.resize((new_width, new_height), resample=Image.BICUBIC) - - # Center crop - left = (new_width - target_size) // 2 - top = (new_height - target_size) // 2 - right = left + target_size - bottom = top + target_size - image_cropped = image_resized.crop((left, top, right, bottom)) - - # Convert to numpy array and normalize to [0, 1] - image_np = np.array(image_cropped).astype(np.float32) / 255.0 - - # CLIP normalization - mean = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32) - std = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32) - image_np = (image_np - mean) / std - - # Add batch dimension: HWC -> NHWC - image_np = np.expand_dims(image_np, axis=0) - - return image_np.astype(np.float32) # [1, 224, 224, 3] - -# ==================== Text Preprocessing ==================== - -def preprocess_text(tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray: - """ - Preprocess text for CLIP model using CLIPTokenizer. - - Args: - tokenizer: CLIPTokenizer instance - text (str): Input text string - max_len (int): Maximum sequence length (default: 64) - - Returns: - np.ndarray: Tokenized text with shape (1, max_len) as int64 - """ - enc = tokenizer( - text, - padding="max_length", - truncation=True, - max_length=max_len, - return_tensors="np", - ) - # text model input: int64[1, max_len] - input_ids = enc["input_ids"].astype(np.int64) - return input_ids - -# ==================== Model Inference ==================== - -def compute_image_embedding(vision_amlnn: AMLNNLite, image_path: str) -> np.ndarray: - """ - Compute image embedding using vision model. - - Args: - vision_amlnn: AMLNNLite instance for vision model - image_path (str): Path to input image - - Returns: - np.ndarray: L2-normalized image embedding with shape (1, embed_dim) - """ - input_data = preprocess_image(image_path) # [1, 224, 224, 3] - - outputs = vision_amlnn.inference( - inputs=[input_data], - inputs_data_format='NHWC', - outputs_data_format='NHWC' - ) - - feats = outputs[0].astype(np.float32) - feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim] - return l2_normalize(feats, axis=1) - -def compute_text_embedding(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray: - """ - Compute text embedding using text model. - - Args: - text_amlnn: AMLNNLite instance for text model - tokenizer: CLIPTokenizer instance - text (str): Input text string - max_len (int): Maximum sequence length - - Returns: - np.ndarray: L2-normalized text embedding with shape (1, embed_dim) - """ - input_ids = preprocess_text(tokenizer, text, max_len) # [1, max_len] - print(f"input_ids: {input_ids}") - - # AMLNNLite requires 4D input, reshape to (1, 1, 1, max_len) - input_ids_4d = input_ids[:, None, None, :] # [1, 1, 1, max_len] - - outputs = text_amlnn.inference( - inputs=[input_ids_4d], - inputs_data_format='NHWC', - outputs_data_format='NHWC' - ) - - feats = outputs[0].astype(np.float32) - feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim] - return l2_normalize(feats, axis=1) - -def compute_text_embeddings_batch(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, texts: list, max_len: int = 64) -> np.ndarray: - """ - Compute text embeddings for multiple texts. - - Args: - text_amlnn: AMLNNLite instance for text model - tokenizer: CLIPTokenizer instance - texts (list): List of input text strings - max_len (int): Maximum sequence length - - Returns: - np.ndarray: L2-normalized text embeddings with shape (num_texts, embed_dim) - """ - embeddings = [] - for text in texts: - emb = compute_text_embedding(text_amlnn, tokenizer, text, max_len) - embeddings.append(emb[0]) # Remove batch dimension - return np.stack(embeddings, axis=0) # [num_texts, embed_dim] - -# ==================== Similarity Calculation ==================== - -def compute_similarity(image_embedding: np.ndarray, text_embeddings: np.ndarray, logit_scale: float = 100.0) -> tuple: - """ - Compute similarity between image and text embeddings. - - Args: - image_embedding (np.ndarray): Image embedding with shape (1, embed_dim) - text_embeddings (np.ndarray): Text embeddings with shape (num_texts, embed_dim) - logit_scale (float): Scale factor for logits - - Returns: - tuple: (similarities, logits, probabilities) - """ - # Cosine similarity (embeddings are already L2-normalized) - sims = text_embeddings @ image_embedding[0] # [num_texts] - logits = sims * logit_scale # [num_texts] - probs = softmax(logits, axis=0) # [num_texts] - - return sims, logits, probs - -# ==================== Main Function ==================== - -def main(): - parser = argparse.ArgumentParser(description='CLIP Image-Text Matching Demo using AMLNNLite') - parser.add_argument('--vision-model', required=True, help='Path to vision model (.adla)') - parser.add_argument('--text-model', required=True, help='Path to text model (.adla)') - parser.add_argument('--tokenizer-dir', required=True, help='Path to CLIPTokenizer directory') - parser.add_argument('--image-path', default=None, help='Path to input image (optional, will prompt if not provided)') - parser.add_argument('--texts', nargs='+', default=None, help='List of text descriptions to compare') - parser.add_argument('--max-len', type=int, default=64, help='Maximum token sequence length (default: 64)') - parser.add_argument('--logit-scale', type=float, default=100.0, help='Logit scale factor (default: 100.0)') - - args = parser.parse_args() - - # Validate model paths - if not os.path.exists(args.vision_model): - print(f"[Error] Vision model not found: {args.vision_model}") - return -1 - - if not os.path.exists(args.text_model): - print(f"[Error] Text model not found: {args.text_model}") - return -1 - - # Load tokenizer - print(f"[Info] Loading CLIPTokenizer from: {args.tokenizer_dir}") - tokenizer = CLIPTokenizer.from_pretrained(args.tokenizer_dir) - - # Initialize vision model - print(f"[Info] Initializing vision model: {args.vision_model}") - vision_amlnn = AMLNNLite() - vision_amlnn.config(model_path=args.vision_model, run_cycles=1) - vision_amlnn.init() - - # Initialize text model - print(f"[Info] Initializing text model: {args.text_model}") - text_amlnn = AMLNNLite() - text_amlnn.config(model_path=args.text_model, run_cycles=1) - text_amlnn.init() - - print("[Info] Models initialized successfully.\n") - - try: - # Interactive loop - while True: - # Get image path - if args.image_path: - image_path = args.image_path - args.image_path = None # Clear for next iteration - else: - print("=" * 60) - print("[Info] Image Path (or 'exit' to quit):") - image_path = input().strip() - - # Check for exit - if image_path.lower() == 'exit': - print("[Info] Exiting...") - break - - # Validate image path - if not image_path: - print("[Warning] Please enter an image path.") - continue - - if not os.path.exists(image_path): - print(f"[Error] Image not found: {image_path}") - continue - - # Get texts to compare - if args.texts: - texts = args.texts - args.texts = None # Clear for next iteration - else: - print("[Info] Enter text descriptions (comma-separated, or 'skip' to use defaults):") - text_input = input().strip() - - if text_input.lower() == 'skip' or not text_input: - # Default texts for demo - texts = [ - "a red handbag", - "a blue jacket", - "a red bus", - ] - print(f"[Info] Using default texts: {texts}") - else: - texts = [t.strip() for t in text_input.split(',') if t.strip()] - - if not texts: - print("[Warning] No texts provided.") - continue - - try: - # Compute image embedding - print(f"\n[Info] Processing image: {image_path}") - image_embedding = compute_image_embedding(vision_amlnn, image_path) - print(f"[Info] Image embedding shape: {image_embedding.shape}") - - # Compute text embeddings - print(f"[Info] Processing {len(texts)} text(s)...") - text_embeddings = compute_text_embeddings_batch(text_amlnn, tokenizer, texts, args.max_len) - print(f"[Info] Text embeddings shape: {text_embeddings.shape}") - - # Compute similarity - sims, logits, probs = compute_similarity(image_embedding, text_embeddings, args.logit_scale) - - # Print results - print("\n" + "=" * 60) - print("CLIP Image-Text Matching Results") - print("=" * 60) - print(f"Image: {image_path}") - print(f"logit_scale: {args.logit_scale:.6f}") - print("-" * 60) - - # Sort by probability (descending) - sorted_indices = np.argsort(probs)[::-1] - for rank, i in enumerate(sorted_indices): - print(f"[{rank + 1}] prob={probs[i]:.6f} sim={float(sims[i]):.6f} text='{texts[i]}'") - - print("=" * 60 + "\n") - - except Exception as e: - print(f"[Error] Processing failed: {e}") - import traceback - traceback.print_exc() - continue - - except KeyboardInterrupt: - print("\n\n[Info] Interrupted by user. Exiting...") - - finally: - # Cleanup - vision_amlnn.uninit() - text_amlnn.uninit() - - print("[Info] Done.") - return 0 - -if __name__ == "__main__": - import sys - sys.exit(main()) +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + +import os +import argparse +import numpy as np +from PIL import Image +from transformers import CLIPTokenizer +from amlnnlite.api import AMLNNLite + +# ==================== Utility Functions ==================== + +def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray: + """Compute softmax values for array x.""" + x = x - np.max(x, axis=axis, keepdims=True) + e = np.exp(x) + return e / np.sum(e, axis=axis, keepdims=True) + + +def l2_normalize(x: np.ndarray, axis: int = -1, eps: float = 1e-12) -> np.ndarray: + """L2 normalize array x along specified axis.""" + return x / (np.linalg.norm(x, axis=axis, keepdims=True) + eps) + +# ==================== Vision Preprocessing ==================== + +def preprocess_image(image_path: str, target_size: int = 224) -> np.ndarray: + """ + Preprocess image for CLIP model. + + Args: + image_path (str): Path to input image + target_size (int): Target image size (default: 224) + + Returns: + np.ndarray: Preprocessed image data with shape (1, target_size, target_size, 3) in NHWC format + """ + image = Image.open(image_path).convert("RGB") + width, height = image.size + + # Scale the shorter side + scale = target_size / min(width, height) + new_width = int(width * scale) + new_height = int(height * scale) + image_resized = image.resize((new_width, new_height), resample=Image.BICUBIC) + + # Center crop + left = (new_width - target_size) // 2 + top = (new_height - target_size) // 2 + right = left + target_size + bottom = top + target_size + image_cropped = image_resized.crop((left, top, right, bottom)) + + # Convert to numpy array and normalize to [0, 1] + image_np = np.array(image_cropped).astype(np.float32) / 255.0 + + # CLIP normalization + mean = np.array([0.48145466, 0.4578275, 0.40821073], dtype=np.float32) + std = np.array([0.26862954, 0.26130258, 0.27577711], dtype=np.float32) + image_np = (image_np - mean) / std + + # Add batch dimension: HWC -> NHWC + image_np = np.expand_dims(image_np, axis=0) + + return image_np.astype(np.float32) # [1, 224, 224, 3] + +# ==================== Text Preprocessing ==================== + +def preprocess_text(tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray: + """ + Preprocess text for CLIP model using CLIPTokenizer. + + Args: + tokenizer: CLIPTokenizer instance + text (str): Input text string + max_len (int): Maximum sequence length (default: 64) + + Returns: + np.ndarray: Tokenized text with shape (1, max_len) as int64 + """ + enc = tokenizer( + text, + padding="max_length", + truncation=True, + max_length=max_len, + return_tensors="np", + ) + # text model input: int64[1, max_len] + input_ids = enc["input_ids"].astype(np.int64) + return input_ids + +# ==================== Model Inference ==================== + +def compute_image_embedding(vision_amlnn: AMLNNLite, image_path: str) -> np.ndarray: + """ + Compute image embedding using vision model. + + Args: + vision_amlnn: AMLNNLite instance for vision model + image_path (str): Path to input image + + Returns: + np.ndarray: L2-normalized image embedding with shape (1, embed_dim) + """ + input_data = preprocess_image(image_path) # [1, 224, 224, 3] + + outputs = vision_amlnn.inference( + inputs=[input_data], + inputs_data_format='NHWC', + outputs_data_format='NHWC' + ) + + feats = outputs[0].astype(np.float32) + feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim] + return l2_normalize(feats, axis=1) + +def compute_text_embedding(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, text: str, max_len: int = 64) -> np.ndarray: + """ + Compute text embedding using text model. + + Args: + text_amlnn: AMLNNLite instance for text model + tokenizer: CLIPTokenizer instance + text (str): Input text string + max_len (int): Maximum sequence length + + Returns: + np.ndarray: L2-normalized text embedding with shape (1, embed_dim) + """ + input_ids = preprocess_text(tokenizer, text, max_len) # [1, max_len] + print(f"input_ids: {input_ids}") + + # AMLNNLite requires 4D input, reshape to (1, 1, 1, max_len) + input_ids_4d = input_ids[:, None, None, :] # [1, 1, 1, max_len] + + outputs = text_amlnn.inference( + inputs=[input_ids_4d], + inputs_data_format='NHWC', + outputs_data_format='NHWC' + ) + + feats = outputs[0].astype(np.float32) + feats = feats.reshape(1, -1) # Squeeze to [1, embed_dim] + return l2_normalize(feats, axis=1) + +def compute_text_embeddings_batch(text_amlnn: AMLNNLite, tokenizer: CLIPTokenizer, texts: list, max_len: int = 64) -> np.ndarray: + """ + Compute text embeddings for multiple texts. + + Args: + text_amlnn: AMLNNLite instance for text model + tokenizer: CLIPTokenizer instance + texts (list): List of input text strings + max_len (int): Maximum sequence length + + Returns: + np.ndarray: L2-normalized text embeddings with shape (num_texts, embed_dim) + """ + embeddings = [] + for text in texts: + emb = compute_text_embedding(text_amlnn, tokenizer, text, max_len) + embeddings.append(emb[0]) # Remove batch dimension + return np.stack(embeddings, axis=0) # [num_texts, embed_dim] + +# ==================== Similarity Calculation ==================== + +def compute_similarity(image_embedding: np.ndarray, text_embeddings: np.ndarray, logit_scale: float = 100.0) -> tuple: + """ + Compute similarity between image and text embeddings. + + Args: + image_embedding (np.ndarray): Image embedding with shape (1, embed_dim) + text_embeddings (np.ndarray): Text embeddings with shape (num_texts, embed_dim) + logit_scale (float): Scale factor for logits + + Returns: + tuple: (similarities, logits, probabilities) + """ + # Cosine similarity (embeddings are already L2-normalized) + sims = text_embeddings @ image_embedding[0] # [num_texts] + logits = sims * logit_scale # [num_texts] + probs = softmax(logits, axis=0) # [num_texts] + + return sims, logits, probs + +# ==================== Main Function ==================== + +def main(): + parser = argparse.ArgumentParser(description='CLIP Image-Text Matching Demo using AMLNNLite') + parser.add_argument('--vision-model', required=True, help='Path to vision model (.adla)') + parser.add_argument('--text-model', required=True, help='Path to text model (.adla)') + parser.add_argument('--tokenizer-dir', required=True, help='Path to CLIPTokenizer directory') + parser.add_argument('--image-path', default=None, help='Path to input image (optional, will prompt if not provided)') + parser.add_argument('--texts', nargs='+', default=None, help='List of text descriptions to compare') + parser.add_argument('--max-len', type=int, default=64, help='Maximum token sequence length (default: 64)') + parser.add_argument('--logit-scale', type=float, default=100.0, help='Logit scale factor (default: 100.0)') + + args = parser.parse_args() + + # Validate model paths + if not os.path.exists(args.vision_model): + print(f"[Error] Vision model not found: {args.vision_model}") + return -1 + + if not os.path.exists(args.text_model): + print(f"[Error] Text model not found: {args.text_model}") + return -1 + + # Load tokenizer + print(f"[Info] Loading CLIPTokenizer from: {args.tokenizer_dir}") + tokenizer = CLIPTokenizer.from_pretrained(args.tokenizer_dir) + + # Initialize vision model + print(f"[Info] Initializing vision model: {args.vision_model}") + vision_amlnn = AMLNNLite() + vision_amlnn.config(model_path=args.vision_model, run_cycles=1) + vision_amlnn.init() + + # Initialize text model + print(f"[Info] Initializing text model: {args.text_model}") + text_amlnn = AMLNNLite() + text_amlnn.config(model_path=args.text_model, run_cycles=1) + text_amlnn.init() + + print("[Info] Models initialized successfully.\n") + + try: + # Interactive loop + while True: + # Get image path + if args.image_path: + image_path = args.image_path + args.image_path = None # Clear for next iteration + else: + print("=" * 60) + print("[Info] Image Path (or 'exit' to quit):") + image_path = input().strip() + + # Check for exit + if image_path.lower() == 'exit': + print("[Info] Exiting...") + break + + # Validate image path + if not image_path: + print("[Warning] Please enter an image path.") + continue + + if not os.path.exists(image_path): + print(f"[Error] Image not found: {image_path}") + continue + + # Get texts to compare + if args.texts: + texts = args.texts + args.texts = None # Clear for next iteration + else: + print("[Info] Enter text descriptions (comma-separated, or 'skip' to use defaults):") + text_input = input().strip() + + if text_input.lower() == 'skip' or not text_input: + # Default texts for demo + texts = [ + "a red handbag", + "a blue jacket", + "a red bus", + ] + print(f"[Info] Using default texts: {texts}") + else: + texts = [t.strip() for t in text_input.split(',') if t.strip()] + + if not texts: + print("[Warning] No texts provided.") + continue + + try: + # Compute image embedding + print(f"\n[Info] Processing image: {image_path}") + image_embedding = compute_image_embedding(vision_amlnn, image_path) + print(f"[Info] Image embedding shape: {image_embedding.shape}") + + # Compute text embeddings + print(f"[Info] Processing {len(texts)} text(s)...") + text_embeddings = compute_text_embeddings_batch(text_amlnn, tokenizer, texts, args.max_len) + print(f"[Info] Text embeddings shape: {text_embeddings.shape}") + + # Compute similarity + sims, logits, probs = compute_similarity(image_embedding, text_embeddings, args.logit_scale) + + # Print results + print("\n" + "=" * 60) + print("CLIP Image-Text Matching Results") + print("=" * 60) + print(f"Image: {image_path}") + print(f"logit_scale: {args.logit_scale:.6f}") + print("-" * 60) + + # Sort by probability (descending) + sorted_indices = np.argsort(probs)[::-1] + for rank, i in enumerate(sorted_indices): + print(f"[{rank + 1}] prob={probs[i]:.6f} sim={float(sims[i]):.6f} text='{texts[i]}'") + + print("=" * 60 + "\n") + + except Exception as e: + print(f"[Error] Processing failed: {e}") + import traceback + traceback.print_exc() + continue + + except KeyboardInterrupt: + print("\n\n[Info] Interrupted by user. Exiting...") + + finally: + # Cleanup + vision_amlnn.uninit() + text_amlnn.uninit() + + print("[Info] Done.") + return 0 + +if __name__ == "__main__": + import sys + sys.exit(main()) diff --git a/examples/mobilenet/README.md b/examples/mobilenet/README.md index e65a0b4..86dd90c 100644 --- a/examples/mobilenet/README.md +++ b/examples/mobilenet/README.md @@ -1,118 +1,118 @@ -## Model Description - -This model is converted from MobileNetV2 pretrained weights -originally released by Google under the Apache License 2.0. - -Original model: -- Architecture: MobileNetV2 -- Source: TensorFlow / Keras official implementation - -The model has been converted and optimized into ADLA format -for deployment on Amlogic NPU platforms. - -## Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/mobilenet/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/mobilenet_v2_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/mobilenet_v2_demo /data/local/tmp/ -adb push model/mobilenet_v2_1.0_224_quant_A311D2.adla /data/local/tmp/ -adb push model/cat_224x224.jpg /data/local/tmp/ -adb push model/labels.txt /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x mobilenet_v2_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./mobilenet_v2_demo -./mobilenet_v2_demo mobilenet_v2_1.0_224_quant_A311D2.adla cat_224x224.jpg labels.txt -``` - -**Note:** Replace `mobilenet_v2_1.0_224_quant_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `Pillow`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy Pillow amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -# Basic usage -python mobilenetv2.py --model-path ./mobilenet_v2_1.0_224_quant_A311D2.adla - -# Run with performance testing (100 cycles) -python mobilenetv2.py --model-path ./mobilenet_v2_1.0_224_quant_A311D2.adla --run-cycles 100 -``` - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and display top-5 classification results for each image. - -## Results - -The program will print the top-5 classification results with probabilities for each processed image. - -**Example output:** -``` -# python demo result -============================================================ -Processing image 1/3: dog_224x224.jpg -============================================================ - -Top-5 Classification Results: - 1. Shih-Tzu (probability: 0.9239) - 2. Pekinese (probability: 0.0476) - 3. Lhasa (probability: 0.0263) - 4. Brabancon griffon (probability: 0.0004) - 5. Dandie Dinmont (probability: 0.0003) - -============================================================ -Processing image 2/3: cat_224x224.jpg -============================================================ - -Top-5 Classification Results: - 1. tiger cat (probability: 0.4774) - 2. tabby (probability: 0.4324) - 3. Egyptian cat (probability: 0.0542) - 4. lynx (probability: 0.0150) - 5. Persian cat (probability: 0.0025) - -============================================================ -Processing image 3/3: fish_224x224.jpeg -============================================================ - -Top-5 Classification Results: - 1. goldfish (probability: 0.9998) - 2. conch (probability: 0.0001) - 3. trifle (probability: 0.0000) - 4. axolotl (probability: 0.0000) - 5. American lobster (probability: 0.0000) -``` - -The classification results show the model's confidence scores (probabilities) for each detected class, with the highest probability indicating the most likely classification. - - +## Model Description + +This model is converted from MobileNetV2 pretrained weights +originally released by Google under the Apache License 2.0. + +Original model: +- Architecture: MobileNetV2 +- Source: TensorFlow / Keras official implementation + +The model has been converted and optimized into ADLA format +for deployment on Amlogic NPU platforms. + +## Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/mobilenet/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/mobilenet_v2_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/mobilenet_v2_demo /data/local/tmp/ +adb push model/mobilenet_v2_1.0_224_quant_A311D2.adla /data/local/tmp/ +adb push model/cat_224x224.jpg /data/local/tmp/ +adb push model/labels.txt /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x mobilenet_v2_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./mobilenet_v2_demo +./mobilenet_v2_demo mobilenet_v2_1.0_224_quant_A311D2.adla cat_224x224.jpg labels.txt +``` + +**Note:** Replace `mobilenet_v2_1.0_224_quant_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `Pillow`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy Pillow amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +# Basic usage +python mobilenetv2.py --model-path ./mobilenet_v2_1.0_224_quant_A311D2.adla + +# Run with performance testing (100 cycles) +python mobilenetv2.py --model-path ./mobilenet_v2_1.0_224_quant_A311D2.adla --run-cycles 100 +``` + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and display top-5 classification results for each image. + +## Results + +The program will print the top-5 classification results with probabilities for each processed image. + +**Example output:** +``` +# python demo result +============================================================ +Processing image 1/3: dog_224x224.jpg +============================================================ + +Top-5 Classification Results: + 1. Shih-Tzu (probability: 0.9239) + 2. Pekinese (probability: 0.0476) + 3. Lhasa (probability: 0.0263) + 4. Brabancon griffon (probability: 0.0004) + 5. Dandie Dinmont (probability: 0.0003) + +============================================================ +Processing image 2/3: cat_224x224.jpg +============================================================ + +Top-5 Classification Results: + 1. tiger cat (probability: 0.4774) + 2. tabby (probability: 0.4324) + 3. Egyptian cat (probability: 0.0542) + 4. lynx (probability: 0.0150) + 5. Persian cat (probability: 0.0025) + +============================================================ +Processing image 3/3: fish_224x224.jpeg +============================================================ + +Top-5 Classification Results: + 1. goldfish (probability: 0.9998) + 2. conch (probability: 0.0001) + 3. trifle (probability: 0.0000) + 4. axolotl (probability: 0.0000) + 5. American lobster (probability: 0.0000) +``` + +The classification results show the model's confidence scores (probabilities) for each detected class, with the highest probability indicating the most likely classification. + + diff --git a/examples/mobilenet/cpp/.gitkeep b/examples/mobilenet/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/mobilenet/cpp/build-android.sh b/examples/mobilenet/cpp/build-android.sh index f01664a..447fa85 100755 --- a/examples/mobilenet/cpp/build-android.sh +++ b/examples/mobilenet/cpp/build-android.sh @@ -1,4 +1,20 @@ #!/bin/bash + +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# set -e usage() { diff --git a/examples/mobilenet/cpp/src/main.cpp b/examples/mobilenet/cpp/src/main.cpp index e8a97bf..d85a46a 100755 --- a/examples/mobilenet/cpp/src/main.cpp +++ b/examples/mobilenet/cpp/src/main.cpp @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include #include #include @@ -107,7 +107,7 @@ int main(int argc, char** argv) { memset(&outconfig, 0, sizeof(aml_output_config_t)); outconfig.typeSize = sizeof(aml_output_config_t); outconfig.format = AML_OUTDATA_FLOAT32; - + nn_output* outdata = (nn_output*)aml_module_output_get(context, outconfig); if (!outdata) { std::cerr << "Failed to run network (get output)." << std::endl; diff --git a/examples/mobilenet/model/.gitkeep b/examples/mobilenet/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/mobilenet/model/labels.txt b/examples/mobilenet/model/labels.txt index fe81123..0521a71 100755 --- a/examples/mobilenet/model/labels.txt +++ b/examples/mobilenet/model/labels.txt @@ -240,7 +240,7 @@ miniature pinscher Greater Swiss Mountain dog Bernese mountain dog Appenzeller -EntleBucher +Entlebucher boxer bull mastiff Tibetan mastiff @@ -420,7 +420,7 @@ balloon ballpoint Band Aid banjo -bannister +banister barbell barber chair barbershop @@ -468,7 +468,7 @@ bulletproof vest bullet train butcher shop cab -caldron +cauldron candle cannon canoe diff --git a/examples/mobilenet/py/.gitkeep b/examples/mobilenet/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/mobilenet/py/mobilenetv2.py b/examples/mobilenet/py/mobilenetv2.py index 58b5742..05474cf 100755 --- a/examples/mobilenet/py/mobilenetv2.py +++ b/examples/mobilenet/py/mobilenetv2.py @@ -1,3 +1,19 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + import numpy as np import os import glob diff --git a/examples/ppocr-det/README.md b/examples/ppocr-det/README.md index fbeb79a..b88ca52 100644 --- a/examples/ppocr-det/README.md +++ b/examples/ppocr-det/README.md @@ -1,20 +1,20 @@ -# PaddleOCR Detection - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK r25c -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/ppocr-det/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated in `build/android/`. +# PaddleOCR Detection + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK r25c +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/ppocr-det/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated in `build/android/`. diff --git a/examples/ppocr-det/cpp/.gitkeep b/examples/ppocr-det/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/ppocr-det/cpp/src/CMakeLists.txt b/examples/ppocr-det/cpp/src/CMakeLists.txt index 2bfec1c..55f952d 100755 --- a/examples/ppocr-det/cpp/src/CMakeLists.txt +++ b/examples/ppocr-det/cpp/src/CMakeLists.txt @@ -1,38 +1,38 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(yolo_world_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set 3rdparty path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(paddleocr_det_demo - main.cpp - postprocess.cpp - postprocess.h - clipper.cpp - clipper.h - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(paddleocr_det_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} -) +cmake_minimum_required(VERSION 3.10...3.27) +project(yolo_world_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(paddleocr_det_demo + main.cpp + postprocess.cpp + postprocess.h + clipper.cpp + clipper.h + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(paddleocr_det_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} +) diff --git a/examples/ppocr-det/cpp/src/clipper.cpp b/examples/ppocr-det/cpp/src/clipper.cpp index 87ab7de..947ef1b 100755 --- a/examples/ppocr-det/cpp/src/clipper.cpp +++ b/examples/ppocr-det/cpp/src/clipper.cpp @@ -1,4380 +1,4379 @@ -/******************************************************************************* -* * -* 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 flavo. * -* * -*******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#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(val - 0.5); - else - return static_cast(val + 0.5); -} -//------------------------------------------------------------------------------ - -inline cInt Abs(cInt val) { return val < 0 ? -val : val; } - -//------------------------------------------------------------------------------ -// PolyTree methods ... -//------------------------------------------------------------------------------ - -void PolyTree::Clear() { - for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) - delete AllNodes[i]; - AllNodes.resize(0); - Children.resize(0); -} -//------------------------------------------------------------------------------ - -PolyNode *PolyTree::GetFirst() const { - if (!Children.empty()) - return Children[0]; - else - return 0; -} -//------------------------------------------------------------------------------ - -int PolyTree::Total() const { - int result = (int)AllNodes.size(); - // with negative offsets, ignore the hidden outer polygon ... - if (result > 0 && Children[0] != AllNodes[0]) - result--; - return result; -} - -//------------------------------------------------------------------------------ -// PolyNode methods ... -//------------------------------------------------------------------------------ - -PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {} -//------------------------------------------------------------------------------ - -int PolyNode::ChildCount() const { return (int)Children.size(); } -//------------------------------------------------------------------------------ - -void PolyNode::AddChild(PolyNode &child) { - unsigned cnt = (unsigned)Children.size(); - Children.push_back(&child); - child.Parent = this; - child.Index = cnt; -} -//------------------------------------------------------------------------------ - -PolyNode *PolyNode::GetNext() const { - if (!Children.empty()) - return Children[0]; - else - return GetNextSiblingUp(); -} -//------------------------------------------------------------------------------ - -PolyNode *PolyNode::GetNextSiblingUp() const { - if (!Parent) // protects against PolyTree.GetNextSiblingUp() - return 0; - else if (Index == Parent->Children.size() - 1) - return Parent->GetNextSiblingUp(); - else - return Parent->Children[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, int(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 precedes '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 || !rb) { - // 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.Children.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.Children[i]; - m_polyNodes.Children.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.Children[(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.Children[(int)m_lowest.X]->Contour)) { - for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { - PolyNode &node = *m_polyNodes.Children[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.Children[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.Children[0]->ChildCount() > 0) { - PolyNode *outerNode = solution.Children[0]; - solution.Children.reserve(outerNode->ChildCount()); - solution.Children[0] = outerNode->Children[0]; - solution.Children[0]->Parent = outerNode->Parent; - for (int i = 1; i < outerNode->ChildCount(); ++i) - solution.AddChild(*outerNode->Children[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.Children[i]; - if (node.m_endtype == etClosedPolygon) - m_destPolys.push_back(node.Contour); - } - return; - } - - // see offset_trigonometry3.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_trigonometry2.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.Children[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 (x1,y1) & (x2,y2) is ... - //(y1 - y2)x + (x2 - x1)y + (y1 - y2)x2 - (x2 - x1)y2 = 0 - // A = (y1 - y2); B = (x2 - x1); C = (y1 - y2)x2 - (x2 - x1)y2 - // perpendicular distance of point (x0,y0) = (Ax0 + By0 + C)/Sqrt(A^2 + B^2) - // 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.Children[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.Children[i]->IsOpen()) - paths.push_back(polytree.Children[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; -} -//------------------------------------------------------------------------------ - +/******************************************************************************* +* * +* 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 flavo. * +* * +*******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#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(val - 0.5); + else + return static_cast(val + 0.5); +} +//------------------------------------------------------------------------------ + +inline cInt Abs(cInt val) { return val < 0 ? -val : val; } + +//------------------------------------------------------------------------------ +// PolyTree methods ... +//------------------------------------------------------------------------------ + +void PolyTree::Clear() { + for (PolyNodes::size_type i = 0; i < AllNodes.size(); ++i) + delete AllNodes[i]; + AllNodes.resize(0); + Children.resize(0); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyTree::GetFirst() const { + if (!Children.empty()) + return Children[0]; + else + return 0; +} +//------------------------------------------------------------------------------ + +int PolyTree::Total() const { + int result = (int)AllNodes.size(); + // with negative offsets, ignore the hidden outer polygon ... + if (result > 0 && Children[0] != AllNodes[0]) + result--; + return result; +} + +//------------------------------------------------------------------------------ +// PolyNode methods ... +//------------------------------------------------------------------------------ + +PolyNode::PolyNode() : Parent(0), Index(0), m_IsOpen(false) {} +//------------------------------------------------------------------------------ + +int PolyNode::ChildCount() const { return (int)Children.size(); } +//------------------------------------------------------------------------------ + +void PolyNode::AddChild(PolyNode &child) { + unsigned cnt = (unsigned)Children.size(); + Children.push_back(&child); + child.Parent = this; + child.Index = cnt; +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNext() const { + if (!Children.empty()) + return Children[0]; + else + return GetNextSiblingUp(); +} +//------------------------------------------------------------------------------ + +PolyNode *PolyNode::GetNextSiblingUp() const { + if (!Parent) // protects against PolyTree.GetNextSiblingUp() + return 0; + else if (Index == Parent->Children.size() - 1) + return Parent->GetNextSiblingUp(); + else + return Parent->Children[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, int(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 precedes '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 || !rb) { + // 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) wherever 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.Children.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.Children[i]; + m_polyNodes.Children.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.Children[(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.Children[(int)m_lowest.X]->Contour)) { + for (int i = 0; i < m_polyNodes.ChildCount(); ++i) { + PolyNode &node = *m_polyNodes.Children[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.Children[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.Children[0]->ChildCount() > 0) { + PolyNode *outerNode = solution.Children[0]; + solution.Children.reserve(outerNode->ChildCount()); + solution.Children[0] = outerNode->Children[0]; + solution.Children[0]->Parent = outerNode->Parent; + for (int i = 1; i < outerNode->ChildCount(); ++i) + solution.AddChild(*outerNode->Children[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.Children[i]; + if (node.m_endtype == etClosedPolygon) + m_destPolys.push_back(node.Contour); + } + return; + } + + // see offset_trigonometry3.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_trigonometry2.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.Children[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.Children[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.Children[i]->IsOpen()) + paths.push_back(polytree.Children[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 \ No newline at end of file diff --git a/examples/ppocr-det/cpp/src/clipper.h b/examples/ppocr-det/cpp/src/clipper.h index 7783262..36b4779 100755 --- a/examples/ppocr-det/cpp/src/clipper.h +++ b/examples/ppocr-det/cpp/src/clipper.h @@ -1,425 +1,425 @@ -/******************************************************************************* -* * -* 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 * -* * -*******************************************************************************/ - -#pragma once - -#ifndef clipper_hpp -#define clipper_hpp - -#define CLIPPER_VERSION "6.4.2" - -// use_int32: When enabled 32bit ints are used instead of 64bit ints. This -// improve performance but coordinate values are limited to the range +/- 46340 -//#define use_int32 - -// use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. -//#define use_xyz - -// use_lines: Enables line clipping. Adds a very minor cost to performance. -#define use_lines - -// use_deprecated: Enables temporary support for the obsolete functions -//#define use_deprecated - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace ClipperLib { - -enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; -enum PolyType { ptSubject, ptClip }; -// By far the most widely used winding rules for polygon filling are -// EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) -// Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) -// see http://glprogramming.com/red/chapter11.html -enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; - -#ifdef use_int32 -typedef int cInt; -static cInt const loRange = 0x7FFF; -static cInt const hiRange = 0x7FFF; -#else -typedef signed long long cInt; -static cInt const loRange = 0x3FFFFFFF; -static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; -typedef signed long long long64; // used by Int128 class -typedef unsigned long long ulong64; - -#endif - -struct IntPoint { - cInt X; - cInt Y; -#ifdef use_xyz - cInt Z; - IntPoint(cInt x = 0, cInt y = 0, cInt z = 0) : X(x), Y(y), Z(z){}; -#else - IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y){}; -#endif - - friend inline bool operator==(const IntPoint &a, const IntPoint &b) { - return a.X == b.X && a.Y == b.Y; - } - friend inline bool operator!=(const IntPoint &a, const IntPoint &b) { - return a.X != b.X || a.Y != b.Y; - } -}; -//------------------------------------------------------------------------------ - -typedef std::vector Path; -typedef std::vector Paths; - -inline Path &operator<<(Path &poly, const IntPoint &p) { - poly.push_back(p); - return poly; -} -inline Paths &operator<<(Paths &polys, const Path &p) { - polys.push_back(p); - return polys; -} - -std::ostream &operator<<(std::ostream &s, const IntPoint &p); -std::ostream &operator<<(std::ostream &s, const Path &p); -std::ostream &operator<<(std::ostream &s, const Paths &p); - -struct DoublePoint { - double X; - double Y; - DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} - DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} -}; -//------------------------------------------------------------------------------ - -#ifdef use_xyz -typedef void (*ZFillCallback)(IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, - IntPoint &e2top, IntPoint &pt); -#endif - -enum InitOptions { - ioReverseSolution = 1, - ioStrictlySimple = 2, - ioPreserveCollinear = 4 -}; -enum JoinType { jtSquare, jtRound, jtMiter }; -enum EndType { - etClosedPolygon, - etClosedLine, - etOpenButt, - etOpenSquare, - etOpenRound -}; - -class PolyNode; -typedef std::vector PolyNodes; - -class PolyNode { -public: - PolyNode(); - virtual ~PolyNode(){}; - Path Contour; - PolyNodes Children; - PolyNode *Parent; - PolyNode *GetNext() const; - bool IsHole() const; - bool IsOpen() const; - int ChildCount() const; - -private: - // PolyNode& operator =(PolyNode& other); - unsigned Index; // node index in Parent.Children - bool m_IsOpen; - JoinType m_jointype; - EndType m_endtype; - PolyNode *GetNextSiblingUp() const; - void AddChild(PolyNode &child); - friend class Clipper; // to access Index - friend class ClipperOffset; -}; - -class PolyTree : public PolyNode { -public: - ~PolyTree() { Clear(); }; - PolyNode *GetFirst() const; - void Clear(); - int Total() const; - -private: - // PolyTree& operator =(PolyTree& other); - PolyNodes AllNodes; - friend class Clipper; // to access AllNodes -}; - -bool Orientation(const Path &poly); -double Area(const Path &poly); -int PointInPolygon(const IntPoint &pt, const Path &path); - -void SimplifyPolygon(const Path &in_poly, Paths &out_polys, - PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, - PolyFillType fillType = pftEvenOdd); -void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); - -void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415); -void CleanPolygon(Path &poly, double distance = 1.415); -void CleanPolygons(const Paths &in_polys, Paths &out_polys, - double distance = 1.415); -void CleanPolygons(Paths &polys, double distance = 1.415); - -void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, - bool pathIsClosed); -void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, - bool pathIsClosed); -void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution); - -void PolyTreeToPaths(const PolyTree &polytree, Paths &paths); -void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths); -void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths); - -void ReversePath(Path &p); -void ReversePaths(Paths &p); - -struct IntRect { - cInt left; - cInt top; - cInt right; - cInt bottom; -}; - -// enums that are used internally ... -enum EdgeSide { esLeft = 1, esRight = 2 }; - -// forward declarations (for stuff used internally) ... -struct TEdge; -struct IntersectNode; -struct LocalMinimum; -struct OutPt; -struct OutRec; -struct Join; - -typedef std::vector PolyOutList; -typedef std::vector EdgeList; -typedef std::vector JoinList; -typedef std::vector IntersectList; - -//------------------------------------------------------------------------------ - -// ClipperBase is the ancestor to the Clipper class. It should not be -// instantiated directly. This class simply abstracts the conversion of sets of -// polygon coordinates into edge objects that are stored in a LocalMinima list. -class ClipperBase { -public: - ClipperBase(); - virtual ~ClipperBase(); - virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); - bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); - virtual void Clear(); - IntRect GetBounds(); - bool PreserveCollinear() { return m_PreserveCollinear; }; - void PreserveCollinear(bool value) { m_PreserveCollinear = value; }; - -protected: - void DisposeLocalMinimaList(); - TEdge *AddBoundsToLML(TEdge *e, bool IsClosed); - virtual void Reset(); - TEdge *ProcessBound(TEdge *E, bool IsClockwise); - void InsertScanbeam(const cInt Y); - bool PopScanbeam(cInt &Y); - bool LocalMinimaPending(); - bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); - OutRec *CreateOutRec(); - void DisposeAllOutRecs(); - void DisposeOutRec(PolyOutList::size_type index); - void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); - void DeleteFromAEL(TEdge *e); - void UpdateEdgeIntoAEL(TEdge *&e); - - typedef std::vector MinimaList; - MinimaList::iterator m_CurrentLM; - MinimaList m_MinimaList; - - bool m_UseFullRange; - EdgeList m_edges; - bool m_PreserveCollinear; - bool m_HasOpenPaths; - PolyOutList m_PolyOuts; - TEdge *m_ActiveEdges; - - typedef std::priority_queue ScanbeamList; - ScanbeamList m_Scanbeam; -}; -//------------------------------------------------------------------------------ - -class Clipper : public virtual ClipperBase { -public: - Clipper(int initOptions = 0); - bool Execute(ClipType clipType, Paths &solution, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, - PolyFillType clipFillType); - bool Execute(ClipType clipType, PolyTree &polytree, - PolyFillType fillType = pftEvenOdd); - bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, - PolyFillType clipFillType); - bool ReverseSolution() { return m_ReverseOutput; }; - void ReverseSolution(bool value) { m_ReverseOutput = value; }; - bool StrictlySimple() { return m_StrictSimple; }; - void StrictlySimple(bool value) { m_StrictSimple = value; }; -// set the callback function for z value filling on intersections (otherwise Z -// is 0) -#ifdef use_xyz - void ZFillFunction(ZFillCallback zFillFunc); -#endif -protected: - virtual bool ExecuteInternal(); - -private: - JoinList m_Joins; - JoinList m_GhostJoins; - IntersectList m_IntersectList; - ClipType m_ClipType; - typedef std::list MaximaList; - MaximaList m_Maxima; - TEdge *m_SortedEdges; - bool m_ExecuteLocked; - PolyFillType m_ClipFillType; - PolyFillType m_SubjFillType; - bool m_ReverseOutput; - bool m_UsingPolyTree; - bool m_StrictSimple; -#ifdef use_xyz - ZFillCallback m_ZFill; // custom callback -#endif - void SetWindingCount(TEdge &edge); - bool IsEvenOddFillType(const TEdge &edge) const; - bool IsEvenOddAltFillType(const TEdge &edge) const; - void InsertLocalMinimaIntoAEL(const cInt botY); - void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge); - void AddEdgeToSEL(TEdge *edge); - bool PopEdgeFromSEL(TEdge *&edge); - void CopyAELToSEL(); - void DeleteFromSEL(TEdge *e); - void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); - bool IsContributing(const TEdge &edge) const; - bool IsTopHorz(const cInt XPos); - void DoMaxima(TEdge *e); - void ProcessHorizontals(); - void ProcessHorizontal(TEdge *horzEdge); - void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); - OutRec *GetOutRec(int idx); - void AppendPolygon(TEdge *e1, TEdge *e2); - void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); - OutPt *AddOutPt(TEdge *e, const IntPoint &pt); - OutPt *GetLastOutPt(TEdge *e); - bool ProcessIntersections(const cInt topY); - void BuildIntersectList(const cInt topY); - void ProcessIntersectList(); - void ProcessEdgesAtTopOfScanbeam(const cInt topY); - void BuildResult(Paths &polys); - void BuildResult2(PolyTree &polytree); - void SetHoleState(TEdge *e, OutRec *outrec); - void DisposeIntersectNodes(); - bool FixupIntersectionOrder(); - void FixupOutPolygon(OutRec &outrec); - void FixupOutPolyline(OutRec &outrec); - bool IsHole(TEdge *e); - bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); - void FixHoleLinkage(OutRec &outrec); - void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); - void ClearJoins(); - void ClearGhostJoins(); - void AddGhostJoin(OutPt *op, const IntPoint offPt); - bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2); - void JoinCommonEdges(); - void DoSimplePolygons(); - void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec); - void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec); - void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec); -#ifdef use_xyz - void SetZ(IntPoint &pt, TEdge &e1, TEdge &e2); -#endif -}; -//------------------------------------------------------------------------------ - -class ClipperOffset { -public: - ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); - ~ClipperOffset(); - void AddPath(const Path &path, JoinType joinType, EndType endType); - void AddPaths(const Paths &paths, JoinType joinType, EndType endType); - void Execute(Paths &solution, double delta); - void Execute(PolyTree &solution, double delta); - void Clear(); - double MiterLimit; - double ArcTolerance; - -private: - Paths m_destPolys; - Path m_srcPoly; - Path m_destPoly; - std::vector m_normals; - double m_delta, m_sinA, m_sin, m_cos; - double m_miterLim, m_StepsPerRad; - IntPoint m_lowest; - PolyNode m_polyNodes; - - void FixOrientations(); - void DoOffset(double delta); - void OffsetPoint(int j, int &k, JoinType jointype); - void DoSquare(int j, int k); - void DoMiter(int j, int k, double r); - void DoRound(int j, int k); -}; -//------------------------------------------------------------------------------ - -class clipperException : public std::exception { -public: - clipperException(const char *description) : m_descr(description) {} - virtual ~clipperException() throw() {} - virtual const char *what() const throw() { return m_descr.c_str(); } - -private: - std::string m_descr; -}; -//------------------------------------------------------------------------------ - -} // ClipperLib namespace - +/******************************************************************************* +* * +* 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 * +* * +*******************************************************************************/ + +#pragma once + +#ifndef clipper_hpp +#define clipper_hpp + +#define CLIPPER_VERSION "6.4.2" + +// use_int32: When enabled 32bit ints are used instead of 64bit ints. This +// improve performance but coordinate values are limited to the range +/- 46340 +//#define use_int32 + +// use_xyz: adds a Z member to IntPoint. Adds a minor cost to performance. +//#define use_xyz + +// use_lines: Enables line clipping. Adds a very minor cost to performance. +#define use_lines + +// use_deprecated: Enables temporary support for the obsolete functions +//#define use_deprecated + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ClipperLib { + +enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor }; +enum PolyType { ptSubject, ptClip }; +// By far the most widely used winding rules for polygon filling are +// EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32) +// Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL) +// see http://glprogramming.com/red/chapter11.html +enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative }; + +#ifdef use_int32 +typedef int cInt; +static cInt const loRange = 0x7FFF; +static cInt const hiRange = 0x7FFF; +#else +typedef signed long long cInt; +static cInt const loRange = 0x3FFFFFFF; +static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL; +typedef signed long long long64; // used by Int128 class +typedef unsigned long long ulong64; + +#endif + +struct IntPoint { + cInt X; + cInt Y; +#ifdef use_xyz + cInt Z; + IntPoint(cInt x = 0, cInt y = 0, cInt z = 0) : X(x), Y(y), Z(z){}; +#else + IntPoint(cInt x = 0, cInt y = 0) : X(x), Y(y){}; +#endif + + friend inline bool operator==(const IntPoint &a, const IntPoint &b) { + return a.X == b.X && a.Y == b.Y; + } + friend inline bool operator!=(const IntPoint &a, const IntPoint &b) { + return a.X != b.X || a.Y != b.Y; + } +}; +//------------------------------------------------------------------------------ + +typedef std::vector Path; +typedef std::vector Paths; + +inline Path &operator<<(Path &poly, const IntPoint &p) { + poly.push_back(p); + return poly; +} +inline Paths &operator<<(Paths &polys, const Path &p) { + polys.push_back(p); + return polys; +} + +std::ostream &operator<<(std::ostream &s, const IntPoint &p); +std::ostream &operator<<(std::ostream &s, const Path &p); +std::ostream &operator<<(std::ostream &s, const Paths &p); + +struct DoublePoint { + double X; + double Y; + DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {} + DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {} +}; +//------------------------------------------------------------------------------ + +#ifdef use_xyz +typedef void (*ZFillCallback)(IntPoint &e1bot, IntPoint &e1top, IntPoint &e2bot, + IntPoint &e2top, IntPoint &pt); +#endif + +enum InitOptions { + ioReverseSolution = 1, + ioStrictlySimple = 2, + ioPreserveCollinear = 4 +}; +enum JoinType { jtSquare, jtRound, jtMiter }; +enum EndType { + etClosedPolygon, + etClosedLine, + etOpenButt, + etOpenSquare, + etOpenRound +}; + +class PolyNode; +typedef std::vector PolyNodes; + +class PolyNode { +public: + PolyNode(); + virtual ~PolyNode(){}; + Path Contour; + PolyNodes Children; + PolyNode *Parent; + PolyNode *GetNext() const; + bool IsHole() const; + bool IsOpen() const; + int ChildCount() const; + +private: + // PolyNode& operator =(PolyNode& other); + unsigned Index; // node index in Parent.Children + bool m_IsOpen; + JoinType m_jointype; + EndType m_endtype; + PolyNode *GetNextSiblingUp() const; + void AddChild(PolyNode &child); + friend class Clipper; // to access Index + friend class ClipperOffset; +}; + +class PolyTree : public PolyNode { +public: + ~PolyTree() { Clear(); }; + PolyNode *GetFirst() const; + void Clear(); + int Total() const; + +private: + // PolyTree& operator =(PolyTree& other); + PolyNodes AllNodes; + friend class Clipper; // to access AllNodes +}; + +bool Orientation(const Path &poly); +double Area(const Path &poly); +int PointInPolygon(const IntPoint &pt, const Path &path); + +void SimplifyPolygon(const Path &in_poly, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, + PolyFillType fillType = pftEvenOdd); +void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd); + +void CleanPolygon(const Path &in_poly, Path &out_poly, double distance = 1.415); +void CleanPolygon(Path &poly, double distance = 1.415); +void CleanPolygons(const Paths &in_polys, Paths &out_polys, + double distance = 1.415); +void CleanPolygons(Paths &polys, double distance = 1.415); + +void MinkowskiSum(const Path &pattern, const Path &path, Paths &solution, + bool pathIsClosed); +void MinkowskiSum(const Path &pattern, const Paths &paths, Paths &solution, + bool pathIsClosed); +void MinkowskiDiff(const Path &poly1, const Path &poly2, Paths &solution); + +void PolyTreeToPaths(const PolyTree &polytree, Paths &paths); +void ClosedPathsFromPolyTree(const PolyTree &polytree, Paths &paths); +void OpenPathsFromPolyTree(PolyTree &polytree, Paths &paths); + +void ReversePath(Path &p); +void ReversePaths(Paths &p); + +struct IntRect { + cInt left; + cInt top; + cInt right; + cInt bottom; +}; + +// enums that are used internally ... +enum EdgeSide { esLeft = 1, esRight = 2 }; + +// forward declarations (for stuff used internally) ... +struct TEdge; +struct IntersectNode; +struct LocalMinimum; +struct OutPt; +struct OutRec; +struct Join; + +typedef std::vector PolyOutList; +typedef std::vector EdgeList; +typedef std::vector JoinList; +typedef std::vector IntersectList; + +//------------------------------------------------------------------------------ + +// ClipperBase is the ancestor to the Clipper class. It should not be +// instantiated directly. This class simply abstracts the conversion of sets of +// polygon coordinates into edge objects that are stored in a LocalMinima list. +class ClipperBase { +public: + ClipperBase(); + virtual ~ClipperBase(); + virtual bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed); + bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed); + virtual void Clear(); + IntRect GetBounds(); + bool PreserveCollinear() { return m_PreserveCollinear; }; + void PreserveCollinear(bool value) { m_PreserveCollinear = value; }; + +protected: + void DisposeLocalMinimaList(); + TEdge *AddBoundsToLML(TEdge *e, bool IsClosed); + virtual void Reset(); + TEdge *ProcessBound(TEdge *E, bool IsClockwise); + void InsertScanbeam(const cInt Y); + bool PopScanbeam(cInt &Y); + bool LocalMinimaPending(); + bool PopLocalMinima(cInt Y, const LocalMinimum *&locMin); + OutRec *CreateOutRec(); + void DisposeAllOutRecs(); + void DisposeOutRec(PolyOutList::size_type index); + void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2); + void DeleteFromAEL(TEdge *e); + void UpdateEdgeIntoAEL(TEdge *&e); + + typedef std::vector MinimaList; + MinimaList::iterator m_CurrentLM; + MinimaList m_MinimaList; + + bool m_UseFullRange; + EdgeList m_edges; + bool m_PreserveCollinear; + bool m_HasOpenPaths; + PolyOutList m_PolyOuts; + TEdge *m_ActiveEdges; + + typedef std::priority_queue ScanbeamList; + ScanbeamList m_Scanbeam; +}; +//------------------------------------------------------------------------------ + +class Clipper : public virtual ClipperBase { +public: + Clipper(int initOptions = 0); + bool Execute(ClipType clipType, Paths &solution, + PolyFillType fillType = pftEvenOdd); + bool Execute(ClipType clipType, Paths &solution, PolyFillType subjFillType, + PolyFillType clipFillType); + bool Execute(ClipType clipType, PolyTree &polytree, + PolyFillType fillType = pftEvenOdd); + bool Execute(ClipType clipType, PolyTree &polytree, PolyFillType subjFillType, + PolyFillType clipFillType); + bool ReverseSolution() { return m_ReverseOutput; }; + void ReverseSolution(bool value) { m_ReverseOutput = value; }; + bool StrictlySimple() { return m_StrictSimple; }; + void StrictlySimple(bool value) { m_StrictSimple = value; }; +// set the callback function for z value filling on intersections (otherwise Z +// is 0) +#ifdef use_xyz + void ZFillFunction(ZFillCallback zFillFunc); +#endif +protected: + virtual bool ExecuteInternal(); + +private: + JoinList m_Joins; + JoinList m_GhostJoins; + IntersectList m_IntersectList; + ClipType m_ClipType; + typedef std::list MaximaList; + MaximaList m_Maxima; + TEdge *m_SortedEdges; + bool m_ExecuteLocked; + PolyFillType m_ClipFillType; + PolyFillType m_SubjFillType; + bool m_ReverseOutput; + bool m_UsingPolyTree; + bool m_StrictSimple; +#ifdef use_xyz + ZFillCallback m_ZFill; // custom callback +#endif + void SetWindingCount(TEdge &edge); + bool IsEvenOddFillType(const TEdge &edge) const; + bool IsEvenOddAltFillType(const TEdge &edge) const; + void InsertLocalMinimaIntoAEL(const cInt botY); + void InsertEdgeIntoAEL(TEdge *edge, TEdge *startEdge); + void AddEdgeToSEL(TEdge *edge); + bool PopEdgeFromSEL(TEdge *&edge); + void CopyAELToSEL(); + void DeleteFromSEL(TEdge *e); + void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2); + bool IsContributing(const TEdge &edge) const; + bool IsTopHorz(const cInt XPos); + void DoMaxima(TEdge *e); + void ProcessHorizontals(); + void ProcessHorizontal(TEdge *horzEdge); + void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutPt *AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt); + OutRec *GetOutRec(int idx); + void AppendPolygon(TEdge *e1, TEdge *e2); + void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt); + OutPt *AddOutPt(TEdge *e, const IntPoint &pt); + OutPt *GetLastOutPt(TEdge *e); + bool ProcessIntersections(const cInt topY); + void BuildIntersectList(const cInt topY); + void ProcessIntersectList(); + void ProcessEdgesAtTopOfScanbeam(const cInt topY); + void BuildResult(Paths &polys); + void BuildResult2(PolyTree &polytree); + void SetHoleState(TEdge *e, OutRec *outrec); + void DisposeIntersectNodes(); + bool FixupIntersectionOrder(); + void FixupOutPolygon(OutRec &outrec); + void FixupOutPolyline(OutRec &outrec); + bool IsHole(TEdge *e); + bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl); + void FixHoleLinkage(OutRec &outrec); + void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt); + void ClearJoins(); + void ClearGhostJoins(); + void AddGhostJoin(OutPt *op, const IntPoint offPt); + bool JoinPoints(Join *j, OutRec *outRec1, OutRec *outRec2); + void JoinCommonEdges(); + void DoSimplePolygons(); + void FixupFirstLefts1(OutRec *OldOutRec, OutRec *NewOutRec); + void FixupFirstLefts2(OutRec *InnerOutRec, OutRec *OuterOutRec); + void FixupFirstLefts3(OutRec *OldOutRec, OutRec *NewOutRec); +#ifdef use_xyz + void SetZ(IntPoint &pt, TEdge &e1, TEdge &e2); +#endif +}; +//------------------------------------------------------------------------------ + +class ClipperOffset { +public: + ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25); + ~ClipperOffset(); + void AddPath(const Path &path, JoinType joinType, EndType endType); + void AddPaths(const Paths &paths, JoinType joinType, EndType endType); + void Execute(Paths &solution, double delta); + void Execute(PolyTree &solution, double delta); + void Clear(); + double MiterLimit; + double ArcTolerance; + +private: + Paths m_destPolys; + Path m_srcPoly; + Path m_destPoly; + std::vector m_normals; + double m_delta, m_sinA, m_sin, m_cos; + double m_miterLim, m_StepsPerRad; + IntPoint m_lowest; + PolyNode m_polyNodes; + + void FixOrientations(); + void DoOffset(double delta); + void OffsetPoint(int j, int &k, JoinType jointype); + void DoSquare(int j, int k); + void DoMiter(int j, int k, double r); + void DoRound(int j, int k); +}; +//------------------------------------------------------------------------------ + +class clipperException : public std::exception { +public: + clipperException(const char *description) : m_descr(description) {} + virtual ~clipperException() throw() {} + virtual const char *what() const throw() { return m_descr.c_str(); } + +private: + std::string m_descr; +}; +//------------------------------------------------------------------------------ + +} // ClipperLib namespace + #endif // clipper_hpp \ No newline at end of file diff --git a/examples/ppocr-det/cpp/src/main.cpp b/examples/ppocr-det/cpp/src/main.cpp index 4cb4dc2..c93f8ce 100755 --- a/examples/ppocr-det/cpp/src/main.cpp +++ b/examples/ppocr-det/cpp/src/main.cpp @@ -1,91 +1,91 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include "nn_sdk.h" -#include "model_loader.h" -#include "postprocess.h" - -const std::string DEFAULT_OUTPUT_PATH = "result.png"; - -int main(int argc, char** argv) { - if (argc < 3) { - printf("Usage: %s [output_path]\n", argv[0]); - return -1; - } - - std::string model_path = argv[1]; - std::string image_path = argv[2]; - std::string output_path = (argc > 3) ? argv[3] : DEFAULT_OUTPUT_PATH; - - printf("Model: %s\n", model_path.c_str()); - printf("Image: %s\n", image_path.c_str()); - - // 1. Initialize Network - void* ctx = init_network(model_path.c_str()); - if (!ctx) { - fprintf(stderr, "Failed to initialize network\n"); - return -1; - } - - // 2. Load Image - cv::Mat img = cv::imread(image_path); - if (img.empty()) { - fprintf(stderr, "Failed to load image: %s\n", image_path.c_str()); - uninit_network(ctx); - return -1; - } - - // 3. Preprocess - auto start_time = std::chrono::high_resolution_clock::now(); - cv::Mat pre_image; - float scale = 1.0f; - preprocess(img, pre_image, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT, scale); - - printf("scale: %f\n", scale); - // 4. Inference - nn_output* outdata = (nn_output*)run_paddleocr_network(ctx, pre_image, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT, MODEL_INPUT_CHANNELS); - if (!outdata) { - fprintf(stderr, "Inference failed\n"); - uninit_network(ctx); - return -1; - } - - // 5. Postprocess - float* out0 = (float*)outdata->out[0].buf; - - std::vector results; - postprocess(out0, img, BOX_SCORE_THRESH, BOX_THRESH, results, scale); - - auto end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration inference_time = end_time - start_time; - printf("Inference + Postprocess time: %.2f ms\n", inference_time.count()); - printf("Results: %zu\n", results.size()); - - // 6. Draw and Save - cv::Mat res = draw_objects(img, results); - cv::imwrite(output_path, res); - printf("Saved result to %s\n", output_path.c_str()); - - // 7. Cleanup - uninit_network(ctx); - - return 0; -} +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "nn_sdk.h" +#include "model_loader.h" +#include "postprocess.h" + +const std::string DEFAULT_OUTPUT_PATH = "result.png"; + +int main(int argc, char** argv) { + if (argc < 3) { + printf("Usage: %s [output_path]\n", argv[0]); + return -1; + } + + std::string model_path = argv[1]; + std::string image_path = argv[2]; + std::string output_path = (argc > 3) ? argv[3] : DEFAULT_OUTPUT_PATH; + + printf("Model: %s\n", model_path.c_str()); + printf("Image: %s\n", image_path.c_str()); + + // 1. Initialize Network + void* ctx = init_network(model_path.c_str()); + if (!ctx) { + fprintf(stderr, "Failed to initialize network\n"); + return -1; + } + + // 2. Load Image + cv::Mat img = cv::imread(image_path); + if (img.empty()) { + fprintf(stderr, "Failed to load image: %s\n", image_path.c_str()); + uninit_network(ctx); + return -1; + } + + // 3. Preprocess + auto start_time = std::chrono::high_resolution_clock::now(); + cv::Mat pre_image; + float scale = 1.0f; + preprocess(img, pre_image, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT, scale); + + printf("scale: %f\n", scale); + // 4. Inference + nn_output* outdata = (nn_output*)run_paddleocr_network(ctx, pre_image, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT, MODEL_INPUT_CHANNELS); + if (!outdata) { + fprintf(stderr, "Inference failed\n"); + uninit_network(ctx); + return -1; + } + + // 5. Postprocess + float* out0 = (float*)outdata->out[0].buf; + + std::vector results; + postprocess(out0, img, BOX_SCORE_THRESH, BOX_THRESH, results, scale); + + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration inference_time = end_time - start_time; + printf("Inference + Postprocess time: %.2f ms\n", inference_time.count()); + printf("Results: %zu\n", results.size()); + + // 6. Draw and Save + cv::Mat res = draw_objects(img, results); + cv::imwrite(output_path, res); + printf("Saved result to %s\n", output_path.c_str()); + + // 7. Cleanup + uninit_network(ctx); + + return 0; +} diff --git a/examples/ppocr-det/cpp/src/postprocess.cpp b/examples/ppocr-det/cpp/src/postprocess.cpp index a672f89..308f4cf 100755 --- a/examples/ppocr-det/cpp/src/postprocess.cpp +++ b/examples/ppocr-det/cpp/src/postprocess.cpp @@ -1,272 +1,272 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" - -#include "clipper.h" -#include -#include -#include - -int preprocess(const cv::Mat& image, cv::Mat& pre_image, const int width, const int height, float& scale ) { - if (image.empty() || width <= 0 || height <= 0) - return -1; - - int w = image.cols; - int h = image.rows; - float width_ratio = (float) image.cols / width; - float height_ratio = (float) image.rows / height; - float ratio_max = std::max(width_ratio, height_ratio); - - int new_w = std::min(int(image.cols / ratio_max), w); - int new_h = std::min(int(image.rows / ratio_max), h); - - cv::Mat resized_img; - cv::resize(image, resized_img, cv::Size(new_w, new_h)); - - pre_image = cv::Mat::zeros(height, width, CV_8UC3); - cv::Rect roi_rect = cv::Rect(0, 0, new_w, new_h); - - resized_img.copyTo(pre_image(roi_rect)); - scale = ratio_max; - - return 0; -} - -int postprocess(float* out, const cv::Mat& image, float box_score_thresh, float box_thresh, std::vector& result, float scale) { - - if (out == NULL) - return -1; - cv::Mat pred_map(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH, CV_32FC1, out); - - cv::Mat bit_map; - bit_map = pred_map > box_thresh; - cv::Mat dila_ele = - cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); - cv::dilate(bit_map, bit_map, dila_ele, cv::Point(-1, -1), 1); - - result = find_box(pred_map, bit_map, box_score_thresh, 1.5f, image, scale); - - return 0; -} - -void* run_paddleocr_network(void* qcontext, cv::Mat& image, const int width, const int height, const int channel) { - unsigned char* rawdata = image.data; - - nn_input inData; - memset(&inData, 0, sizeof(nn_input)); - inData.input_type = BINARY_RAW_DATA; - inData.input = rawdata; - inData.input_index = 0; - inData.size = width * height * channel * sizeof(uint8_t); - - int ret = aml_module_input_set(qcontext, &inData); - if (ret) { - printf("aml_module_input_set fail for index %d. Ret=%d", 0, ret); - return NULL; - } - - aml_output_config_t outconfig; - memset(&outconfig, 0, sizeof(aml_output_config_t)); - outconfig.typeSize = sizeof(aml_output_config_t); - outconfig.format = AML_OUTDATA_FLOAT32; - return aml_module_output_get(qcontext, outconfig); -} - -std::vector find_box(const cv::Mat pred_map, const cv::Mat& bit_map, - const float box_score_thresh, const float unclip_ratio, - const cv::Mat& image, float scale) { - - std::vector res_boxes; - res_boxes.clear(); - - std::vector> contours; - cv::findContours(bit_map, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); - - int num_coutours = contours.size() >= MAX_CANDIDATES ? MAX_CANDIDATES : contours.size(); - - for (int i = 0; i < num_coutours; i++) { - if (contours[i].size() <= 2) { - continue; - } - - float min_side_len; - float perimeter; - Object text_box; - - std::vector min_box = get_min_boxes(contours[i], min_side_len, perimeter); - - if (min_side_len < MIN_SIZE) - continue; - - // score - float score = get_box_score_fast(pred_map, contours[i]); - - if (score < box_score_thresh) - continue; - - //--- use clipper - std::vector clip_box = unclip(min_box, perimeter, unclip_ratio); - std::vector clip_min_box = get_min_boxes(clip_box, min_side_len, perimeter); - - if (min_side_len < MIN_SIZE + 2) - continue; - - for (int j = 0; j < clip_min_box.size(); ++j) { - clip_min_box[j].x = (float)(clip_min_box[j].x / 1.0f); - clip_min_box[j].x = std::min(std::max(int(clip_min_box[j].x * scale), 0), image.cols); - - clip_min_box[j].y = (float)(clip_min_box[j].y / 1.0f); - clip_min_box[j].y = std::min(std::max(int(clip_min_box[j].y * scale), 0), image.rows); - - text_box.box.push_back(clip_min_box[j]); - } - - text_box.score = score; - // printf("text detect:%f \n", score); - res_boxes.push_back(text_box); - } - - return res_boxes; -} - -std::vector get_min_boxes(const std::vector& in_vec, float& min_side_len, float& perimeter) { - std::vector min_box_vec; - cv::RotatedRect text_rect = cv::minAreaRect(in_vec); - cv::Mat box_point2f; - cv::boxPoints(text_rect, box_point2f); - - float* p1 = (float*) box_point2f.data; - std::vector temp_vec; - - for (int i = 0; i < 4; ++i, p1 += 2) { - temp_vec.emplace_back(int(p1[0]), int(p1[1])); - } - - std::sort(temp_vec.begin(), temp_vec.end(), cv_point_compare); - - int index1, index2, index3, index4; - - if (temp_vec[1].y > temp_vec[0].y) { - index1 = 0; - index4 = 1; - } - else { - index1 = 1; - index4 = 0; - } - - if (temp_vec[3].y > temp_vec[2].y) { - index2 = 2; - index3 = 3; - } - else { - index2 = 3; - index3 = 2; - } - - min_box_vec.clear(); - - min_box_vec.push_back(temp_vec[index1]); - min_box_vec.push_back(temp_vec[index2]); - min_box_vec.push_back(temp_vec[index3]); - min_box_vec.push_back(temp_vec[index4]); - - min_side_len = std::min(text_rect.size.width, text_rect.size.height); - perimeter = 2.f * (text_rect.size.width + text_rect.size.height); - - return min_box_vec; -} - -float get_box_score_fast(const cv::Mat& in_mat, const std::vector& in_box) { - std::vector box = in_box; - int width = in_mat.cols; - int height = in_mat.rows; - - int max_x = -1; - int max_y = -1; - int min_x = std::numeric_limits::max(); - int min_y = std::numeric_limits::max(); - - for (int i = 0; i < box.size(); ++i) { - if (max_x < box[i].x) - max_x = box[i].x; - if (max_y < box[i].y) - max_y = box[i].y; - if (min_x > box[i].x) - min_x = box[i].x; - if (min_y > box[i].y) - min_y = box[i].y; - } - - max_x = std::min(std::max(max_x, 0), width - 1); - max_y = std::min(std::max(max_y, 0), height - 1); - min_x = std::max(std::min(min_x, width - 1), 0); - min_y = std::max(std::min(min_y, height - 1), 0); - - for (int i = 0; i < box.size(); ++i) { - box[i].x = box[i].x - min_x; - box[i].y = box[i].y - min_y; - } - - std::vector> mask_box; - mask_box.push_back(box); - - cv::Mat mask_mat(max_y - min_y + 1, max_x - min_x + 1, CV_8UC1, cv::Scalar(0, 0, 0)); - cv::fillPoly(mask_mat, mask_box, cv::Scalar(1, 1, 1), 1); - - return cv::mean(in_mat(cv::Rect(cv::Point(min_x, min_y), cv::Point(max_x + 1, max_y + 1))).clone(), mask_mat).val[0]; -} - -std::vector unclip(const std::vector& in_box, float perimeter, float unclip_ratio) { - std::vector out_box; - ClipperLib::Path poly; - - for (int i = 0; i < in_box.size(); ++i) { - poly.push_back(ClipperLib::IntPoint(in_box[i].x, in_box[i].y)); - } - - double distance = unclip_ratio * ClipperLib::Area(poly) / (double) perimeter; - - ClipperLib::ClipperOffset clipper_offset; - clipper_offset.AddPath(poly, ClipperLib::JoinType::jtRound, ClipperLib::EndType::etClosedPolygon); - ClipperLib::Paths polys; - polys.push_back(poly); - clipper_offset.Execute(polys, distance); - - out_box.clear(); - - for (int i = 0; i < polys.size(); ++i) { - ClipperLib::Path temp_poly = polys[i]; - for (int j = 0; j < temp_poly.size(); ++j) { - out_box.emplace_back(temp_poly[j].X, temp_poly[j].Y); - } - } - - return out_box; -} - -bool cv_point_compare(const cv::Point& a, const cv::Point& b) { - return a.x < b.x; -} - -cv::Mat draw_objects(cv::Mat image, const std::vector& results) { - for (int i = 0; i < results.size(); i++) { - cv::polylines(image, results[i].box, true, cv::Scalar(0, 0, 255), 2); - } - - return image; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" + +#include "clipper.h" +#include +#include +#include + +int preprocess(const cv::Mat& image, cv::Mat& pre_image, const int width, const int height, float& scale ) { + if (image.empty() || width <= 0 || height <= 0) + return -1; + + int w = image.cols; + int h = image.rows; + float width_ratio = (float) image.cols / width; + float height_ratio = (float) image.rows / height; + float ratio_max = std::max(width_ratio, height_ratio); + + int new_w = std::min(int(image.cols / ratio_max), w); + int new_h = std::min(int(image.rows / ratio_max), h); + + cv::Mat resized_img; + cv::resize(image, resized_img, cv::Size(new_w, new_h)); + + pre_image = cv::Mat::zeros(height, width, CV_8UC3); + cv::Rect roi_rect = cv::Rect(0, 0, new_w, new_h); + + resized_img.copyTo(pre_image(roi_rect)); + scale = ratio_max; + + return 0; +} + +int postprocess(float* out, const cv::Mat& image, float box_score_thresh, float box_thresh, std::vector& result, float scale) { + + if (out == NULL) + return -1; + cv::Mat pred_map(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH, CV_32FC1, out); + + cv::Mat bit_map; + bit_map = pred_map > box_thresh; + cv::Mat dila_ele = + cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); + cv::dilate(bit_map, bit_map, dila_ele, cv::Point(-1, -1), 1); + + result = find_box(pred_map, bit_map, box_score_thresh, 1.5f, image, scale); + + return 0; +} + +void* run_paddleocr_network(void* qcontext, cv::Mat& image, const int width, const int height, const int channel) { + unsigned char* rawdata = image.data; + + nn_input inData; + memset(&inData, 0, sizeof(nn_input)); + inData.input_type = BINARY_RAW_DATA; + inData.input = rawdata; + inData.input_index = 0; + inData.size = width * height * channel * sizeof(uint8_t); + + int ret = aml_module_input_set(qcontext, &inData); + if (ret) { + printf("aml_module_input_set fail for index %d. Ret=%d", 0, ret); + return NULL; + } + + aml_output_config_t outconfig; + memset(&outconfig, 0, sizeof(aml_output_config_t)); + outconfig.typeSize = sizeof(aml_output_config_t); + outconfig.format = AML_OUTDATA_FLOAT32; + return aml_module_output_get(qcontext, outconfig); +} + +std::vector find_box(const cv::Mat pred_map, const cv::Mat& bit_map, + const float box_score_thresh, const float unclip_ratio, + const cv::Mat& image, float scale) { + + std::vector res_boxes; + res_boxes.clear(); + + std::vector> contours; + cv::findContours(bit_map, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE); + + int num_coutours = contours.size() >= MAX_CANDIDATES ? MAX_CANDIDATES : contours.size(); + + for (int i = 0; i < num_coutours; i++) { + if (contours[i].size() <= 2) { + continue; + } + + float min_side_len; + float perimeter; + Object text_box; + + std::vector min_box = get_min_boxes(contours[i], min_side_len, perimeter); + + if (min_side_len < MIN_SIZE) + continue; + + // score + float score = get_box_score_fast(pred_map, contours[i]); + + if (score < box_score_thresh) + continue; + + //--- use clipper + std::vector clip_box = unclip(min_box, perimeter, unclip_ratio); + std::vector clip_min_box = get_min_boxes(clip_box, min_side_len, perimeter); + + if (min_side_len < MIN_SIZE + 2) + continue; + + for (int j = 0; j < clip_min_box.size(); ++j) { + clip_min_box[j].x = (float)(clip_min_box[j].x / 1.0f); + clip_min_box[j].x = std::min(std::max(int(clip_min_box[j].x * scale), 0), image.cols); + + clip_min_box[j].y = (float)(clip_min_box[j].y / 1.0f); + clip_min_box[j].y = std::min(std::max(int(clip_min_box[j].y * scale), 0), image.rows); + + text_box.box.push_back(clip_min_box[j]); + } + + text_box.score = score; + // printf("text detect:%f \n", score); + res_boxes.push_back(text_box); + } + + return res_boxes; +} + +std::vector get_min_boxes(const std::vector& in_vec, float& min_side_len, float& perimeter) { + std::vector min_box_vec; + cv::RotatedRect text_rect = cv::minAreaRect(in_vec); + cv::Mat box_point2f; + cv::boxPoints(text_rect, box_point2f); + + float* p1 = (float*) box_point2f.data; + std::vector temp_vec; + + for (int i = 0; i < 4; ++i, p1 += 2) { + temp_vec.emplace_back(int(p1[0]), int(p1[1])); + } + + std::sort(temp_vec.begin(), temp_vec.end(), cv_point_compare); + + int index1, index2, index3, index4; + + if (temp_vec[1].y > temp_vec[0].y) { + index1 = 0; + index4 = 1; + } + else { + index1 = 1; + index4 = 0; + } + + if (temp_vec[3].y > temp_vec[2].y) { + index2 = 2; + index3 = 3; + } + else { + index2 = 3; + index3 = 2; + } + + min_box_vec.clear(); + + min_box_vec.push_back(temp_vec[index1]); + min_box_vec.push_back(temp_vec[index2]); + min_box_vec.push_back(temp_vec[index3]); + min_box_vec.push_back(temp_vec[index4]); + + min_side_len = std::min(text_rect.size.width, text_rect.size.height); + perimeter = 2.f * (text_rect.size.width + text_rect.size.height); + + return min_box_vec; +} + +float get_box_score_fast(const cv::Mat& in_mat, const std::vector& in_box) { + std::vector box = in_box; + int width = in_mat.cols; + int height = in_mat.rows; + + int max_x = -1; + int max_y = -1; + int min_x = std::numeric_limits::max(); + int min_y = std::numeric_limits::max(); + + for (int i = 0; i < box.size(); ++i) { + if (max_x < box[i].x) + max_x = box[i].x; + if (max_y < box[i].y) + max_y = box[i].y; + if (min_x > box[i].x) + min_x = box[i].x; + if (min_y > box[i].y) + min_y = box[i].y; + } + + max_x = std::min(std::max(max_x, 0), width - 1); + max_y = std::min(std::max(max_y, 0), height - 1); + min_x = std::max(std::min(min_x, width - 1), 0); + min_y = std::max(std::min(min_y, height - 1), 0); + + for (int i = 0; i < box.size(); ++i) { + box[i].x = box[i].x - min_x; + box[i].y = box[i].y - min_y; + } + + std::vector> mask_box; + mask_box.push_back(box); + + cv::Mat mask_mat(max_y - min_y + 1, max_x - min_x + 1, CV_8UC1, cv::Scalar(0, 0, 0)); + cv::fillPoly(mask_mat, mask_box, cv::Scalar(1, 1, 1), 1); + + return cv::mean(in_mat(cv::Rect(cv::Point(min_x, min_y), cv::Point(max_x + 1, max_y + 1))).clone(), mask_mat).val[0]; +} + +std::vector unclip(const std::vector& in_box, float perimeter, float unclip_ratio) { + std::vector out_box; + ClipperLib::Path poly; + + for (int i = 0; i < in_box.size(); ++i) { + poly.push_back(ClipperLib::IntPoint(in_box[i].x, in_box[i].y)); + } + + double distance = unclip_ratio * ClipperLib::Area(poly) / (double) perimeter; + + ClipperLib::ClipperOffset clipper_offset; + clipper_offset.AddPath(poly, ClipperLib::JoinType::jtRound, ClipperLib::EndType::etClosedPolygon); + ClipperLib::Paths polys; + polys.push_back(poly); + clipper_offset.Execute(polys, distance); + + out_box.clear(); + + for (int i = 0; i < polys.size(); ++i) { + ClipperLib::Path temp_poly = polys[i]; + for (int j = 0; j < temp_poly.size(); ++j) { + out_box.emplace_back(temp_poly[j].X, temp_poly[j].Y); + } + } + + return out_box; +} + +bool cv_point_compare(const cv::Point& a, const cv::Point& b) { + return a.x < b.x; +} + +cv::Mat draw_objects(cv::Mat image, const std::vector& results) { + for (int i = 0; i < results.size(); i++) { + cv::polylines(image, results[i].box, true, cv::Scalar(0, 0, 255), 2); + } + + return image; } \ No newline at end of file diff --git a/examples/ppocr-det/cpp/src/postprocess.h b/examples/ppocr-det/cpp/src/postprocess.h index 23dbcf7..c7a030f 100755 --- a/examples/ppocr-det/cpp/src/postprocess.h +++ b/examples/ppocr-det/cpp/src/postprocess.h @@ -1,61 +1,61 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef __AMLNN_PADDLEOCR_DET_DEMO_POSTPROCESS_H__ -#define __AMLNN_PADDLEOCR_DET_DEMO_POSTPROCESS_H__ - -#include -#include - -#include "nn_sdk.h" - -const int MIN_SIZE = 3; -const int MAX_CANDIDATES = 1000; -const int MODEL_INPUT_WIDTH = 640; -const int MODEL_INPUT_HEIGHT = 640; -const int MODEL_INPUT_CHANNELS = 3; -const float BOX_SCORE_THRESH = 0.5; -const float BOX_THRESH = 0.3; - -typedef struct { - float score; - - std::vector box; -} Object; - -void* run_paddleocr_network(void* qcontext, cv::Mat& image, const int width, const int height, const int channel); - -int preprocess(const cv::Mat& image, cv::Mat& pre_image, const int width, const int height, float& scale ); - -int postprocess(float* out, const cv::Mat& image, float box_score_thresh, float box_thresh, std::vector& result, float scale); - -std::vector find_box(const cv::Mat pred_map, const cv::Mat& bit_map, - const float box_score_thresh, const float unclip_ratio, - const cv::Mat& image, float scale); - -std::vector get_min_boxes(const std::vector& in_vec, - float& min_side_len, float& perimeter); - -float get_box_score_fast(const cv::Mat& in_mat, const std::vector& in_box); - -std::vector unclip(const std::vector& in_box, float perimeter, float unclip_ratio); - -bool cv_point_compare(const cv::Point& a, const cv::Point& b); - -cv::Mat draw_objects(cv::Mat image, const std::vector& results); - - +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef __AMLNN_PADDLEOCR_DET_DEMO_POSTPROCESS_H__ +#define __AMLNN_PADDLEOCR_DET_DEMO_POSTPROCESS_H__ + +#include +#include + +#include "nn_sdk.h" + +const int MIN_SIZE = 3; +const int MAX_CANDIDATES = 1000; +const int MODEL_INPUT_WIDTH = 640; +const int MODEL_INPUT_HEIGHT = 640; +const int MODEL_INPUT_CHANNELS = 3; +const float BOX_SCORE_THRESH = 0.5; +const float BOX_THRESH = 0.3; + +typedef struct { + float score; + + std::vector box; +} Object; + +void* run_paddleocr_network(void* qcontext, cv::Mat& image, const int width, const int height, const int channel); + +int preprocess(const cv::Mat& image, cv::Mat& pre_image, const int width, const int height, float& scale ); + +int postprocess(float* out, const cv::Mat& image, float box_score_thresh, float box_thresh, std::vector& result, float scale); + +std::vector find_box(const cv::Mat pred_map, const cv::Mat& bit_map, + const float box_score_thresh, const float unclip_ratio, + const cv::Mat& image, float scale); + +std::vector get_min_boxes(const std::vector& in_vec, + float& min_side_len, float& perimeter); + +float get_box_score_fast(const cv::Mat& in_mat, const std::vector& in_box); + +std::vector unclip(const std::vector& in_box, float perimeter, float unclip_ratio); + +bool cv_point_compare(const cv::Point& a, const cv::Point& b); + +cv::Mat draw_objects(cv::Mat image, const std::vector& results); + + #endif \ No newline at end of file diff --git a/examples/ppocr-det/model/.gitkeep b/examples/ppocr-det/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/ppocr-det/py/.gitkeep b/examples/ppocr-det/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/resnet/README.md b/examples/resnet/README.md index defa581..786257b 100755 --- a/examples/resnet/README.md +++ b/examples/resnet/README.md @@ -1,165 +1,165 @@ -# resnet - -## 1.Overview - - - -## 2.Model Download - -- **Open Source model** - - - **Open Source projects:** - - - **Export Model Step:** - - - **Install ultralytics** - - pip install torch==2.4.1 - - pip install torchvision==0.19.1 - - pip install ultralytics==8.3.0 - - - **Download weights** - - - - - **Export Model** - - ``` - - ``` - - -- **Exported Model** - - ​ link to amlogic server( **onnx model or quantized tflite**) - - - -## 3. Model Conversion - -``` -cd model -Usage: ./adla_covnert.sh model_path adla_tookkit_path target_platform - -example - -``` - -| Parameter | Discription | -| ----------------- | ------------------------------------------------------------ | -| model_path | onnx model path | -| adla_tookkit_path | path to adla_toolkit | -| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | - - - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/resnet/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/resnet_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/resnet_demo /data/local/tmp/ -adb push model/res2net50_int8_A311D2.adla /data/local/tmp/ -adb push imgs /data/local/tmp/ -adb push labels.txt /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x resnet_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./resnet_demo -./resnet_demo res2net50_int8_A311D2.adla imgs/ labels.txt -``` - -**Note:** Replace `res2net50_int8_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `opencv-python`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -python resnet.py \ - --model-path ./res2net50_int8_A311D2.adla \ - --image-dir ./imgs \ - --labels labels.txt \ - --run-cycles 1 \ - --loglevel INFO -``` -Argument Descriptions: -| Argument | Description | -| ----------------- | ------------------------------------------------------------ | -| --board-work-path | Work path on board, default is /data/local/tmp | -| --model-path | path to .adla model | -| --image-dir | Directory containing test images | -| --labels | Path to synset_words.txt or labels.txt | -| --run-cycles | Number of inference cycles, default is 1 | -| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. - -## 5.Results -**Performance Feedback** - -By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: -- Hardware Information: System and ADLA library versions. -- Model Overview: Basic input/output configurations. -- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. - -**Classification Output** - -For each image, the program prints the Top-5 classification results with their respective scores: -```bash -============================================================ -Processing image 1/1: dog.jpg -============================================================ Top-5 Results: -1: Pekinese score=9.851644 -2: West Highland white terrier score=5.055449 -3: Maltese dog score=4.796195 -4: basenji score=3.111045 -5: Scotch terrier score=2.786978 ============================================================ -``` -**Profiling Visualization** - -After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: -- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. -- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. -- `pie_charts_distribution.html`: Overall resource allocation. - -You can pull the result folder back to view it: -```bash -adb pull /data/local/tmp/res2net50_int8_A311D2 -``` - -Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. - +# resnet + +## 1.Overview + + + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** + + - **Export Model Step:** + + - **Install ultralytics** + + pip install torch==2.4.1 + + pip install torchvision==0.19.1 + + pip install ultralytics==8.3.0 + + - **Download weights** + + + + - **Export Model** + + ``` + + ``` + + +- **Exported Model** + + ​ link to amlogic server( **onnx model or quantized tflite**) + + + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/resnet/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/resnet_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/resnet_demo /data/local/tmp/ +adb push model/res2net50_int8_A311D2.adla /data/local/tmp/ +adb push imgs /data/local/tmp/ +adb push labels.txt /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x resnet_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./resnet_demo +./resnet_demo res2net50_int8_A311D2.adla imgs/ labels.txt +``` + +**Note:** Replace `res2net50_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python resnet.py \ + --model-path ./res2net50_int8_A311D2.adla \ + --image-dir ./imgs \ + --labels labels.txt \ + --run-cycles 1 \ + --loglevel INFO +``` +Argument Descriptions: +| Argument | Description | +| ----------------- | ------------------------------------------------------------ | +| --board-work-path | Work path on board, default is /data/local/tmp | +| --model-path | path to .adla model | +| --image-dir | Directory containing test images | +| --labels | Path to synset_words.txt or labels.txt | +| --run-cycles | Number of inference cycles, default is 1 | +| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results +**Performance Feedback** + +By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: +- Hardware Information: System and ADLA library versions. +- Model Overview: Basic input/output configurations. +- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. + +**Classification Output** + +For each image, the program prints the Top-5 classification results with their respective scores: +```bash +============================================================ +Processing image 1/1: dog.jpg +============================================================ Top-5 Results: +1: Pekinese score=9.851644 +2: West Highland white terrier score=5.055449 +3: Maltese dog score=4.796195 +4: basenji score=3.111045 +5: Scotch terrier score=2.786978 ============================================================ +``` +**Profiling Visualization** + +After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: +- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. +- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. +- `pie_charts_distribution.html`: Overall resource allocation. + +You can pull the result folder back to view it: +```bash +adb pull /data/local/tmp/res2net50_int8_A311D2 +``` + +Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. + ![alt text](Visualization.png) \ No newline at end of file diff --git a/examples/resnet/cpp/src/CMakeLists.txt b/examples/resnet/cpp/src/CMakeLists.txt index 2f9e546..093aa78 100755 --- a/examples/resnet/cpp/src/CMakeLists.txt +++ b/examples/resnet/cpp/src/CMakeLists.txt @@ -1,35 +1,35 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(resnet_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set dependency path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(resnet_demo - main.cpp - postprocess.cpp - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(resnet_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} +cmake_minimum_required(VERSION 3.10...3.27) +project(resnet_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set dependency path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(resnet_demo + main.cpp + postprocess.cpp + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(resnet_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} ) \ No newline at end of file diff --git a/examples/resnet/cpp/src/main.cpp b/examples/resnet/cpp/src/main.cpp index 4fda6c0..522f774 100755 --- a/examples/resnet/cpp/src/main.cpp +++ b/examples/resnet/cpp/src/main.cpp @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include #include #include @@ -57,7 +57,7 @@ int main(int argc, char** argv) { in.input = (unsigned char*)input_buffer.data(); in.size = input_buffer.size() * sizeof(float); in.info.valid = 1; - in.info.input_format = AML_INPUT_MODEL_NHWC; + in.info.input_format = AML_INPUT_MODEL_NHWC; in.info.input_data_type = AML_INPUT_FP32; aml_module_input_set(ctx, &in); diff --git a/examples/resnet/cpp/src/postprocess.cpp b/examples/resnet/cpp/src/postprocess.cpp index 11e030e..1db2ca5 100755 --- a/examples/resnet/cpp/src/postprocess.cpp +++ b/examples/resnet/cpp/src/postprocess.cpp @@ -1,62 +1,62 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" -#include -#include -#include -#include - -void preprocess(const cv::Mat& src, float* dst) { - cv::Mat rgb, resized; - cv::cvtColor(src, rgb, cv::COLOR_BGR2RGB); - cv::resize(rgb, resized, cv::Size(kInputW, kInputH)); - - for (int i = 0; i < kInputH; ++i) { - for (int j = 0; j < kInputW; ++j) { - cv::Vec3f pixel = resized.at(i, j); - int idx = (i * kInputW + j) * 3; - dst[idx + 0] = (pixel[0] - MEAN[0]) / STD[0]; - dst[idx + 1] = (pixel[1] - MEAN[1]) / STD[1]; - dst[idx + 2] = (pixel[2] - MEAN[2]) / STD[2]; - } - } -} - -void postprocess_topk(float* logits, int size, const std::vector& labels, int k) { - std::vector indices(size); - std::iota(indices.begin(), indices.end(), 0); - - std::partial_sort(indices.begin(), indices.begin() + k, indices.end(), - [&](int a, int b) { return logits[a] > logits[b]; }); - - std::cout << "\nTop-" << k << " Results:" << std::endl; - for (int i = 0; i < k; ++i) { - int idx = indices[i]; - std::string name = (idx < (int)labels.size()) ? labels[idx] : "N/A"; - printf("%d: %-20s score=%.6f\n", i + 1, name.c_str(), logits[idx]); - } -} - -std::vector load_labels(const std::string& path) { - std::vector labels; - std::ifstream f(path); - std::string line; - while (std::getline(f, line)) { - if(!line.empty()) labels.push_back(line); - } - return labels; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" +#include +#include +#include +#include + +void preprocess(const cv::Mat& src, float* dst) { + cv::Mat rgb, resized; + cv::cvtColor(src, rgb, cv::COLOR_BGR2RGB); + cv::resize(rgb, resized, cv::Size(kInputW, kInputH)); + + for (int i = 0; i < kInputH; ++i) { + for (int j = 0; j < kInputW; ++j) { + cv::Vec3f pixel = resized.at(i, j); + int idx = (i * kInputW + j) * 3; + dst[idx + 0] = (pixel[0] - MEAN[0]) / STD[0]; + dst[idx + 1] = (pixel[1] - MEAN[1]) / STD[1]; + dst[idx + 2] = (pixel[2] - MEAN[2]) / STD[2]; + } + } +} + +void postprocess_topk(float* logits, int size, const std::vector& labels, int k) { + std::vector indices(size); + std::iota(indices.begin(), indices.end(), 0); + + std::partial_sort(indices.begin(), indices.begin() + k, indices.end(), + [&](int a, int b) { return logits[a] > logits[b]; }); + + std::cout << "\nTop-" << k << " Results:" << std::endl; + for (int i = 0; i < k; ++i) { + int idx = indices[i]; + std::string name = (idx < (int)labels.size()) ? labels[idx] : "N/A"; + printf("%d: %-20s score=%.6f\n", i + 1, name.c_str(), logits[idx]); + } +} + +std::vector load_labels(const std::string& path) { + std::vector labels; + std::ifstream f(path); + std::string line; + while (std::getline(f, line)) { + if (!line.empty()) labels.push_back(line); + } + return labels; } \ No newline at end of file diff --git a/examples/resnet/cpp/src/postprocess.h b/examples/resnet/cpp/src/postprocess.h index 8e7936e..c8ec6a6 100755 --- a/examples/resnet/cpp/src/postprocess.h +++ b/examples/resnet/cpp/src/postprocess.h @@ -1,35 +1,35 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef RESNET_POSTPROCESS_H -#define RESNET_POSTPROCESS_H - -#include -#include -#include - -static const float MEAN[3] = {123.675f, 116.28f, 103.53f}; -static const float STD[3] = {58.395f, 58.395f, 58.395f}; -static constexpr int kInputW = 224; -static constexpr int kInputH = 224; - -void preprocess(const cv::Mat& src, float* dst); - -void postprocess_topk(float* logits, int size, const std::vector& labels, int k = 5); - -std::vector load_labels(const std::string& path); - +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef RESNET_POSTPROCESS_H +#define RESNET_POSTPROCESS_H + +#include +#include +#include + +static const float MEAN[3] = {123.675f, 116.28f, 103.53f}; +static const float STD[3] = {58.395f, 58.395f, 58.395f}; +static constexpr int kInputW = 224; +static constexpr int kInputH = 224; + +void preprocess(const cv::Mat& src, float* dst); + +void postprocess_topk(float* logits, int size, const std::vector& labels, int k = 5); + +std::vector load_labels(const std::string& path); + #endif \ No newline at end of file diff --git a/examples/retinaface/README.md b/examples/retinaface/README.md index 1f732b4..17823bb 100755 --- a/examples/retinaface/README.md +++ b/examples/retinaface/README.md @@ -1,160 +1,160 @@ -# retinaface - -## 1.Overview - - -## 2.Model Download - -- **Open Source model** - - - **Open Source projects:** - - - **Export Model Step:** - - - **Install ultralytics** - - pip install torch==2.4.1 - - pip install torchvision==0.19.1 - - pip install ultralytics==8.3.0 - - - **Download weights** - - - - - **Export Model** - - - - -- **Exported Model** - - ​ link to amlogic server( **onnx model or quantized tflite**) - - - -## 3. Model Conversion - -``` -cd model -Usage: ./adla_covnert.sh model_path adla_tookkit_path target_platform - -example - -``` - -| Parameter | Discription | -| ----------------- | ------------------------------------------------------------ | -| model_path | onnx model path | -| adla_tookkit_path | path to adla_toolkit | -| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | - - - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/retinaface/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/retinaface_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/retinaface_demo /data/local/tmp/ -adb push model/RetinaFace_int8_A311D2.adla /data/local/tmp/ -adb push imgs /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x retinaface_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./retinaface_demo -./retinaface_demo RetinaFace_int8_A311D2.adla ./imgs -``` - -**Note:** Replace `RetinaFace_int8_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `opencv-python`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -python RetinaFace.py \ - --model-path ./RetinaFace_int8_A311D2.adla \ - --image-dir ./imgs \ - --run-cycles 1 \ - --loglevel INFO -``` - -Argument Descriptions: -| Argument | Description | -| ----------------- | ------------------------------------------------------------ | -| --board-work-path | Work path on board, default is /data/local/tmp | -| --model-path | path to .adla model | -| --image-dir | Directory containing test images | -| --run-cycles | Number of inference cycles, default is 1 | -| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. - -## 5.Results - -**Performance Feedback** - -By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: -- Hardware Information: System and ADLA library versions. -- Model Overview: Basic input/output configurations. -- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. - -**Detection Output** - -The program will print the detection count. The output images, featuring bounding boxes and five facial landmarks (eyes, nose, and mouth corners), will be saved to the `{model_name}_result` folder. - - -You can pull the result folder back to view it: -```bash -adb pull /data/local/tmp/RetinaFace_int8_A311D2_result -``` -![alt text](result.jpg) - - -**Profiling Visualization** - -After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: -- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. -- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. -- `pie_charts_distribution.html`: Overall resource allocation. - -You can pull the result folder back to view it: -```bash -adb pull /data/local/tmp/RetinaFace_int8_A311D2 -``` - -Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. - -![alt text](Visualization.png) +# retinaface + +## 1.Overview + + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** + + - **Export Model Step:** + + - **Install ultralytics** + + pip install torch==2.4.1 + + pip install torchvision==0.19.1 + + pip install ultralytics==8.3.0 + + - **Download weights** + + + + - **Export Model** + + + + +- **Exported Model** + + ​ link to amlogic server( **onnx model or quantized tflite**) + + + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/retinaface/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/retinaface_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/retinaface_demo /data/local/tmp/ +adb push model/RetinaFace_int8_A311D2.adla /data/local/tmp/ +adb push imgs /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x retinaface_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./retinaface_demo +./retinaface_demo RetinaFace_int8_A311D2.adla ./imgs +``` + +**Note:** Replace `RetinaFace_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python RetinaFace.py \ + --model-path ./RetinaFace_int8_A311D2.adla \ + --image-dir ./imgs \ + --run-cycles 1 \ + --loglevel INFO +``` + +Argument Descriptions: +| Argument | Description | +| ----------------- | ------------------------------------------------------------ | +| --board-work-path | Work path on board, default is /data/local/tmp | +| --model-path | path to .adla model | +| --image-dir | Directory containing test images | +| --run-cycles | Number of inference cycles, default is 1 | +| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results + +**Performance Feedback** + +By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: +- Hardware Information: System and ADLA library versions. +- Model Overview: Basic input/output configurations. +- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. + +**Detection Output** + +The program will print the detection count. The output images, featuring bounding boxes and five facial landmarks (eyes, nose, and mouth corners), will be saved to the `{model_name}_result` folder. + + +You can pull the result folder back to view it: +```bash +adb pull /data/local/tmp/RetinaFace_int8_A311D2_result +``` +![alt text](result.jpg) + + +**Profiling Visualization** + +After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: +- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. +- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. +- `pie_charts_distribution.html`: Overall resource allocation. + +You can pull the result folder back to view it: +```bash +adb pull /data/local/tmp/RetinaFace_int8_A311D2 +``` + +Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. + +![alt text](Visualization.png) diff --git a/examples/retinaface/cpp/src/CMakeLists.txt b/examples/retinaface/cpp/src/CMakeLists.txt index 80053d7..5b26c03 100755 --- a/examples/retinaface/cpp/src/CMakeLists.txt +++ b/examples/retinaface/cpp/src/CMakeLists.txt @@ -1,35 +1,35 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(retinaface_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set dependency path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(retinaface_demo - main.cpp - postprocess.cpp - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(retinaface_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} +cmake_minimum_required(VERSION 3.10...3.27) +project(retinaface_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set dependency path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(retinaface_demo + main.cpp + postprocess.cpp + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(retinaface_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} ) \ No newline at end of file diff --git a/examples/retinaface/cpp/src/main.cpp b/examples/retinaface/cpp/src/main.cpp index 36bfeca..4748905 100755 --- a/examples/retinaface/cpp/src/main.cpp +++ b/examples/retinaface/cpp/src/main.cpp @@ -139,25 +139,25 @@ int main(int argc, char** argv) { auto& b = boxes[k]; int x1 = (b[0] * kInputW - px) / scale, y1 = (b[1] * kInputH - py) / scale; int x2 = (b[2] * kInputW - px) / scale, y2 = (b[3] * kInputH - py) / scale; - + cv::rectangle(img, {x1, y1}, {x2, y2}, {0, 255, 0}, 2); char score_text[16]; std::snprintf(score_text, sizeof(score_text), "%.2f", scores_vec[k]); - cv::putText(img, score_text, {x1, std::max(y1 - 5, 5)}, + cv::putText(img, score_text, {x1, std::max(y1 - 5, 5)}, cv::FONT_HERSHEY_SIMPLEX, 0.5, {0, 255, 0}, 1, cv::LINE_AA); auto& lm = lms[k]; for (int j = 0; j < 5; j++) { int lx = (lm[2 * j] * kInputW - px) / scale; int ly = (lm[2 * j + 1] * kInputH - py) / scale; - cv::circle(img, {lx, ly}, 2, {0, 0, 255}, -1); + cv::circle(img, {lx, ly}, 2, {0, 0, 255}, -1); } } std::string save_path = out_dir + "/" + filename; cv::imwrite(save_path, img); - + std::cout << " Detected " << keep.size() << " faces\n"; std::cout << " Result saved to: " << save_path << "\n\n"; } diff --git a/examples/whisper/README.md b/examples/whisper/README.md index cc96ec1..0f7905f 100644 --- a/examples/whisper/README.md +++ b/examples/whisper/README.md @@ -1,20 +1,20 @@ -# Whisper - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK r25c -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/whisper/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated in `build/android/`. +# Whisper + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK r25c +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/whisper/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated in `build/android/`. diff --git a/examples/whisper/cpp/.gitkeep b/examples/whisper/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/whisper/cpp/src/CMakeLists.txt b/examples/whisper/cpp/src/CMakeLists.txt index b2f1d3d..372305a 100755 --- a/examples/whisper/cpp/src/CMakeLists.txt +++ b/examples/whisper/cpp/src/CMakeLists.txt @@ -1,35 +1,35 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(whisper_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set 3rdparty path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -add_executable(${PROJECT_NAME} - main.cpp - common.cpp - whisper.cpp - whisper_invoke.cpp - pre_process_whisper.cpp - post_process_whisper.cpp -) - -target_link_libraries(${PROJECT_NAME} - ${AMLNN_LIBRARY} - dl - m -) - +cmake_minimum_required(VERSION 3.10...3.27) +project(whisper_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +add_executable(${PROJECT_NAME} + main.cpp + common.cpp + whisper.cpp + whisper_invoke.cpp + pre_process_whisper.cpp + post_process_whisper.cpp +) + +target_link_libraries(${PROJECT_NAME} + ${AMLNN_LIBRARY} + dl + m +) + diff --git a/examples/whisper/cpp/src/common.cpp b/examples/whisper/cpp/src/common.cpp index cb61858..f76dacf 100755 --- a/examples/whisper/cpp/src/common.cpp +++ b/examples/whisper/cpp/src/common.cpp @@ -1,135 +1,135 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "common.h" - -// third-party utilities -// use your favorite implementations -#define DR_WAV_IMPLEMENTATION -#include "dr_wav.h" - - -bool is_wav_buffer(const std::string buf) { - // RIFF ref: https://en.wikipedia.org/wiki/Resource_Interchange_File_Format - // WAV ref: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html - if (buf.size() < 12 || buf.substr(0, 4) != "RIFF" || buf.substr(8, 4) != "WAVE") { - return false; - } - - uint32_t chunk_size = *reinterpret_cast(buf.data() + 4); - if (chunk_size + 8 != buf.size()) { - return false; - } - - return true; -} - -bool read_wav(const std::string & fname, std::vector& pcmf32, std::vector>& pcmf32s, bool stereo) { - drwav wav; - std::vector wav_data; // used for pipe input from stdin - - if (fname == "-") { - { - #ifdef _WIN32 - _setmode(_fileno(stdin), _O_BINARY); - #endif - - uint8_t buf[1024]; - while (true) - { - const size_t n = fread(buf, 1, sizeof(buf), stdin); - if (n == 0) { - break; - } - wav_data.insert(wav_data.end(), buf, buf + n); - } - } - - if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) { - fprintf(stderr, "error: failed to open WAV file from stdin\n"); - return false; - } - - fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size()); - } - else if (is_wav_buffer(fname)) { - if (drwav_init_memory(&wav, fname.c_str(), fname.size(), nullptr) == false) { - fprintf(stderr, "error: failed to open WAV file from fname buffer\n"); - return false; - } - } - else if (drwav_init_file(&wav, fname.c_str(), nullptr) == false) { - fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname.c_str()); - return false; - } - - if (wav.channels != 1 && wav.channels != 2) { - fprintf(stderr, "%s: WAV file '%s' must be mono or stereo\n", __func__, fname.c_str()); - drwav_uninit(&wav); - return false; - } - - if (stereo && wav.channels != 2) { - fprintf(stderr, "%s: WAV file '%s' must be stereo for diarization\n", __func__, fname.c_str()); - drwav_uninit(&wav); - return false; - } - - if (wav.sampleRate != COMMON_SAMPLE_RATE) { - fprintf(stderr, "%s: WAV file '%s' must be %i kHz\n", __func__, fname.c_str(), COMMON_SAMPLE_RATE/1000); - drwav_uninit(&wav); - return false; - } - - if (wav.bitsPerSample != 16) { - fprintf(stderr, "%s: WAV file '%s' must be 16-bit\n", __func__, fname.c_str()); - drwav_uninit(&wav); - return false; - } - - const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8); - - std::vector pcm16; - pcm16.resize(n*wav.channels); - drwav_read_pcm_frames_s16(&wav, n, pcm16.data()); - drwav_uninit(&wav); - - // convert to mono, float - pcmf32.resize(n); - if (wav.channels == 1) { - for (uint64_t i = 0; i < n; i++) { - pcmf32[i] = float(pcm16[i])/32768.0f; - } - } else { - for (uint64_t i = 0; i < n; i++) { - pcmf32[i] = float(pcm16[2*i] + pcm16[2*i + 1])/65536.0f; - } - } - - if (stereo) { - // convert to stereo, float - pcmf32s.resize(2); - - pcmf32s[0].resize(n); - pcmf32s[1].resize(n); - for (uint64_t i = 0; i < n; i++) { - pcmf32s[0][i] = float(pcm16[2*i])/32768.0f; - pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f; - } - } - - return true; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "common.h" + +// third-party utilities +// use your favorite implementations +#define DR_WAV_IMPLEMENTATION +#include "dr_wav.h" + + +bool is_wav_buffer(const std::string buf) { + // RIFF ref: https://en.wikipedia.org/wiki/Resource_Interchange_File_Format + // WAV ref: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html + if (buf.size() < 12 || buf.substr(0, 4) != "RIFF" || buf.substr(8, 4) != "WAVE") { + return false; + } + + uint32_t chunk_size = *reinterpret_cast(buf.data() + 4); + if (chunk_size + 8 != buf.size()) { + return false; + } + + return true; +} + +bool read_wav(const std::string & fname, std::vector& pcmf32, std::vector>& pcmf32s, bool stereo) { + drwav wav; + std::vector wav_data; // used for pipe input from stdin + + if (fname == "-") { + { + #ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + #endif + + uint8_t buf[1024]; + while (true) + { + const size_t n = fread(buf, 1, sizeof(buf), stdin); + if (n == 0) { + break; + } + wav_data.insert(wav_data.end(), buf, buf + n); + } + } + + if (drwav_init_memory(&wav, wav_data.data(), wav_data.size(), nullptr) == false) { + fprintf(stderr, "error: failed to open WAV file from stdin\n"); + return false; + } + + fprintf(stderr, "%s: read %zu bytes from stdin\n", __func__, wav_data.size()); + } + else if (is_wav_buffer(fname)) { + if (drwav_init_memory(&wav, fname.c_str(), fname.size(), nullptr) == false) { + fprintf(stderr, "error: failed to open WAV file from fname buffer\n"); + return false; + } + } + else if (drwav_init_file(&wav, fname.c_str(), nullptr) == false) { + fprintf(stderr, "error: failed to open '%s' as WAV file\n", fname.c_str()); + return false; + } + + if (wav.channels != 1 && wav.channels != 2) { + fprintf(stderr, "%s: WAV file '%s' must be mono or stereo\n", __func__, fname.c_str()); + drwav_uninit(&wav); + return false; + } + + if (stereo && wav.channels != 2) { + fprintf(stderr, "%s: WAV file '%s' must be stereo for diarization\n", __func__, fname.c_str()); + drwav_uninit(&wav); + return false; + } + + if (wav.sampleRate != COMMON_SAMPLE_RATE) { + fprintf(stderr, "%s: WAV file '%s' must be %i kHz\n", __func__, fname.c_str(), COMMON_SAMPLE_RATE/1000); + drwav_uninit(&wav); + return false; + } + + if (wav.bitsPerSample != 16) { + fprintf(stderr, "%s: WAV file '%s' must be 16-bit\n", __func__, fname.c_str()); + drwav_uninit(&wav); + return false; + } + + const uint64_t n = wav_data.empty() ? wav.totalPCMFrameCount : wav_data.size()/(wav.channels*wav.bitsPerSample/8); + + std::vector pcm16; + pcm16.resize(n*wav.channels); + drwav_read_pcm_frames_s16(&wav, n, pcm16.data()); + drwav_uninit(&wav); + + // convert to mono, float + pcmf32.resize(n); + if (wav.channels == 1) { + for (uint64_t i = 0; i < n; i++) { + pcmf32[i] = float(pcm16[i])/32768.0f; + } + } else { + for (uint64_t i = 0; i < n; i++) { + pcmf32[i] = float(pcm16[2*i] + pcm16[2*i + 1])/65536.0f; + } + } + + if (stereo) { + // convert to stereo, float + pcmf32s.resize(2); + + pcmf32s[0].resize(n); + pcmf32s[1].resize(n); + for (uint64_t i = 0; i < n; i++) { + pcmf32s[0][i] = float(pcm16[2*i])/32768.0f; + pcmf32s[1][i] = float(pcm16[2*i + 1])/32768.0f; + } + } + + return true; } \ No newline at end of file diff --git a/examples/whisper/cpp/src/common.h b/examples/whisper/cpp/src/common.h index d04e5d1..e8371c4 100755 --- a/examples/whisper/cpp/src/common.h +++ b/examples/whisper/cpp/src/common.h @@ -1,40 +1,40 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 -#include -#include -#include -#include -#include -#include - - -#define COMMON_SAMPLE_RATE 16000 - -bool is_wav_buffer(const std::string buf); - -// Read WAV audio file and store the PCM data into pcmf32 -// fname can be a buffer of WAV data instead of a filename -// The sample rate of the audio must be equal to COMMON_SAMPLE_RATE -// If stereo flag is set and the audio has 2 channels, the pcmf32s will contain 2 channel PCM -bool read_wav( - const std::string & fname, - std::vector & pcmf32, - std::vector> & pcmf32s, +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 +#include +#include +#include +#include +#include +#include + + +#define COMMON_SAMPLE_RATE 16000 + +bool is_wav_buffer(const std::string buf); + +// Read WAV audio file and store the PCM data into pcmf32 +// fname can be a buffer of WAV data instead of a filename +// The sample rate of the audio must be equal to COMMON_SAMPLE_RATE +// If stereo flag is set and the audio has 2 channels, the pcmf32s will contain 2 channel PCM +bool read_wav( + const std::string & fname, + std::vector & pcmf32, + std::vector> & pcmf32s, bool stereo); \ No newline at end of file diff --git a/examples/whisper/cpp/src/dr_wav.h b/examples/whisper/cpp/src/dr_wav.h index fd3e95b..7ae916e 100755 --- a/examples/whisper/cpp/src/dr_wav.h +++ b/examples/whisper/cpp/src/dr_wav.h @@ -30,7 +30,7 @@ callback which will give you information about the data format. To determine the Introduction ============ This is a single file library. To use it, do something like the following in one .c file. - + ```c #define DR_WAV_IMPLEMENTATION #include "dr_wav.h" @@ -391,7 +391,7 @@ If the return value differs from bytesToWrite, it indicates an error. typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); /* -Callback for when data needs to be seeked. +Callback for when data needs to be sought. pUserData [in] The user data that was passed to drwav_init() and family. offset [in] The number of bytes to move, relative to the origin. Will never be negative. @@ -415,16 +415,16 @@ pChunkHeader [in] A pointer to an object containing basic header informatio container [in] Whether or not the WAV file is a RIFF or Wave64 container. If you're unsure of the difference, assume RIFF. pFMT [in] A pointer to the object containing the contents of the "fmt" chunk. -Returns the number of bytes read + seeked. +Returns the number of bytes read + sought. To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same for seeking with onSeek(). The return value must -be the total number of bytes you have read _plus_ seeked. +be the total number of bytes you have read _plus_ sought. Use the `container` argument to discriminate the fields in `pChunkHeader->id`. If the container is `drwav_container_riff` or `drwav_container_rf64` you should use `id.fourcc`, otherwise you should use `id.guid`. The `pFMT` parameter can be used to determine the data format of the wave file. Use `drwav_fmt_get_format()` to get the sample format, which will be one of the -`DR_WAVE_FORMAT_*` identifiers. +`DR_WAVE_FORMAT_*` identifiers. The read pointer will be sitting on the first byte after the chunk's header. You must not attempt to read beyond the boundary of the chunk. */ @@ -499,7 +499,7 @@ typedef struct /* A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. */ drwav_write_proc onWrite; - /* A pointer to the function to call when the wav file needs to be seeked. */ + /* A pointer to the function to call when the wav file needs to be sought. */ drwav_seek_proc onSeek; /* The user data to pass to callbacks. */ @@ -534,7 +534,7 @@ typedef struct /* The size in bytes of the data chunk. */ drwav_uint64 dataChunkDataSize; - + /* The position in the stream of the first byte of the data chunk. This is used for seeking. */ drwav_uint64 dataChunkDataPos; @@ -565,7 +565,7 @@ typedef struct { drwav_uint64 iCurrentPCMFrame; /* The index of the next PCM frame that will be read by drwav_read_*(). This is used with "totalPCMFrameCount" to ensure we don't read excess samples at the end of the last block. */ } compressed; - + /* Microsoft ADPCM specific data. */ struct { @@ -1985,7 +1985,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, We need to enumerate over each chunk for two reasons: 1) The "data" chunk may not be the next one 2) We may want to report each chunk back to the client - + In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. */ foundDataChunk = DRWAV_FALSE; @@ -2017,7 +2017,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } } } - + if (!foundDataChunk) { pWav->dataChunkDataPos = cursor; @@ -2159,7 +2159,7 @@ static drwav_bool32 drwav_init__internal(drwav* pWav, drwav_chunk_proc onChunk, } cursor = pWav->dataChunkDataPos; } - + /* At this point we should be sitting on the first byte of the raw audio data. */ @@ -2431,7 +2431,7 @@ static drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_for runningPos += drwav__write(pWav, "WAVE", 4); } - + /* "ds64" chunk (RF64 only). */ if (pFormat->container == drwav_container_rf64) { drwav_uint32 initialds64ChunkSize = 28; /* 28 = [Size of RIFF (8 bytes)] + [Size of DATA (8 bytes)] + [Sample Count (8 bytes)] + [Table Length (4 bytes)]. Table length always set to 0. */ @@ -3291,7 +3291,7 @@ static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_see return DRWAV_FALSE; /* Trying to seek too far forward. */ } } - + return DRWAV_TRUE; } @@ -3360,7 +3360,7 @@ static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drw pWav->memoryStreamWrite.currentWritePos = pWav->memoryStreamWrite.dataSize; /* Trying to seek too far forward. */ } } - + return DRWAV_TRUE; } @@ -3452,7 +3452,7 @@ DRWAV_API drwav_result drwav_uninit(drwav* pWav) } else { paddingSize = drwav__chunk_padding_size_w64(pWav->dataChunkDataSize); } - + if (paddingSize > 0) { drwav_uint64 paddingData = 0; drwav__write(pWav, &paddingData, paddingSize); /* Byte order does not matter for this. */ @@ -3561,16 +3561,16 @@ DRWAV_API size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOu /* When we get here we may need to read-and-discard some data. */ while (bytesRead < bytesToRead) { drwav_uint8 buffer[4096]; - size_t bytesSeeked; + size_t bytessought; size_t bytesToSeek = (bytesToRead - bytesRead); if (bytesToSeek > sizeof(buffer)) { bytesToSeek = sizeof(buffer); } - bytesSeeked = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); - bytesRead += bytesSeeked; + bytessought = pWav->onRead(pWav->pUserData, buffer, bytesToSeek); + bytesRead += bytessought; - if (bytesSeeked < bytesToSeek) { + if (bytessought < bytesToSeek) { break; /* Reached the end. */ } } @@ -3662,7 +3662,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) DRWAV_ASSERT(DRWAV_FALSE); /* If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. */ } } - + pWav->bytesRemaining = pWav->dataChunkDataSize; return DRWAV_TRUE; } @@ -3696,7 +3696,7 @@ DRWAV_API drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetF */ if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { /* TODO: This can be optimized. */ - + /* If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, we first need to seek back to the start and then just do the same thing as a forward seek. @@ -3844,7 +3844,7 @@ DRWAV_API drwav_uint64 drwav_write_pcm_frames_be(drwav* pWav, drwav_uint64 frame pRunningData = (const drwav_uint8*)pData; bytesPerSample = drwav_get_bytes_per_pcm_frame(pWav) / pWav->channels; - + while (bytesToWrite > 0) { drwav_uint8 temp[4096]; drwav_uint32 sampleCount; @@ -3972,9 +3972,9 @@ static drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav_uint64 if (pWav->msadpcm.bytesRemainingInBlock == 0) { continue; } else { - static drwav_int32 adaptationTable[] = { - 230, 230, 230, 230, 307, 409, 512, 614, - 768, 614, 512, 409, 307, 230, 230, 230 + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 }; static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; @@ -4081,15 +4081,15 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra }; static drwav_int32 stepTable[89] = { - 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, - 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, - 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, - 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, - 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; DRWAV_ASSERT(pWav != NULL); @@ -4229,40 +4229,40 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ima(drwav* pWav, drwav_uint64 fra #ifndef DR_WAV_NO_CONVERSION_API static unsigned short g_drwavAlawTable[256] = { - 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, - 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, - 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, - 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, - 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, - 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, - 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, - 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, - 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, - 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, - 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, - 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, - 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, - 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, - 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 }; static unsigned short g_drwavMulawTable[256] = { - 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, - 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, - 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, - 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, - 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, - 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, - 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, - 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, - 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, - 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, - 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, - 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, - 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, - 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, - 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 }; @@ -4355,14 +4355,14 @@ static drwav_uint64 drwav_read_pcm_frames_s16__pcm(drwav* pWav, drwav_uint64 fra if ((pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) || pBufferOut == NULL) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } - + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; } totalFramesRead = 0; - + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { @@ -4395,7 +4395,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__ieee(drwav* pWav, drwav_uint64 fr } totalFramesRead = 0; - + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { @@ -4428,7 +4428,7 @@ static drwav_uint64 drwav_read_pcm_frames_s16__alaw(drwav* pWav, drwav_uint64 fr } totalFramesRead = 0; - + while (framesToRead > 0) { drwav_uint64 framesRead = drwav_read_pcm_frames(pWav, drwav_min(framesToRead, sizeof(sampleData)/bytesPerFrame), sampleData); if (framesRead == 0) { @@ -4777,7 +4777,7 @@ static drwav_uint64 drwav_read_pcm_frames_f32__ieee(drwav* pWav, drwav_uint64 fr if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } - + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; @@ -5110,7 +5110,7 @@ static drwav_uint64 drwav_read_pcm_frames_s32__pcm(drwav* pWav, drwav_uint64 fra if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { return drwav_read_pcm_frames(pWav, framesToRead, pBufferOut); } - + bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); if (bytesPerFrame == 0) { return 0; diff --git a/examples/whisper/cpp/src/main.cpp b/examples/whisper/cpp/src/main.cpp index 5d32fd4..d413521 100755 --- a/examples/whisper/cpp/src/main.cpp +++ b/examples/whisper/cpp/src/main.cpp @@ -1,204 +1,204 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -#include "whisper_invoke.h" -#include "nn_sdk.h" - -#define BILLION 1000000000 -#define GET_INFERENCE_TIME (1) -#define WHISPER_DECODER_INPUTS 48 - -struct Get_Times -{ - uint64_t init_start_time, init_end_time, init_total_time; - uint64_t preProcess_start_time, preProcess_end_time, preProcess_total_time; - uint64_t invoke_start_time, invoke_end_time, invoke_total_time; /* for whisper_decoder or llm invoke time once */ - uint64_t total_time; /* for whisper or llm pipeline time */ - std::vector total_time_group; /* for whisper_decoder or llm invoke time everytimes */ -}; - -static uint64_t get_time_count() -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)((uint64_t)ts.tv_nsec + (uint64_t)ts.tv_sec * BILLION); -} - -int main(int argc, char ** argv) -{ - Get_Times encoder_time, decoder_time, whisper_time; - Input_Decoder decoder_inputs_data; - std::vector encoder_input_data; - std::vector encoder_output_data; - - int64_t input_1_data[] = {50257, 50362}; /* init token, for tiny_en or base_en */ - int input_1_data_size = sizeof(input_1_data) / sizeof(input_1_data[0]); - - int ret = 0; - char* model_path_encoder = argv[1]; - char* model_path_decoder = argv[2]; - void *context_enc = NULL; - void *context_dec = NULL; - - whisper_time.init_start_time = get_time_count(); - context_enc = init_network_file(model_path_encoder); - context_dec = init_network_file(model_path_decoder); - whisper_time.init_end_time = get_time_count(); - - whisper_time.init_total_time = (whisper_time.init_end_time - whisper_time.init_start_time) / 1000000; - - if (context_enc == NULL) - { - printf("init_network [context_enc] fail.\n"); - return -1; - } - if (context_dec == NULL) - { - printf("init_network [context_dec] fail.\n"); - return -1; - } - - if (getenv("GET_TIME")) - { - std::cout << "init_whisper_total time : " << whisper_time.init_total_time << "ms" << std::endl; - } - - while (true) - { - std::string input_str; - bool is_finish = false; - std::string out_text = "start"; /* end adla model output text init */ - - printf("\n"); - printf("Audio Path:\n"); - std::getline(std::cin, input_str); - if (input_str == "exit") - { - break; - } else if (input_str == "") { - printf("Please enter wav path\n"); - continue; - } else if (input_str.size() < 4 || input_str.substr(input_str.size() - 4) != ".wav") { - std::cout << "Invalid wav path or file does not exist, please try again" << std::endl; - continue; - } - - decoder_inputs_data.input_1_size = WHISPER_DECODER_INPUTS; - decoder_inputs_data.input_1= new int64_t[decoder_inputs_data.input_1_size]; - std::copy(input_1_data, input_1_data + input_1_data_size, decoder_inputs_data.input_1); - - // need enough data 0 - std::fill(decoder_inputs_data.input_1 + input_1_data_size, - decoder_inputs_data.input_1 + decoder_inputs_data.input_1_size, - 0); - - whisper_time.preProcess_start_time = get_time_count(); - - encoder_input_data = do_pre_process(input_str); - if (!encoder_input_data.size()) /* support wav 0s */ - { - is_finish = is_finish_end(); - std::cout << "wav is null, please try again" << std::endl; - continue; - } - whisper_time.preProcess_end_time = get_time_count(); - encoder_output_data = run_network_encoder_process(context_enc, encoder_input_data); - encoder_time.invoke_end_time = get_time_count(); - - decoder_inputs_data.input_0_size = encoder_output_data.size(); - decoder_inputs_data.input_0 = new float[decoder_inputs_data.input_0_size]; - std::copy(encoder_output_data.begin(), encoder_output_data.end(), decoder_inputs_data.input_0); - - whisper_time.preProcess_total_time = (whisper_time.preProcess_end_time - whisper_time.preProcess_start_time) / 1000000; - encoder_time.invoke_total_time = (encoder_time.invoke_end_time - whisper_time.preProcess_end_time) / 1000000; - - printf("\n"); - printf("Audio Text:\n"); - while (!is_finish) - { - decoder_time.invoke_start_time = get_time_count(); - out_text = run_network_decoder(context_dec, &decoder_inputs_data); - decoder_time.invoke_end_time = get_time_count(); - is_finish = is_finish_end(); - decoder_time.total_time_group.push_back((decoder_time.invoke_end_time - decoder_time.invoke_start_time) / 1000000); - std::cout << out_text << std::flush; - } - printf("\n"); - - if (getenv("GET_OUTPUTS_SIZE")) - { - std::cout << "==================================" << std::endl; - std::cout << "WHISPER_OUTPUTS_SIZE : " << decoder_time.total_time_group.size() << std::endl; - } - - if (getenv("GET_TIME")) - { - uint64_t total_time_whisper, total_time_decoder, total_time_llm; - for (int i = 0; i < decoder_time.total_time_group.size(); i++) { - std::cout << "==================================" << std::endl; - if (i < 1) - { - total_time_whisper = whisper_time.preProcess_total_time + encoder_time.invoke_total_time; - whisper_time.total_time = whisper_time.preProcess_total_time + encoder_time.invoke_total_time; - std::cout << "pre-process time : " << whisper_time.preProcess_total_time << "ms" << std::endl; - std::cout << "encoder_inference_total time : " << encoder_time.invoke_total_time << "ms" << std::endl; - } - decoder_time.invoke_total_time += decoder_time.total_time_group[i]; - std::cout << "decoder inference time[" << i << "] : " << decoder_time.total_time_group[i] << "ms" << std::endl; - } - - - whisper_time.total_time += decoder_time.invoke_total_time; - std::cout << "model->whisper decoder avg : " << decoder_time.invoke_total_time / decoder_time.total_time_group.size() << "ms" << std::endl; - std::cout << "model->whisper total time : " << whisper_time.total_time << "ms" << std::endl; - whisper_time.total_time = decoder_time.invoke_total_time = 0; - } - encoder_time.total_time_group.clear(); - - if (decoder_inputs_data.input_0 != nullptr) - { - delete[] decoder_inputs_data.input_0; - decoder_inputs_data.input_0 = nullptr; - decoder_inputs_data.input_0_size = 0; - } - - if (decoder_inputs_data.input_1 != nullptr) - { - delete[] decoder_inputs_data.input_1; - decoder_inputs_data.input_1 = nullptr; - decoder_inputs_data.input_1_size = 0; - } - } - - ret = destroy_network(context_enc); - if (ret != 0) - { - printf("destroy_network [context_enc] fail.\n"); - return -1; - } - ret = destroy_network(context_dec); - if (ret != 0) - { - printf("destroy_network [context_dec] fail.\n"); - return -1; - } - - return ret; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "whisper_invoke.h" +#include "nn_sdk.h" + +#define BILLION 1000000000 +#define GET_INFERENCE_TIME (1) +#define WHISPER_DECODER_INPUTS 48 + +struct Get_Times +{ + uint64_t init_start_time, init_end_time, init_total_time; + uint64_t preProcess_start_time, preProcess_end_time, preProcess_total_time; + uint64_t invoke_start_time, invoke_end_time, invoke_total_time; /* for whisper_decoder or llm invoke time once */ + uint64_t total_time; /* for whisper or llm pipeline time */ + std::vector total_time_group; /* for whisper_decoder or llm invoke time everytimes */ +}; + +static uint64_t get_time_count() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)((uint64_t)ts.tv_nsec + (uint64_t)ts.tv_sec * BILLION); +} + +int main(int argc, char ** argv) +{ + Get_Times encoder_time, decoder_time, whisper_time; + Input_Decoder decoder_inputs_data; + std::vector encoder_input_data; + std::vector encoder_output_data; + + int64_t input_1_data[] = {50257, 50362}; /* init token, for tiny_en or base_en */ + int input_1_data_size = sizeof(input_1_data) / sizeof(input_1_data[0]); + + int ret = 0; + char* model_path_encoder = argv[1]; + char* model_path_decoder = argv[2]; + void *context_enc = NULL; + void *context_dec = NULL; + + whisper_time.init_start_time = get_time_count(); + context_enc = init_network_file(model_path_encoder); + context_dec = init_network_file(model_path_decoder); + whisper_time.init_end_time = get_time_count(); + + whisper_time.init_total_time = (whisper_time.init_end_time - whisper_time.init_start_time) / 1000000; + + if (context_enc == NULL) + { + printf("init_network [context_enc] fail.\n"); + return -1; + } + if (context_dec == NULL) + { + printf("init_network [context_dec] fail.\n"); + return -1; + } + + if (getenv("GET_TIME")) + { + std::cout << "init_whisper_total time : " << whisper_time.init_total_time << "ms" << std::endl; + } + + while (true) + { + std::string input_str; + bool is_finish = false; + std::string out_text = "start"; /* end adla model output text init */ + + printf("\n"); + printf("Audio Path:\n"); + std::getline(std::cin, input_str); + if (input_str == "exit") + { + break; + } else if (input_str == "") { + printf("Please enter wav path\n"); + continue; + } else if (input_str.size() < 4 || input_str.substr(input_str.size() - 4) != ".wav") { + std::cout << "Invalid wav path or file does not exist, please try again" << std::endl; + continue; + } + + decoder_inputs_data.input_1_size = WHISPER_DECODER_INPUTS; + decoder_inputs_data.input_1= new int64_t[decoder_inputs_data.input_1_size]; + std::copy(input_1_data, input_1_data + input_1_data_size, decoder_inputs_data.input_1); + + // need enough data 0 + std::fill(decoder_inputs_data.input_1 + input_1_data_size, + decoder_inputs_data.input_1 + decoder_inputs_data.input_1_size, + 0); + + whisper_time.preProcess_start_time = get_time_count(); + + encoder_input_data = do_pre_process(input_str); + if (!encoder_input_data.size()) /* support wav 0s */ + { + is_finish = is_finish_end(); + std::cout << "wav is null, please try again" << std::endl; + continue; + } + whisper_time.preProcess_end_time = get_time_count(); + encoder_output_data = run_network_encoder_process(context_enc, encoder_input_data); + encoder_time.invoke_end_time = get_time_count(); + + decoder_inputs_data.input_0_size = encoder_output_data.size(); + decoder_inputs_data.input_0 = new float[decoder_inputs_data.input_0_size]; + std::copy(encoder_output_data.begin(), encoder_output_data.end(), decoder_inputs_data.input_0); + + whisper_time.preProcess_total_time = (whisper_time.preProcess_end_time - whisper_time.preProcess_start_time) / 1000000; + encoder_time.invoke_total_time = (encoder_time.invoke_end_time - whisper_time.preProcess_end_time) / 1000000; + + printf("\n"); + printf("Audio Text:\n"); + while (!is_finish) + { + decoder_time.invoke_start_time = get_time_count(); + out_text = run_network_decoder(context_dec, &decoder_inputs_data); + decoder_time.invoke_end_time = get_time_count(); + is_finish = is_finish_end(); + decoder_time.total_time_group.push_back((decoder_time.invoke_end_time - decoder_time.invoke_start_time) / 1000000); + std::cout << out_text << std::flush; + } + printf("\n"); + + if (getenv("GET_OUTPUTS_SIZE")) + { + std::cout << "==================================" << std::endl; + std::cout << "WHISPER_OUTPUTS_SIZE : " << decoder_time.total_time_group.size() << std::endl; + } + + if (getenv("GET_TIME")) + { + uint64_t total_time_whisper, total_time_decoder, total_time_llm; + for (int i = 0; i < decoder_time.total_time_group.size(); i++) { + std::cout << "==================================" << std::endl; + if (i < 1) + { + total_time_whisper = whisper_time.preProcess_total_time + encoder_time.invoke_total_time; + whisper_time.total_time = whisper_time.preProcess_total_time + encoder_time.invoke_total_time; + std::cout << "pre-process time : " << whisper_time.preProcess_total_time << "ms" << std::endl; + std::cout << "encoder_inference_total time : " << encoder_time.invoke_total_time << "ms" << std::endl; + } + decoder_time.invoke_total_time += decoder_time.total_time_group[i]; + std::cout << "decoder inference time[" << i << "] : " << decoder_time.total_time_group[i] << "ms" << std::endl; + } + + + whisper_time.total_time += decoder_time.invoke_total_time; + std::cout << "model->whisper decoder avg : " << decoder_time.invoke_total_time / decoder_time.total_time_group.size() << "ms" << std::endl; + std::cout << "model->whisper total time : " << whisper_time.total_time << "ms" << std::endl; + whisper_time.total_time = decoder_time.invoke_total_time = 0; + } + encoder_time.total_time_group.clear(); + + if (decoder_inputs_data.input_0 != nullptr) + { + delete[] decoder_inputs_data.input_0; + decoder_inputs_data.input_0 = nullptr; + decoder_inputs_data.input_0_size = 0; + } + + if (decoder_inputs_data.input_1 != nullptr) + { + delete[] decoder_inputs_data.input_1; + decoder_inputs_data.input_1 = nullptr; + decoder_inputs_data.input_1_size = 0; + } + } + + ret = destroy_network(context_enc); + if (ret != 0) + { + printf("destroy_network [context_enc] fail.\n"); + return -1; + } + ret = destroy_network(context_dec); + if (ret != 0) + { + printf("destroy_network [context_dec] fail.\n"); + return -1; + } + + return ret; } \ No newline at end of file diff --git a/examples/whisper/cpp/src/post_process_whisper.cpp b/examples/whisper/cpp/src/post_process_whisper.cpp index 82ae7da..2bfbf9f 100755 --- a/examples/whisper/cpp/src/post_process_whisper.cpp +++ b/examples/whisper/cpp/src/post_process_whisper.cpp @@ -1,127 +1,127 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_post_common.h" -#include "post_process_whisper.h" - -whisper_vocab read_token_info(std::string token_path) -{ - struct whisper_context ctx; - auto & vocab = ctx.vocab; - whisper_model_loader loader = {}; - - auto fin = std::ifstream(token_path, std::ios::binary); - if (!fin) - { - fprintf(stderr, "%s : fail to open '%s'\n", __func__, token_path.c_str()); - } - loader.context = &fin; - - loader.read = [](void * ctx, void * output, size_t read_size) { - std::ifstream * fin = (std::ifstream*)ctx; - fin->read((char *)output, read_size); - return read_size; - }; - - loader.eof = [](void * ctx) { - std::ifstream * fin = (std::ifstream*)ctx; - return fin->eof(); - }; - - loader.close = [](void * ctx) { - std::ifstream * fin = (std::ifstream*)ctx; - fin->close(); - }; - - int32_t n_vocab = 0; - read_safe(&loader, n_vocab); - - std::string word; - std::vector tmp; - - tmp.reserve(128); - - for (int i = 0; i < n_vocab; i++) { - uint32_t len; - read_safe(&loader, len); - - if (len > 0 and i != 50256) { - tmp.resize(len); - loader.read(loader.context, &tmp[0], tmp.size()); // read to buffer - word.assign(tmp.data(), tmp.size()); - } else { - word = ""; - } - - vocab.token_to_id[word] = i; - vocab.id_to_token[i] = word; - } - fin.eof(); - fin.close(); - n_vocab = 50256; - - if (n_vocab < 51863) { - // WHISPER_LOG_INFO("%s: adding %d extra tokens\n", __func__, model.hparams.n_vocab - n_vocab); - for (int i = n_vocab; i < 51863; i++) { - if (i > vocab.token_beg) { - word = "[_TT_" + std::to_string(i - vocab.token_beg) + "]"; - } else if (i == vocab.token_eot) { - word = "<|endoftext|>"; - } else if (i == vocab.token_sot) { - word = "<|startoftranscript|>"; - } else if (i == vocab.token_translate) { - word = "<|translate|>"; - } else if (i == vocab.token_transcribe) { - word = "<|transcribe|>"; - } else if (i == vocab.token_solm) { - word = "[_SOLM_]"; - } else if (i == vocab.token_prev) { - word = "[_PREV_]"; - } else if (i == vocab.token_nosp) { - word = "[_NOSP_]"; - } else if (i == vocab.token_not) { - word = "<|notimestamps|>"; - } else if (i == vocab.token_beg) { - word = "[_BEG_]"; - } - else if (i == 50258) { - word= "<|en|>"; - } - else if (i == 50259) { - word= "<|zh|>"; - } - else if (i == 50263) { - word= "<|ko|>"; - } - else { - word = "[_extra_token_" + std::to_string(i) + "]"; - } - vocab.token_to_id[word] = i; - vocab.id_to_token[i] = word; - } - } - return vocab; -} - -std::string do_post_process(int64_t output_id, whisper_vocab vocab) -{ - // std::vector prompt_init = {50258, 50259, 50359, 50363,2221,13,2326,388,391,307,264,50244,295,264,2808,5359,11,293,321,366,5404,281,2928,702,14943,13,50257}; - - std::string text; - text = vocab.id_to_token.at(output_id).c_str(); - - return text; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_post_common.h" +#include "post_process_whisper.h" + +whisper_vocab read_token_info(std::string token_path) +{ + struct whisper_context ctx; + auto & vocab = ctx.vocab; + whisper_model_loader loader = {}; + + auto fin = std::ifstream(token_path, std::ios::binary); + if (!fin) + { + fprintf(stderr, "%s : fail to open '%s'\n", __func__, token_path.c_str()); + } + loader.context = &fin; + + loader.read = [](void * ctx, void * output, size_t read_size) { + std::ifstream * fin = (std::ifstream*)ctx; + fin->read((char *)output, read_size); + return read_size; + }; + + loader.eof = [](void * ctx) { + std::ifstream * fin = (std::ifstream*)ctx; + return fin->eof(); + }; + + loader.close = [](void * ctx) { + std::ifstream * fin = (std::ifstream*)ctx; + fin->close(); + }; + + int32_t n_vocab = 0; + read_safe(&loader, n_vocab); + + std::string word; + std::vector tmp; + + tmp.reserve(128); + + for (int i = 0; i < n_vocab; i++) { + uint32_t len; + read_safe(&loader, len); + + if (len > 0 and i != 50256) { + tmp.resize(len); + loader.read(loader.context, &tmp[0], tmp.size()); // read to buffer + word.assign(tmp.data(), tmp.size()); + } else { + word = ""; + } + + vocab.token_to_id[word] = i; + vocab.id_to_token[i] = word; + } + fin.eof(); + fin.close(); + n_vocab = 50256; + + if (n_vocab < 51863) { + // WHISPER_LOG_INFO("%s: adding %d extra tokens\n", __func__, model.hparams.n_vocab - n_vocab); + for (int i = n_vocab; i < 51863; i++) { + if (i > vocab.token_beg) { + word = "[_TT_" + std::to_string(i - vocab.token_beg) + "]"; + } else if (i == vocab.token_eot) { + word = "<|endoftext|>"; + } else if (i == vocab.token_sot) { + word = "<|startoftranscript|>"; + } else if (i == vocab.token_translate) { + word = "<|translate|>"; + } else if (i == vocab.token_transcribe) { + word = "<|transcribe|>"; + } else if (i == vocab.token_solm) { + word = "[_SOLM_]"; + } else if (i == vocab.token_prev) { + word = "[_PREV_]"; + } else if (i == vocab.token_nosp) { + word = "[_NOSP_]"; + } else if (i == vocab.token_not) { + word = "<|notimestamps|>"; + } else if (i == vocab.token_beg) { + word = "[_BEG_]"; + } + else if (i == 50258) { + word= "<|en|>"; + } + else if (i == 50259) { + word= "<|zh|>"; + } + else if (i == 50263) { + word= "<|ko|>"; + } + else { + word = "[_extra_token_" + std::to_string(i) + "]"; + } + vocab.token_to_id[word] = i; + vocab.id_to_token[i] = word; + } + } + return vocab; +} + +std::string do_post_process(int64_t output_id, whisper_vocab vocab) +{ + // std::vector prompt_init = {50258, 50259, 50359, 50363,2221,13,2326,388,391,307,264,50244,295,264,2808,5359,11,293,321,366,5404,281,2928,702,14943,13,50257}; + + std::string text; + text = vocab.id_to_token.at(output_id).c_str(); + + return text; } \ No newline at end of file diff --git a/examples/whisper/cpp/src/post_process_whisper.h b/examples/whisper/cpp/src/post_process_whisper.h index ed0325c..5263311 100755 --- a/examples/whisper/cpp/src/post_process_whisper.h +++ b/examples/whisper/cpp/src/post_process_whisper.h @@ -1,21 +1,21 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_post_common.h" - -whisper_vocab read_token_info(std::string token_path); -int get_output_max_index(size_t id_shape, std::vector buf_data); +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_post_common.h" + +whisper_vocab read_token_info(std::string token_path); +int get_output_max_index(size_t id_shape, std::vector buf_data); std::string do_post_process(int64_t output_id, whisper_vocab vocab); \ No newline at end of file diff --git a/examples/whisper/cpp/src/pre_post_common.h b/examples/whisper/cpp/src/pre_post_common.h index 2282ee2..ec9b652 100755 --- a/examples/whisper/cpp/src/pre_post_common.h +++ b/examples/whisper/cpp/src/pre_post_common.h @@ -1,105 +1,105 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef PRE_POST_COMMON_H -#define PRE_POST_COMMON_H - -#include "common.h" -#include "whisper.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -struct whisper_mel { - int n_len; - int n_len_org; - int n_mel; - - std::vector data; -}; - -struct whisper_filters { - int32_t n_mel = 80; - int32_t n_fft = 201; - - std::vector data; -}; - -struct whisper_state { - int64_t t_sample_us = 0; - int64_t t_encode_us = 0; - int64_t t_decode_us = 0; - int64_t t_batchd_us = 0; - int64_t t_prompt_us = 0; - int64_t t_mel_us = 0; - - int32_t n_sample = 0; // number of tokens sampled - int32_t n_encode = 0; // number of encoder calls - int32_t n_decode = 0; // number of decoder calls with n_tokens == 1 (text-generation) - int32_t n_batchd = 0; // number of decoder calls with n_tokens < 16 (batch decoding) - int32_t n_prompt = 0; // number of decoder calls with n_tokens > 1 (prompt encoding) - int32_t n_fail_p = 0; // number of logprob threshold failures - int32_t n_fail_h = 0; // number of entropy threshold failures - - whisper_mel mel; - - // decode output (2-dimensional array: [n_tokens][n_vocab]) - std::vector logits; - std::vector prompt_past; - - int lang_id = 0; // english by default - - std::string path_model; // populated by whisper_init_from_file_with_params() - - // [EXPERIMENTAL] token-level timestamps data - int64_t t_beg = 0; - int64_t t_last = 0; - - whisper_token tid_last; - - std::vector energy; // PCM signal energy - - // [EXPERIMENTAL] speed-up techniques - int32_t exp_n_audio_ctx = 0; // 0 - use default -}; - -struct whisper_model { - whisper_filters filters; -}; - -struct whisper_context { - int64_t t_load_us = 0; - int64_t t_start_us = 0; - - whisper_model model; - whisper_vocab vocab; - - whisper_state * state = nullptr; -}; - -template -static void read_safe(whisper_model_loader * loader, T & dest) { - loader->read(loader->context, &dest, sizeof(T)); -} - +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef PRE_POST_COMMON_H +#define PRE_POST_COMMON_H + +#include "common.h" +#include "whisper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +struct whisper_mel { + int n_len; + int n_len_org; + int n_mel; + + std::vector data; +}; + +struct whisper_filters { + int32_t n_mel = 80; + int32_t n_fft = 201; + + std::vector data; +}; + +struct whisper_state { + int64_t t_sample_us = 0; + int64_t t_encode_us = 0; + int64_t t_decode_us = 0; + int64_t t_batchd_us = 0; + int64_t t_prompt_us = 0; + int64_t t_mel_us = 0; + + int32_t n_sample = 0; // number of tokens sampled + int32_t n_encode = 0; // number of encoder calls + int32_t n_decode = 0; // number of decoder calls with n_tokens == 1 (text-generation) + int32_t n_batchd = 0; // number of decoder calls with n_tokens < 16 (batch decoding) + int32_t n_prompt = 0; // number of decoder calls with n_tokens > 1 (prompt encoding) + int32_t n_fail_p = 0; // number of logprob threshold failures + int32_t n_fail_h = 0; // number of entropy threshold failures + + whisper_mel mel; + + // decode output (2-dimensional array: [n_tokens][n_vocab]) + std::vector logits; + std::vector prompt_past; + + int lang_id = 0; // english by default + + std::string path_model; // populated by whisper_init_from_file_with_params() + + // [EXPERIMENTAL] token-level timestamps data + int64_t t_beg = 0; + int64_t t_last = 0; + + whisper_token tid_last; + + std::vector energy; // PCM signal energy + + // [EXPERIMENTAL] speed-up techniques + int32_t exp_n_audio_ctx = 0; // 0 - use default +}; + +struct whisper_model { + whisper_filters filters; +}; + +struct whisper_context { + int64_t t_load_us = 0; + int64_t t_start_us = 0; + + whisper_model model; + whisper_vocab vocab; + + whisper_state * state = nullptr; +}; + +template +static void read_safe(whisper_model_loader * loader, T & dest) { + loader->read(loader->context, &dest, sizeof(T)); +} + #endif // PRE_POST_COMMON_H \ No newline at end of file diff --git a/examples/whisper/cpp/src/pre_process_whisper.cpp b/examples/whisper/cpp/src/pre_process_whisper.cpp index d7feb33..d38ef80 100755 --- a/examples/whisper/cpp/src/pre_process_whisper.cpp +++ b/examples/whisper/cpp/src/pre_process_whisper.cpp @@ -1,53 +1,53 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_process_whisper.h" -#include "pre_post_common.h" - -extern bool is_finish; - -std::vector do_pre_process(std::string fname_inp) -{ - std::vector pcmf32; // mono-channel F32 PCM - std::vector> pcmf32s; // stereo-channel F32 PCM - - struct whisper_context ctx; - struct whisper_state state; - - if (!read_wav(fname_inp, pcmf32, pcmf32s, false)) { - fprintf(stderr, "error: failed to read WAV file '%s'\n", fname_inp.c_str()); - is_finish = true; - return {}; - } - - if (float(pcmf32.size())/WHISPER_SAMPLE_RATE == 0) { - is_finish = true; - return {}; - } - - if (whisper_pcm_to_mel_with_state(&ctx, &state, pcmf32.data(), pcmf32.size(), 8) != 0) { - printf("%s: failed to compute log mel spectrogram\n", __func__); - } - - std::vector input_data; - for (int j = 0; j < 80; j++) { - for (int i = 0; i < 3000; i++) { - input_data.push_back(state.mel.data[j * state.mel.n_len + i]); - } - } - - return input_data; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "pre_process_whisper.h" +#include "pre_post_common.h" + +extern bool is_finish; + +std::vector do_pre_process(std::string fname_inp) +{ + std::vector pcmf32; // mono-channel F32 PCM + std::vector> pcmf32s; // stereo-channel F32 PCM + + struct whisper_context ctx; + struct whisper_state state; + + if (!read_wav(fname_inp, pcmf32, pcmf32s, false)) { + fprintf(stderr, "error: failed to read WAV file '%s'\n", fname_inp.c_str()); + is_finish = true; + return {}; + } + + if (float(pcmf32.size())/WHISPER_SAMPLE_RATE == 0) { + is_finish = true; + return {}; + } + + if (whisper_pcm_to_mel_with_state(&ctx, &state, pcmf32.data(), pcmf32.size(), 8) != 0) { + printf("%s: failed to compute log mel spectrogram\n", __func__); + } + + std::vector input_data; + for (int j = 0; j < 80; j++) { + for (int i = 0; i < 3000; i++) { + input_data.push_back(state.mel.data[j * state.mel.n_len + i]); + } + } + + return input_data; } \ No newline at end of file diff --git a/examples/whisper/cpp/src/pre_process_whisper.h b/examples/whisper/cpp/src/pre_process_whisper.h index bd69929..ed48742 100755 --- a/examples/whisper/cpp/src/pre_process_whisper.h +++ b/examples/whisper/cpp/src/pre_process_whisper.h @@ -1,21 +1,21 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + std::vector do_pre_process(std::string fname_inp); \ No newline at end of file diff --git a/examples/whisper/cpp/src/whisper.cpp b/examples/whisper/cpp/src/whisper.cpp index 055081f..cc38f0c 100755 --- a/examples/whisper/cpp/src/whisper.cpp +++ b/examples/whisper/cpp/src/whisper.cpp @@ -1,433 +1,433 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "whisper.h" - -#include -#include -#define _USE_MATH_DEFINES -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct whisper_mel { - int n_len; - int n_len_org; - int n_mel; - - std::vector data; -}; - -struct whisper_filters { - int32_t n_mel = 80; - int32_t n_fft = 201; - - std::vector data; -}; - -struct whisper_state { - int64_t t_sample_us = 0; - int64_t t_encode_us = 0; - int64_t t_decode_us = 0; - int64_t t_batchd_us = 0; - int64_t t_prompt_us = 0; - int64_t t_mel_us = 0; - - int32_t n_sample = 0; // number of tokens sampled - int32_t n_encode = 0; // number of encoder calls - int32_t n_decode = 0; // number of decoder calls with n_tokens == 1 (text-generation) - int32_t n_batchd = 0; // number of decoder calls with n_tokens < 16 (batch decoding) - int32_t n_prompt = 0; // number of decoder calls with n_tokens > 1 (prompt encoding) - int32_t n_fail_p = 0; // number of logprob threshold failures - int32_t n_fail_h = 0; // number of entropy threshold failures - - whisper_mel mel; - - std::vector logits; - std::vector prompt_past; - - int lang_id = 0; // english by default - - std::string path_model; // populated by whisper_init_from_file_with_params() - - // [EXPERIMENTAL] token-level timestamps data - int64_t t_beg = 0; - int64_t t_last = 0; - - whisper_token tid_last; - - std::vector energy; // PCM signal energy - - // [EXPERIMENTAL] speed-up techniques - int32_t exp_n_audio_ctx = 0; // 0 - use default -}; - -struct whisper_model { - whisper_filters filters; -}; - -struct whisper_context { - int64_t t_load_us = 0; - int64_t t_start_us = 0; - - whisper_model model; - whisper_vocab vocab; - - whisper_state * state = nullptr; -}; - - -#define SIN_COS_N_COUNT WHISPER_N_FFT -static float sin_vals[SIN_COS_N_COUNT]; -static float cos_vals[SIN_COS_N_COUNT]; - -// In FFT, we frequently use sine and cosine operations with the same values. -// We can use precalculated values to speed up the process. -static void fill_sin_cos_table() { - static bool is_filled = false; - if (is_filled) return; - for (int i = 0; i < SIN_COS_N_COUNT; i++) { - double theta = (2*M_PI*i)/SIN_COS_N_COUNT; - sin_vals[i] = sinf(theta); - cos_vals[i] = cosf(theta); - } - is_filled = true; -} - -// naive Discrete Fourier Transform -// input is real-valued -// output is complex-valued -static void dft(const std::vector & in, std::vector & out) { - int N = in.size(); - - out.resize(N*2); - const int sin_cos_step = SIN_COS_N_COUNT / N; - - for (int k = 0; k < N; k++) { - float re = 0; - float im = 0; - - for (int n = 0; n < N; n++) { - int idx = (k * n * sin_cos_step) % (SIN_COS_N_COUNT); // t = 2*M_PI*k*n/N - re += in[n]*cos_vals[idx]; // cos(t) - im -= in[n]*sin_vals[idx]; // sin(t) - } - - out[k*2 + 0] = re; - out[k*2 + 1] = im; - } -} - -// Cooley-Tukey FFT -// poor man's implementation - use something better -// input is real-valued -// output is complex-valued -static void fft(const std::vector & in, std::vector & out) { - out.resize(in.size()*2); - - int N = in.size(); - - if (N == 1) { - out[0] = in[0]; - out[1] = 0; - return; - } - - if (N%2 == 1) { - dft(in, out); - return; - } - - std::vector even; - std::vector odd; - - even.reserve(N/2); - odd.reserve(N/2); - - for (int i = 0; i < N; i++) { - if (i % 2 == 0) { - even.push_back(in[i]); - } else { - odd.push_back(in[i]); - } - } - - std::vector even_fft; - std::vector odd_fft; - - fft(even, even_fft); - fft(odd, odd_fft); - - const int sin_cos_step = SIN_COS_N_COUNT / N; - for (int k = 0; k < N/2; k++) { - int idx = k * sin_cos_step; // t = 2*M_PI*k/N - float re = cos_vals[idx]; // cos(t) - float im = -sin_vals[idx]; // sin(t) - - float re_odd = odd_fft[2*k + 0]; - float im_odd = odd_fft[2*k + 1]; - - out[2*k + 0] = even_fft[2*k + 0] + re*re_odd - im*im_odd; - out[2*k + 1] = even_fft[2*k + 1] + re*im_odd + im*re_odd; - - out[2*(k + N/2) + 0] = even_fft[2*k + 0] - re*re_odd + im*im_odd; - out[2*(k + N/2) + 1] = even_fft[2*k + 1] - re*im_odd - im*re_odd; - } -} - -static bool hann_window(int length, bool periodic, std::vector & output) { - if (output.size() < static_cast(length)) { - output.resize(length); - } - int offset = -1; - if (periodic) { - offset = 0; - } - for (int i = 0; i < length; i++) { - output[i] = 0.5*(1.0 - cosf((2.0*M_PI*i)/(length + offset))); - } - - return true; -} - -static void log_mel_spectrogram_worker_thread(int ith, const std::vector & hann, const std::vector & samples, - int n_samples, int frame_size, int frame_step, int n_threads, - const whisper_filters & filters, whisper_mel & mel) { - std::vector fft_in(frame_size, 0.0); - std::vector fft_out(2 * frame_size); - int n_fft = filters.n_fft; - int i = ith; - - assert(n_fft == 1 + (frame_size / 2)); - - // calculate FFT only when fft_in are not all zero - for (; i < std::min(n_samples / frame_step + 1, mel.n_len); i += n_threads) { - const int offset = i * frame_step; - - // apply Hanning window (~10% faster) - for (int j = 0; j < std::min(frame_size, n_samples - offset); j++) { - fft_in[j] = hann[j] * samples[offset + j]; - } - // fill the rest with zeros - if (n_samples - offset < frame_size) { - std::fill(fft_in.begin() + (n_samples - offset), fft_in.end(), 0.0); - } - - // FFT - fft(fft_in, fft_out); - - // Calculate modulus^2 of complex numbers - // Use pow(fft_out[2 * j + 0], 2) + pow(fft_out[2 * j + 1], 2) causes inference quality problem? Interesting. - for (int j = 0; j < n_fft; j++) { - fft_out[j] = (fft_out[2 * j + 0] * fft_out[2 * j + 0] + fft_out[2 * j + 1] * fft_out[2 * j + 1]); - } - - // mel spectrogram - for (int j = 0; j < mel.n_mel; j++) { - double sum = 0.0; - - // unroll loop (suggested by GH user @lunixbochs) - int k = 0; - for (k = 0; k < n_fft - 3; k += 4) { - sum += - fft_out[k + 0] * filters.data[j * n_fft + k + 0] + - fft_out[k + 1] * filters.data[j * n_fft + k + 1] + - fft_out[k + 2] * filters.data[j * n_fft + k + 2] + - fft_out[k + 3] * filters.data[j * n_fft + k + 3]; - } - - // handle n_fft remainder - for (; k < n_fft; k++) { - sum += fft_out[k] * filters.data[j * n_fft + k]; - } - - sum = log10(std::max(sum, 1e-10)); - - mel.data[j * mel.n_len + i] = sum; - } - } - - // Otherwise fft_out are all zero - double sum = log10(1e-10); - for (; i < mel.n_len; i += n_threads) { - for (int j = 0; j < mel.n_mel; j++) { - mel.data[j * mel.n_len + i] = sum; - } - } -} - -static bool log_mel_spectrogram( - whisper_state & wstate, - const float * samples, - const int n_samples, - const int /*sample_rate*/, - const int frame_size, - const int frame_step, - const int n_mel, - const int n_threads, - whisper_filters & filters, - const bool debug, - whisper_mel & mel) { - // Hanning window (Use cosf to eliminate difference) - // ref: https://pytorch.org/docs/stable/generated/torch.hann_window.html - // ref: https://github.com/openai/whisper/blob/main/whisper/audio.py#L147 - fill_sin_cos_table(); - - // auto & filters = filters; - filters.data.resize(filters.n_mel*filters.n_fft); - auto fin = std::ifstream("./data_bin/data.bin", std::ios::binary); - if (!fin) - { - fprintf(stderr, "%s : fail to open '%s'\n", __func__, "./data_bin/data.bin"); - } - fin.read((char *)filters.data.data(), filters.data.size()*sizeof(float)); - fin.eof(); - fin.close(); - - - std::vector hann; - hann_window(frame_size, true, hann); - - // Calculate the length of padding - int64_t stage_1_pad = WHISPER_SAMPLE_RATE * 30; - int64_t stage_2_pad = frame_size / 2; - - // Initialize a vector and copy data from C array to it. - std::vector samples_padded; - samples_padded.resize(n_samples + stage_1_pad + stage_2_pad * 2); - std::copy(samples, samples + n_samples, samples_padded.begin() + stage_2_pad); - - // pad 30 seconds of zeros at the end of audio (480,000 samples) + reflective pad 200 samples at the end of audio - std::fill(samples_padded.begin() + n_samples + stage_2_pad, samples_padded.begin() + n_samples + stage_1_pad + 2 * stage_2_pad, 0); - - // reflective pad 200 samples at the beginning of audio - std::reverse_copy(samples + 1, samples + 1 + stage_2_pad, samples_padded.begin()); - - mel.n_mel = n_mel; - // https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/SpectralOps.cpp#L936 - // Calculate number of frames + remove the last frame - mel.n_len = (samples_padded.size() - frame_size) / frame_step; - // Calculate semi-padded sample length to ensure compatibility - mel.n_len_org = 1 + (n_samples + stage_2_pad - frame_size) / frame_step; - - mel.data.resize(mel.n_mel * mel.n_len); - - { - std::vector workers(n_threads - 1); - for (int iw = 0; iw < n_threads - 1; ++iw) { - workers[iw] = std::thread( - log_mel_spectrogram_worker_thread, iw + 1, std::cref(hann), samples_padded, - n_samples + stage_2_pad, frame_size, frame_step, n_threads, - std::cref(filters), std::ref(mel)); - } - - // main thread - log_mel_spectrogram_worker_thread(0, hann, samples_padded, n_samples + stage_2_pad, frame_size, frame_step, n_threads, filters, mel); - - for (int iw = 0; iw < n_threads - 1; ++iw) { - workers[iw].join(); - } - } - - // clamping and normalization - double mmax = -1e20; - for (int i = 0; i < mel.n_mel*mel.n_len; i++) { - if (mel.data[i] > mmax) { - mmax = mel.data[i]; - } - } - - mmax -= 8.0; - - for (int i = 0; i < mel.n_mel*mel.n_len; i++) { - if (mel.data[i] < mmax) { - mel.data[i] = mmax; - } - - mel.data[i] = (mel.data[i] + 4.0)/4.0; - } - - return true; -} - -int whisper_pcm_to_mel_with_state(struct whisper_context * ctx, struct whisper_state * state, const float * samples, int n_samples, int n_threads) { - if (!log_mel_spectrogram(*state, samples, n_samples, WHISPER_SAMPLE_RATE, WHISPER_N_FFT, WHISPER_HOP_LENGTH, 80, n_threads, ctx->model.filters, true, state->mel)) { - printf("%s: failed to compute mel spectrogram\n", __func__); - return -1; - } - - return 0; -} - -static std::vector tokenize(const whisper_vocab & vocab, const std::string & text) { - std::vector words; - - // first split the text into words - { - std::string str = text; - std::string pat = R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)"; - - std::regex re(pat); - std::smatch m; - - while (std::regex_search(str, m, re)) { - for (auto x : m) { - words.push_back(x); - } - str = m.suffix(); - } - } - - // find the longest tokens that form the words: - std::vector tokens; - for (const auto & word : words) { - if (word.empty()) continue; - - int i = 0; - int n = word.size(); - while (i < n) { - int j = n; - bool found = false; - while (j > i) { - auto sub = word.substr(i, j-i); - auto it = vocab.token_to_id.find(sub); - if (it != vocab.token_to_id.end()) { - tokens.push_back(it->second); - i = j; - found = true; - break; - } - --j; - } - if (!found) { - printf("unknown token\n"); - ++i; - } - } - } - - return tokens; -} +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "whisper.h" + +#include +#include +#define _USE_MATH_DEFINES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct whisper_mel { + int n_len; + int n_len_org; + int n_mel; + + std::vector data; +}; + +struct whisper_filters { + int32_t n_mel = 80; + int32_t n_fft = 201; + + std::vector data; +}; + +struct whisper_state { + int64_t t_sample_us = 0; + int64_t t_encode_us = 0; + int64_t t_decode_us = 0; + int64_t t_batchd_us = 0; + int64_t t_prompt_us = 0; + int64_t t_mel_us = 0; + + int32_t n_sample = 0; // number of tokens sampled + int32_t n_encode = 0; // number of encoder calls + int32_t n_decode = 0; // number of decoder calls with n_tokens == 1 (text-generation) + int32_t n_batchd = 0; // number of decoder calls with n_tokens < 16 (batch decoding) + int32_t n_prompt = 0; // number of decoder calls with n_tokens > 1 (prompt encoding) + int32_t n_fail_p = 0; // number of logprob threshold failures + int32_t n_fail_h = 0; // number of entropy threshold failures + + whisper_mel mel; + + std::vector logits; + std::vector prompt_past; + + int lang_id = 0; // english by default + + std::string path_model; // populated by whisper_init_from_file_with_params() + + // [EXPERIMENTAL] token-level timestamps data + int64_t t_beg = 0; + int64_t t_last = 0; + + whisper_token tid_last; + + std::vector energy; // PCM signal energy + + // [EXPERIMENTAL] speed-up techniques + int32_t exp_n_audio_ctx = 0; // 0 - use default +}; + +struct whisper_model { + whisper_filters filters; +}; + +struct whisper_context { + int64_t t_load_us = 0; + int64_t t_start_us = 0; + + whisper_model model; + whisper_vocab vocab; + + whisper_state * state = nullptr; +}; + + +#define SIN_COS_N_COUNT WHISPER_N_FFT +static float sin_vals[SIN_COS_N_COUNT]; +static float cos_vals[SIN_COS_N_COUNT]; + +// In FFT, we frequently use sine and cosine operations with the same values. +// We can use precalculated values to speed up the process. +static void fill_sin_cos_table() { + static bool is_filled = false; + if (is_filled) return; + for (int i = 0; i < SIN_COS_N_COUNT; i++) { + double theta = (2*M_PI*i)/SIN_COS_N_COUNT; + sin_vals[i] = sinf(theta); + cos_vals[i] = cosf(theta); + } + is_filled = true; +} + +// naive Discrete Fourier Transform +// input is real-valued +// output is complex-valued +static void dft(const std::vector & in, std::vector & out) { + int N = in.size(); + + out.resize(N*2); + const int sin_cos_step = SIN_COS_N_COUNT / N; + + for (int k = 0; k < N; k++) { + float re = 0; + float im = 0; + + for (int n = 0; n < N; n++) { + int idx = (k * n * sin_cos_step) % (SIN_COS_N_COUNT); // t = 2*M_PI*k*n/N + re += in[n]*cos_vals[idx]; // cos(t) + im -= in[n]*sin_vals[idx]; // sin(t) + } + + out[k*2 + 0] = re; + out[k*2 + 1] = im; + } +} + +// Cooley-Tukey FFT +// poor man's implementation - use something better +// input is real-valued +// output is complex-valued +static void fft(const std::vector & in, std::vector & out) { + out.resize(in.size()*2); + + int N = in.size(); + + if (N == 1) { + out[0] = in[0]; + out[1] = 0; + return; + } + + if (N%2 == 1) { + dft(in, out); + return; + } + + std::vector even; + std::vector odd; + + even.reserve(N/2); + odd.reserve(N/2); + + for (int i = 0; i < N; i++) { + if (i % 2 == 0) { + even.push_back(in[i]); + } else { + odd.push_back(in[i]); + } + } + + std::vector even_fft; + std::vector odd_fft; + + fft(even, even_fft); + fft(odd, odd_fft); + + const int sin_cos_step = SIN_COS_N_COUNT / N; + for (int k = 0; k < N/2; k++) { + int idx = k * sin_cos_step; // t = 2*M_PI*k/N + float re = cos_vals[idx]; // cos(t) + float im = -sin_vals[idx]; // sin(t) + + float re_odd = odd_fft[2*k + 0]; + float im_odd = odd_fft[2*k + 1]; + + out[2*k + 0] = even_fft[2*k + 0] + re*re_odd - im*im_odd; + out[2*k + 1] = even_fft[2*k + 1] + re*im_odd + im*re_odd; + + out[2*(k + N/2) + 0] = even_fft[2*k + 0] - re*re_odd + im*im_odd; + out[2*(k + N/2) + 1] = even_fft[2*k + 1] - re*im_odd - im*re_odd; + } +} + +static bool hann_window(int length, bool periodic, std::vector & output) { + if (output.size() < static_cast(length)) { + output.resize(length); + } + int offset = -1; + if (periodic) { + offset = 0; + } + for (int i = 0; i < length; i++) { + output[i] = 0.5*(1.0 - cosf((2.0*M_PI*i)/(length + offset))); + } + + return true; +} + +static void log_mel_spectrogram_worker_thread(int ith, const std::vector & hann, const std::vector & samples, + int n_samples, int frame_size, int frame_step, int n_threads, + const whisper_filters & filters, whisper_mel & mel) { + std::vector fft_in(frame_size, 0.0); + std::vector fft_out(2 * frame_size); + int n_fft = filters.n_fft; + int i = ith; + + assert(n_fft == 1 + (frame_size / 2)); + + // calculate FFT only when fft_in are not all zero + for (; i < std::min(n_samples / frame_step + 1, mel.n_len); i += n_threads) { + const int offset = i * frame_step; + + // apply Hanning window (~10% faster) + for (int j = 0; j < std::min(frame_size, n_samples - offset); j++) { + fft_in[j] = hann[j] * samples[offset + j]; + } + // fill the rest with zeros + if (n_samples - offset < frame_size) { + std::fill(fft_in.begin() + (n_samples - offset), fft_in.end(), 0.0); + } + + // FFT + fft(fft_in, fft_out); + + // Calculate modulus^2 of complex numbers + // Use pow(fft_out[2 * j + 0], 2) + pow(fft_out[2 * j + 1], 2) causes inference quality problem? Interesting. + for (int j = 0; j < n_fft; j++) { + fft_out[j] = (fft_out[2 * j + 0] * fft_out[2 * j + 0] + fft_out[2 * j + 1] * fft_out[2 * j + 1]); + } + + // mel spectrogram + for (int j = 0; j < mel.n_mel; j++) { + double sum = 0.0; + + // unroll loop (suggested by GH user @lunixbochs) + int k = 0; + for (k = 0; k < n_fft - 3; k += 4) { + sum += + fft_out[k + 0] * filters.data[j * n_fft + k + 0] + + fft_out[k + 1] * filters.data[j * n_fft + k + 1] + + fft_out[k + 2] * filters.data[j * n_fft + k + 2] + + fft_out[k + 3] * filters.data[j * n_fft + k + 3]; + } + + // handle n_fft remainder + for (; k < n_fft; k++) { + sum += fft_out[k] * filters.data[j * n_fft + k]; + } + + sum = log10(std::max(sum, 1e-10)); + + mel.data[j * mel.n_len + i] = sum; + } + } + + // Otherwise fft_out are all zero + double sum = log10(1e-10); + for (; i < mel.n_len; i += n_threads) { + for (int j = 0; j < mel.n_mel; j++) { + mel.data[j * mel.n_len + i] = sum; + } + } +} + +static bool log_mel_spectrogram( + whisper_state & wstate, + const float * samples, + const int n_samples, + const int /*sample_rate*/, + const int frame_size, + const int frame_step, + const int n_mel, + const int n_threads, + whisper_filters & filters, + const bool debug, + whisper_mel & mel) { + // Hanning window (Use cosf to eliminate difference) + // ref: https://pytorch.org/docs/stable/generated/torch.hann_window.html + // ref: https://github.com/openai/whisper/blob/main/whisper/audio.py#L147 + fill_sin_cos_table(); + + // auto & filters = filters; + filters.data.resize(filters.n_mel*filters.n_fft); + auto fin = std::ifstream("./data_bin/data.bin", std::ios::binary); + if (!fin) + { + fprintf(stderr, "%s : fail to open '%s'\n", __func__, "./data_bin/data.bin"); + } + fin.read((char *)filters.data.data(), filters.data.size()*sizeof(float)); + fin.eof(); + fin.close(); + + + std::vector hann; + hann_window(frame_size, true, hann); + + // Calculate the length of padding + int64_t stage_1_pad = WHISPER_SAMPLE_RATE * 30; + int64_t stage_2_pad = frame_size / 2; + + // Initialize a vector and copy data from C array to it. + std::vector samples_padded; + samples_padded.resize(n_samples + stage_1_pad + stage_2_pad * 2); + std::copy(samples, samples + n_samples, samples_padded.begin() + stage_2_pad); + + // pad 30 seconds of zeros at the end of audio (480,000 samples) + reflective pad 200 samples at the end of audio + std::fill(samples_padded.begin() + n_samples + stage_2_pad, samples_padded.begin() + n_samples + stage_1_pad + 2 * stage_2_pad, 0); + + // reflective pad 200 samples at the beginning of audio + std::reverse_copy(samples + 1, samples + 1 + stage_2_pad, samples_padded.begin()); + + mel.n_mel = n_mel; + // https://github.com/pytorch/pytorch/blob/main/aten/src/ATen/native/SpectralOps.cpp#L936 + // Calculate number of frames + remove the last frame + mel.n_len = (samples_padded.size() - frame_size) / frame_step; + // Calculate semi-padded sample length to ensure compatibility + mel.n_len_org = 1 + (n_samples + stage_2_pad - frame_size) / frame_step; + + mel.data.resize(mel.n_mel * mel.n_len); + + { + std::vector workers(n_threads - 1); + for (int iw = 0; iw < n_threads - 1; ++iw) { + workers[iw] = std::thread( + log_mel_spectrogram_worker_thread, iw + 1, std::cref(hann), samples_padded, + n_samples + stage_2_pad, frame_size, frame_step, n_threads, + std::cref(filters), std::ref(mel)); + } + + // main thread + log_mel_spectrogram_worker_thread(0, hann, samples_padded, n_samples + stage_2_pad, frame_size, frame_step, n_threads, filters, mel); + + for (int iw = 0; iw < n_threads - 1; ++iw) { + workers[iw].join(); + } + } + + // clamping and normalization + double mmax = -1e20; + for (int i = 0; i < mel.n_mel*mel.n_len; i++) { + if (mel.data[i] > mmax) { + mmax = mel.data[i]; + } + } + + mmax -= 8.0; + + for (int i = 0; i < mel.n_mel*mel.n_len; i++) { + if (mel.data[i] < mmax) { + mel.data[i] = mmax; + } + + mel.data[i] = (mel.data[i] + 4.0)/4.0; + } + + return true; +} + +int whisper_pcm_to_mel_with_state(struct whisper_context * ctx, struct whisper_state * state, const float * samples, int n_samples, int n_threads) { + if (!log_mel_spectrogram(*state, samples, n_samples, WHISPER_SAMPLE_RATE, WHISPER_N_FFT, WHISPER_HOP_LENGTH, 80, n_threads, ctx->model.filters, true, state->mel)) { + printf("%s: failed to compute mel spectrogram\n", __func__); + return -1; + } + + return 0; +} + +static std::vector tokenize(const whisper_vocab & vocab, const std::string & text) { + std::vector words; + + // first split the text into words + { + std::string str = text; + std::string pat = R"('s|'t|'re|'ve|'m|'ll|'d| ?[[:alpha:]]+| ?[[:digit:]]+| ?[^\s[:alpha:][:digit:]]+|\s+(?!\S)|\s+)"; + + std::regex re(pat); + std::smatch m; + + while (std::regex_search(str, m, re)) { + for (auto x : m) { + words.push_back(x); + } + str = m.suffix(); + } + } + + // find the longest tokens that form the words: + std::vector tokens; + for (const auto & word : words) { + if (word.empty()) continue; + + int i = 0; + int n = word.size(); + while (i < n) { + int j = n; + bool found = false; + while (j > i) { + auto sub = word.substr(i, j-i); + auto it = vocab.token_to_id.find(sub); + if (it != vocab.token_to_id.end()) { + tokens.push_back(it->second); + i = j; + found = true; + break; + } + --j; + } + if (!found) { + printf("unknown token\n"); + ++i; + } + } + } + + return tokens; +} diff --git a/examples/whisper/cpp/src/whisper.h b/examples/whisper/cpp/src/whisper.h index 6b59ece..d05fe2a 100755 --- a/examples/whisper/cpp/src/whisper.h +++ b/examples/whisper/cpp/src/whisper.h @@ -1,404 +1,404 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef WHISPER_H -#define WHISPER_H - -#include -#include -#include - -#include -#include -#include - -#ifdef __GNUC__ -# define WHISPER_DEPRECATED(func, hint) func __attribute__((deprecated(hint))) -#elif defined(_MSC_VER) -# define WHISPER_DEPRECATED(func, hint) __declspec(deprecated(hint)) func -#else -# define WHISPER_DEPRECATED(func, hint) func -#endif - -#ifdef WHISPER_SHARED -# ifdef _WIN32 -# ifdef WHISPER_BUILD -# define WHISPER_API __declspec(dllexport) -# else -# define WHISPER_API __declspec(dllimport) -# endif -# else -# define WHISPER_API __attribute__ ((visibility ("default"))) -# endif -#else -# define WHISPER_API -#endif - -#define WHISPER_SAMPLE_RATE 16000 -#define WHISPER_N_FFT 400 -#define WHISPER_HOP_LENGTH 160 -#define WHISPER_CHUNK_SIZE 30 -#define WHISPER_N_MELS 80 -#define WHISPER_N_FRAMES 3000 -#define WHISPER_N_SAMPLES 48000 - -struct whisper_vocab { - using id = int32_t; - using token = std::string; - - int n_vocab = 51864; - - std::map token_to_id; - std::map id_to_token; - - // reference: https://github.com/openai/whisper/blob/248b6cb124225dd263bb9bd32d060b6517e067f8/whisper/tokenizer.py#L334-L349 - id token_eot = 50256; - id token_sot = 50257; - // task tokens (used only for multilingual models) - id token_translate = 50357; - id token_transcribe = 50358; - // other special tokens - id token_solm = 50359; // [TDRZ] used by tinydiarize models to indicate speaker turn - id token_prev = 50360; - id token_nosp = 50361; - id token_not = 50362; // no timestamps - id token_beg = 50363; // begin timestamps - - bool is_multilingual() const { - return n_vocab >= 51865; - } - - int num_languages() const { - return n_vocab - 51765 - (is_multilingual() ? 1 : 0); - } -}; - -#ifdef __cplusplus -extern "C" { -#endif - - struct whisper_context; - struct whisper_state; - struct whisper_full_params; - - typedef int32_t whisper_pos; - typedef int32_t whisper_token; - typedef int32_t whisper_seq_id; - - - typedef struct whisper_token_data { - whisper_token id; // token id - whisper_token tid; // forced timestamp token id - - float p; // probability of the token - float plog; // log probability of the token - float pt; // probability of the timestamp token - float ptsum; // sum of probabilities of all timestamp tokens - - // token-level timestamp data - // do not use if you haven't computed token-level timestamps - int64_t t0; // start time of the token - int64_t t1; // end time of the token - - // [EXPERIMENTAL] Token-level timestamps with DTW - // do not use if you haven't computed token-level timestamps with dtw - // Roughly corresponds to the moment in audio in which the token was output - int64_t t_dtw; - - float vlen; // voice length of the token - } whisper_token_data; - - typedef struct whisper_model_loader { - void * context; - - size_t (*read)(void * ctx, void * output, size_t read_size); - bool (*eof)(void * ctx); - void (*close)(void * ctx); - } whisper_model_loader; - - // Various functions for loading a ggml whisper model. - // Allocate (almost) all memory needed for the model. - // Return NULL on failure - WHISPER_API struct whisper_context * whisper_init_from_file_with_params (const char * path_model, struct whisper_context_params params); - WHISPER_API struct whisper_context * whisper_init_from_buffer_with_params(void * buffer, size_t buffer_size, struct whisper_context_params params); - WHISPER_API struct whisper_context * whisper_init_with_params (struct whisper_model_loader * loader, struct whisper_context_params params); - - // These are the same as the above, but the internal state of the context is not allocated automatically - // It is the responsibility of the caller to allocate the state using whisper_init_state() (#523) - WHISPER_API struct whisper_context * whisper_init_from_file_with_params_no_state (const char * path_model, struct whisper_context_params params); - WHISPER_API struct whisper_context * whisper_init_from_buffer_with_params_no_state(void * buffer, size_t buffer_size, struct whisper_context_params params); - WHISPER_API struct whisper_context * whisper_init_with_params_no_state (struct whisper_model_loader * loader, struct whisper_context_params params); - - WHISPER_API struct whisper_state * whisper_init_state(struct whisper_context * ctx); - - // Given a context, enable use of OpenVINO for encode inference. - // model_path: Optional path to OpenVINO encoder IR model. If set to nullptr, - // the path will be generated from the ggml model path that was passed - // in to whisper_init_from_file. For example, if 'path_model' was - // "/path/to/ggml-base.en.bin", then OpenVINO IR model path will be - // assumed to be "/path/to/ggml-base.en-encoder-openvino.xml". - // device: OpenVINO device to run inference on ("CPU", "GPU", etc.) - // cache_dir: Optional cache directory that can speed up init time, especially for - // GPU, by caching compiled 'blobs' there. - // Set to nullptr if not used. - // Returns 0 on success. If OpenVINO is not enabled in build, this simply returns 1. - WHISPER_API int whisper_ctx_init_openvino_encoder( - struct whisper_context * ctx, - const char * model_path, - const char * device, - const char * cache_dir); - - // Frees all allocated memory - WHISPER_API void whisper_free (struct whisper_context * ctx); - WHISPER_API void whisper_free_state(struct whisper_state * state); - WHISPER_API void whisper_free_params(struct whisper_full_params * params); - WHISPER_API void whisper_free_context_params(struct whisper_context_params * params); - - // Convert RAW PCM audio to log mel spectrogram. - // The resulting spectrogram is stored inside the default state of the provided whisper context. - // Returns 0 on success - WHISPER_API int whisper_pcm_to_mel( - struct whisper_context * ctx, - const float * samples, - int n_samples, - int n_threads); - - WHISPER_API int whisper_pcm_to_mel_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - const float * samples, - int n_samples, - int n_threads); - - // Convert RAW PCM audio to log mel spectrogram but applies a Phase Vocoder to speed up the audio x2. - // The resulting spectrogram is stored inside the default state of the provided whisper context. - // Returns 0 on success - WHISPER_API int whisper_pcm_to_mel_phase_vocoder( - struct whisper_context * ctx, - const float * samples, - int n_samples, - int n_threads); - - WHISPER_API int whisper_pcm_to_mel_phase_vocoder_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - const float * samples, - int n_samples, - int n_threads); - - // This can be used to set a custom log mel spectrogram inside the default state of the provided whisper context. - // Use this instead of whisper_pcm_to_mel() if you want to provide your own log mel spectrogram. - // n_mel must be 80 - // Returns 0 on success - WHISPER_API int whisper_set_mel( - struct whisper_context * ctx, - const float * data, - int n_len, - int n_mel); - - WHISPER_API int whisper_set_mel_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - const float * data, - int n_len, - int n_mel); - - // Run the Whisper encoder on the log mel spectrogram stored inside the default state in the provided whisper context. - // Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first. - // offset can be used to specify the offset of the first frame in the spectrogram. - // Returns 0 on success - WHISPER_API int whisper_encode( - struct whisper_context * ctx, - int offset, - int n_threads); - - WHISPER_API int whisper_encode_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - int offset, - int n_threads); - - // Run the Whisper decoder to obtain the logits and probabilities for the next token. - // Make sure to call whisper_encode() first. - // tokens + n_tokens is the provided context for the decoder. - // n_past is the number of tokens to use from previous decoder calls. - // Returns 0 on success - // TODO: add support for multiple decoders - WHISPER_API int whisper_decode( - struct whisper_context * ctx, - const whisper_token * tokens, - int n_tokens, - int n_past, - int n_threads); - - WHISPER_API int whisper_decode_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - const whisper_token * tokens, - int n_tokens, - int n_past, - int n_threads); - - // Convert the provided text into tokens. - // The tokens pointer must be large enough to hold the resulting tokens. - // Returns the number of tokens on success, no more than n_max_tokens - // Returns a negative number on failure - the number of tokens that would have been returned - // TODO: not sure if correct - WHISPER_API int whisper_tokenize( - struct whisper_context * ctx, - const char * text, - whisper_token * tokens, - int n_max_tokens); - - // Return the number of tokens in the provided text - // Equivalent to: -whisper_tokenize(ctx, text, NULL, 0) - int whisper_token_count(struct whisper_context * ctx, const char * text); - - // Largest language id (i.e. number of available languages - 1) - WHISPER_API int whisper_lang_max_id(); - - // Return the id of the specified language, returns -1 if not found - // Examples: - // "de" -> 2 - // "german" -> 2 - WHISPER_API int whisper_lang_id(const char * lang); - - // Return the short string of the specified language id (e.g. 2 -> "de"), returns nullptr if not found - WHISPER_API const char * whisper_lang_str(int id); - - // Return the short string of the specified language name (e.g. 2 -> "german"), returns nullptr if not found - WHISPER_API const char * whisper_lang_str_full(int id); - - // Use mel data at offset_ms to try and auto-detect the spoken language - // Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first - // Returns the top language id or negative on failure - // If not null, fills the lang_probs array with the probabilities of all languages - // The array must be whisper_lang_max_id() + 1 in size - // ref: https://github.com/openai/whisper/blob/main/whisper/decoding.py#L18-L69 - WHISPER_API int whisper_lang_auto_detect( - struct whisper_context * ctx, - int offset_ms, - int n_threads, - float * lang_probs); - - WHISPER_API int whisper_lang_auto_detect_with_state( - struct whisper_context * ctx, - struct whisper_state * state, - int offset_ms, - int n_threads, - float * lang_probs); - - WHISPER_API int whisper_n_len (struct whisper_context * ctx); // mel length - WHISPER_API int whisper_n_len_from_state(struct whisper_state * state); // mel length - WHISPER_API int whisper_n_vocab (struct whisper_context * ctx); - WHISPER_API int whisper_n_text_ctx (struct whisper_context * ctx); - WHISPER_API int whisper_n_audio_ctx (struct whisper_context * ctx); - WHISPER_API int whisper_is_multilingual (struct whisper_context * ctx); - - WHISPER_API int whisper_model_n_vocab (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_audio_ctx (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_audio_state(struct whisper_context * ctx); - WHISPER_API int whisper_model_n_audio_head (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_audio_layer(struct whisper_context * ctx); - WHISPER_API int whisper_model_n_text_ctx (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_text_state (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_text_head (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_text_layer (struct whisper_context * ctx); - WHISPER_API int whisper_model_n_mels (struct whisper_context * ctx); - WHISPER_API int whisper_model_ftype (struct whisper_context * ctx); - WHISPER_API int whisper_model_type (struct whisper_context * ctx); - - // Token logits obtained from the last call to whisper_decode() - // The logits for the last token are stored in the last row - // Rows: n_tokens - // Cols: n_vocab - WHISPER_API float * whisper_get_logits (struct whisper_context * ctx); - WHISPER_API float * whisper_get_logits_from_state(struct whisper_state * state); - - // Token Id -> String. Uses the vocabulary in the provided context - WHISPER_API const char * whisper_token_to_str(struct whisper_context * ctx, whisper_token token); - WHISPER_API const char * whisper_model_type_readable(struct whisper_context * ctx); - - - // Special tokens - WHISPER_API whisper_token whisper_token_eot (struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_sot (struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_solm(struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_prev(struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_nosp(struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_not (struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_beg (struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_lang(struct whisper_context * ctx, int lang_id); - - // Task tokens - WHISPER_API whisper_token whisper_token_translate (struct whisper_context * ctx); - WHISPER_API whisper_token whisper_token_transcribe(struct whisper_context * ctx); - - //////////////////////////////////////////////////////////////////////////// - - // Number of generated text segments - // A segment can be a few words, a sentence, or even a paragraph. - WHISPER_API int whisper_full_n_segments (struct whisper_context * ctx); - WHISPER_API int whisper_full_n_segments_from_state(struct whisper_state * state); - - // Language id associated with the context's default state - WHISPER_API int whisper_full_lang_id(struct whisper_context * ctx); - - // Language id associated with the provided state - WHISPER_API int whisper_full_lang_id_from_state(struct whisper_state * state); - - // Get the start and end time of the specified segment - WHISPER_API int64_t whisper_full_get_segment_t0 (struct whisper_context * ctx, int i_segment); - WHISPER_API int64_t whisper_full_get_segment_t0_from_state(struct whisper_state * state, int i_segment); - - WHISPER_API int64_t whisper_full_get_segment_t1 (struct whisper_context * ctx, int i_segment); - WHISPER_API int64_t whisper_full_get_segment_t1_from_state(struct whisper_state * state, int i_segment); - - // Get whether the next segment is predicted as a speaker turn - WHISPER_API bool whisper_full_get_segment_speaker_turn_next(struct whisper_context * ctx, int i_segment); - WHISPER_API bool whisper_full_get_segment_speaker_turn_next_from_state(struct whisper_state * state, int i_segment); - - // Get the text of the specified segment - WHISPER_API const char * whisper_full_get_segment_text (struct whisper_context * ctx, int i_segment); - WHISPER_API const char * whisper_full_get_segment_text_from_state(struct whisper_state * state, int i_segment); - - // Get number of tokens in the specified segment - WHISPER_API int whisper_full_n_tokens (struct whisper_context * ctx, int i_segment); - WHISPER_API int whisper_full_n_tokens_from_state(struct whisper_state * state, int i_segment); - - // Get the token text of the specified token in the specified segment - WHISPER_API const char * whisper_full_get_token_text (struct whisper_context * ctx, int i_segment, int i_token); - WHISPER_API const char * whisper_full_get_token_text_from_state(struct whisper_context * ctx, struct whisper_state * state, int i_segment, int i_token); - - WHISPER_API whisper_token whisper_full_get_token_id (struct whisper_context * ctx, int i_segment, int i_token); - WHISPER_API whisper_token whisper_full_get_token_id_from_state(struct whisper_state * state, int i_segment, int i_token); - - // Get token data for the specified token in the specified segment - // This contains probabilities, timestamps, etc. - WHISPER_API whisper_token_data whisper_full_get_token_data (struct whisper_context * ctx, int i_segment, int i_token); - WHISPER_API whisper_token_data whisper_full_get_token_data_from_state(struct whisper_state * state, int i_segment, int i_token); - - // Get the probability of the specified token in the specified segment - WHISPER_API float whisper_full_get_token_p (struct whisper_context * ctx, int i_segment, int i_token); - WHISPER_API float whisper_full_get_token_p_from_state(struct whisper_state * state, int i_segment, int i_token); - - //////////////////////////////////////////////////////////////////////////// - -#ifdef __cplusplus -} -#endif - -#endif +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef WHISPER_H +#define WHISPER_H + +#include +#include +#include + +#include +#include +#include + +#ifdef __GNUC__ +# define WHISPER_DEPRECATED(func, hint) func __attribute__((deprecated(hint))) +#elif defined(_MSC_VER) +# define WHISPER_DEPRECATED(func, hint) __declspec(deprecated(hint)) func +#else +# define WHISPER_DEPRECATED(func, hint) func +#endif + +#ifdef WHISPER_SHARED +# ifdef _WIN32 +# ifdef WHISPER_BUILD +# define WHISPER_API __declspec(dllexport) +# else +# define WHISPER_API __declspec(dllimport) +# endif +# else +# define WHISPER_API __attribute__ ((visibility ("default"))) +# endif +#else +# define WHISPER_API +#endif + +#define WHISPER_SAMPLE_RATE 16000 +#define WHISPER_N_FFT 400 +#define WHISPER_HOP_LENGTH 160 +#define WHISPER_CHUNK_SIZE 30 +#define WHISPER_N_MELS 80 +#define WHISPER_N_FRAMES 3000 +#define WHISPER_N_SAMPLES 48000 + +struct whisper_vocab { + using id = int32_t; + using token = std::string; + + int n_vocab = 51864; + + std::map token_to_id; + std::map id_to_token; + + // reference: https://github.com/openai/whisper/blob/248b6cb124225dd263bb9bd32d060b6517e067f8/whisper/tokenizer.py#L334-L349 + id token_eot = 50256; + id token_sot = 50257; + // task tokens (used only for multilingual models) + id token_translate = 50357; + id token_transcribe = 50358; + // other special tokens + id token_solm = 50359; // [TDRZ] used by tinydiarize models to indicate speaker turn + id token_prev = 50360; + id token_nosp = 50361; + id token_not = 50362; // no timestamps + id token_beg = 50363; // begin timestamps + + bool is_multilingual() const { + return n_vocab >= 51865; + } + + int num_languages() const { + return n_vocab - 51765 - (is_multilingual() ? 1 : 0); + } +}; + +#ifdef __cplusplus +extern "C" { +#endif + + struct whisper_context; + struct whisper_state; + struct whisper_full_params; + + typedef int32_t whisper_pos; + typedef int32_t whisper_token; + typedef int32_t whisper_seq_id; + + + typedef struct whisper_token_data { + whisper_token id; // token id + whisper_token tid; // forced timestamp token id + + float p; // probability of the token + float plog; // log probability of the token + float pt; // probability of the timestamp token + float ptsum; // sum of probabilities of all timestamp tokens + + // token-level timestamp data + // do not use if you haven't computed token-level timestamps + int64_t t0; // start time of the token + int64_t t1; // end time of the token + + // [EXPERIMENTAL] Token-level timestamps with DTW + // do not use if you haven't computed token-level timestamps with dtw + // Roughly corresponds to the moment in audio in which the token was output + int64_t t_dtw; + + float vlen; // voice length of the token + } whisper_token_data; + + typedef struct whisper_model_loader { + void * context; + + size_t (*read)(void * ctx, void * output, size_t read_size); + bool (*eof)(void * ctx); + void (*close)(void * ctx); + } whisper_model_loader; + + // Various functions for loading a ggml whisper model. + // Allocate (almost) all memory needed for the model. + // Return NULL on failure + WHISPER_API struct whisper_context * whisper_init_from_file_with_params (const char * path_model, struct whisper_context_params params); + WHISPER_API struct whisper_context * whisper_init_from_buffer_with_params(void * buffer, size_t buffer_size, struct whisper_context_params params); + WHISPER_API struct whisper_context * whisper_init_with_params (struct whisper_model_loader * loader, struct whisper_context_params params); + + // These are the same as the above, but the internal state of the context is not allocated automatically + // It is the responsibility of the caller to allocate the state using whisper_init_state() (#523) + WHISPER_API struct whisper_context * whisper_init_from_file_with_params_no_state (const char * path_model, struct whisper_context_params params); + WHISPER_API struct whisper_context * whisper_init_from_buffer_with_params_no_state(void * buffer, size_t buffer_size, struct whisper_context_params params); + WHISPER_API struct whisper_context * whisper_init_with_params_no_state (struct whisper_model_loader * loader, struct whisper_context_params params); + + WHISPER_API struct whisper_state * whisper_init_state(struct whisper_context * ctx); + + // Given a context, enable use of OpenVINO for encode inference. + // model_path: Optional path to OpenVINO encoder IR model. If set to nullptr, + // the path will be generated from the ggml model path that was passed + // in to whisper_init_from_file. For example, if 'path_model' was + // "/path/to/ggml-base.en.bin", then OpenVINO IR model path will be + // assumed to be "/path/to/ggml-base.en-encoder-openvino.xml". + // device: OpenVINO device to run inference on ("CPU", "GPU", etc.) + // cache_dir: Optional cache directory that can speed up init time, especially for + // GPU, by caching compiled 'blobs' there. + // Set to nullptr if not used. + // Returns 0 on success. If OpenVINO is not enabled in build, this simply returns 1. + WHISPER_API int whisper_ctx_init_openvino_encoder( + struct whisper_context * ctx, + const char * model_path, + const char * device, + const char * cache_dir); + + // Frees all allocated memory + WHISPER_API void whisper_free (struct whisper_context * ctx); + WHISPER_API void whisper_free_state(struct whisper_state * state); + WHISPER_API void whisper_free_params(struct whisper_full_params * params); + WHISPER_API void whisper_free_context_params(struct whisper_context_params * params); + + // Convert RAW PCM audio to log mel spectrogram. + // The resulting spectrogram is stored inside the default state of the provided whisper context. + // Returns 0 on success + WHISPER_API int whisper_pcm_to_mel( + struct whisper_context * ctx, + const float * samples, + int n_samples, + int n_threads); + + WHISPER_API int whisper_pcm_to_mel_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + const float * samples, + int n_samples, + int n_threads); + + // Convert RAW PCM audio to log mel spectrogram but applies a Phase Vocoder to speed up the audio x2. + // The resulting spectrogram is stored inside the default state of the provided whisper context. + // Returns 0 on success + WHISPER_API int whisper_pcm_to_mel_phase_vocoder( + struct whisper_context * ctx, + const float * samples, + int n_samples, + int n_threads); + + WHISPER_API int whisper_pcm_to_mel_phase_vocoder_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + const float * samples, + int n_samples, + int n_threads); + + // This can be used to set a custom log mel spectrogram inside the default state of the provided whisper context. + // Use this instead of whisper_pcm_to_mel() if you want to provide your own log mel spectrogram. + // n_mel must be 80 + // Returns 0 on success + WHISPER_API int whisper_set_mel( + struct whisper_context * ctx, + const float * data, + int n_len, + int n_mel); + + WHISPER_API int whisper_set_mel_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + const float * data, + int n_len, + int n_mel); + + // Run the Whisper encoder on the log mel spectrogram stored inside the default state in the provided whisper context. + // Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first. + // offset can be used to specify the offset of the first frame in the spectrogram. + // Returns 0 on success + WHISPER_API int whisper_encode( + struct whisper_context * ctx, + int offset, + int n_threads); + + WHISPER_API int whisper_encode_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + int offset, + int n_threads); + + // Run the Whisper decoder to obtain the logits and probabilities for the next token. + // Make sure to call whisper_encode() first. + // tokens + n_tokens is the provided context for the decoder. + // n_past is the number of tokens to use from previous decoder calls. + // Returns 0 on success + // TODO: add support for multiple decoders + WHISPER_API int whisper_decode( + struct whisper_context * ctx, + const whisper_token * tokens, + int n_tokens, + int n_past, + int n_threads); + + WHISPER_API int whisper_decode_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + const whisper_token * tokens, + int n_tokens, + int n_past, + int n_threads); + + // Convert the provided text into tokens. + // The tokens pointer must be large enough to hold the resulting tokens. + // Returns the number of tokens on success, no more than n_max_tokens + // Returns a negative number on failure - the number of tokens that would have been returned + // TODO: not sure if correct + WHISPER_API int whisper_tokenize( + struct whisper_context * ctx, + const char * text, + whisper_token * tokens, + int n_max_tokens); + + // Return the number of tokens in the provided text + // Equivalent to: -whisper_tokenize(ctx, text, NULL, 0) + int whisper_token_count(struct whisper_context * ctx, const char * text); + + // Largest language id (i.e. number of available languages - 1) + WHISPER_API int whisper_lang_max_id(); + + // Return the id of the specified language, returns -1 if not found + // Examples: + // "de" -> 2 + // "german" -> 2 + WHISPER_API int whisper_lang_id(const char * lang); + + // Return the short string of the specified language id (e.g. 2 -> "de"), returns nullptr if not found + WHISPER_API const char * whisper_lang_str(int id); + + // Return the short string of the specified language name (e.g. 2 -> "german"), returns nullptr if not found + WHISPER_API const char * whisper_lang_str_full(int id); + + // Use mel data at offset_ms to try and auto-detect the spoken language + // Make sure to call whisper_pcm_to_mel() or whisper_set_mel() first + // Returns the top language id or negative on failure + // If not null, fills the lang_probs array with the probabilities of all languages + // The array must be whisper_lang_max_id() + 1 in size + // ref: https://github.com/openai/whisper/blob/main/whisper/decoding.py#L18-L69 + WHISPER_API int whisper_lang_auto_detect( + struct whisper_context * ctx, + int offset_ms, + int n_threads, + float * lang_probs); + + WHISPER_API int whisper_lang_auto_detect_with_state( + struct whisper_context * ctx, + struct whisper_state * state, + int offset_ms, + int n_threads, + float * lang_probs); + + WHISPER_API int whisper_n_len (struct whisper_context * ctx); // mel length + WHISPER_API int whisper_n_len_from_state(struct whisper_state * state); // mel length + WHISPER_API int whisper_n_vocab (struct whisper_context * ctx); + WHISPER_API int whisper_n_text_ctx (struct whisper_context * ctx); + WHISPER_API int whisper_n_audio_ctx (struct whisper_context * ctx); + WHISPER_API int whisper_is_multilingual (struct whisper_context * ctx); + + WHISPER_API int whisper_model_n_vocab (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_audio_ctx (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_audio_state(struct whisper_context * ctx); + WHISPER_API int whisper_model_n_audio_head (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_audio_layer(struct whisper_context * ctx); + WHISPER_API int whisper_model_n_text_ctx (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_text_state (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_text_head (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_text_layer (struct whisper_context * ctx); + WHISPER_API int whisper_model_n_mels (struct whisper_context * ctx); + WHISPER_API int whisper_model_ftype (struct whisper_context * ctx); + WHISPER_API int whisper_model_type (struct whisper_context * ctx); + + // Token logits obtained from the last call to whisper_decode() + // The logits for the last token are stored in the last row + // Rows: n_tokens + // Cols: n_vocab + WHISPER_API float * whisper_get_logits (struct whisper_context * ctx); + WHISPER_API float * whisper_get_logits_from_state(struct whisper_state * state); + + // Token Id -> String. Uses the vocabulary in the provided context + WHISPER_API const char * whisper_token_to_str(struct whisper_context * ctx, whisper_token token); + WHISPER_API const char * whisper_model_type_readable(struct whisper_context * ctx); + + + // Special tokens + WHISPER_API whisper_token whisper_token_eot (struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_sot (struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_solm(struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_prev(struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_nosp(struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_not (struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_beg (struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_lang(struct whisper_context * ctx, int lang_id); + + // Task tokens + WHISPER_API whisper_token whisper_token_translate (struct whisper_context * ctx); + WHISPER_API whisper_token whisper_token_transcribe(struct whisper_context * ctx); + + //////////////////////////////////////////////////////////////////////////// + + // Number of generated text segments + // A segment can be a few words, a sentence, or even a paragraph. + WHISPER_API int whisper_full_n_segments (struct whisper_context * ctx); + WHISPER_API int whisper_full_n_segments_from_state(struct whisper_state * state); + + // Language id associated with the context's default state + WHISPER_API int whisper_full_lang_id(struct whisper_context * ctx); + + // Language id associated with the provided state + WHISPER_API int whisper_full_lang_id_from_state(struct whisper_state * state); + + // Get the start and end time of the specified segment + WHISPER_API int64_t whisper_full_get_segment_t0 (struct whisper_context * ctx, int i_segment); + WHISPER_API int64_t whisper_full_get_segment_t0_from_state(struct whisper_state * state, int i_segment); + + WHISPER_API int64_t whisper_full_get_segment_t1 (struct whisper_context * ctx, int i_segment); + WHISPER_API int64_t whisper_full_get_segment_t1_from_state(struct whisper_state * state, int i_segment); + + // Get whether the next segment is predicted as a speaker turn + WHISPER_API bool whisper_full_get_segment_speaker_turn_next(struct whisper_context * ctx, int i_segment); + WHISPER_API bool whisper_full_get_segment_speaker_turn_next_from_state(struct whisper_state * state, int i_segment); + + // Get the text of the specified segment + WHISPER_API const char * whisper_full_get_segment_text (struct whisper_context * ctx, int i_segment); + WHISPER_API const char * whisper_full_get_segment_text_from_state(struct whisper_state * state, int i_segment); + + // Get number of tokens in the specified segment + WHISPER_API int whisper_full_n_tokens (struct whisper_context * ctx, int i_segment); + WHISPER_API int whisper_full_n_tokens_from_state(struct whisper_state * state, int i_segment); + + // Get the token text of the specified token in the specified segment + WHISPER_API const char * whisper_full_get_token_text (struct whisper_context * ctx, int i_segment, int i_token); + WHISPER_API const char * whisper_full_get_token_text_from_state(struct whisper_context * ctx, struct whisper_state * state, int i_segment, int i_token); + + WHISPER_API whisper_token whisper_full_get_token_id (struct whisper_context * ctx, int i_segment, int i_token); + WHISPER_API whisper_token whisper_full_get_token_id_from_state(struct whisper_state * state, int i_segment, int i_token); + + // Get token data for the specified token in the specified segment + // This contains probabilities, timestamps, etc. + WHISPER_API whisper_token_data whisper_full_get_token_data (struct whisper_context * ctx, int i_segment, int i_token); + WHISPER_API whisper_token_data whisper_full_get_token_data_from_state(struct whisper_state * state, int i_segment, int i_token); + + // Get the probability of the specified token in the specified segment + WHISPER_API float whisper_full_get_token_p (struct whisper_context * ctx, int i_segment, int i_token); + WHISPER_API float whisper_full_get_token_p_from_state(struct whisper_state * state, int i_segment, int i_token); + + //////////////////////////////////////////////////////////////////////////// + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/examples/whisper/cpp/src/whisper_invoke.cpp b/examples/whisper/cpp/src/whisper_invoke.cpp index 36bde23..f291939 100755 --- a/examples/whisper/cpp/src/whisper_invoke.cpp +++ b/examples/whisper/cpp/src/whisper_invoke.cpp @@ -70,7 +70,7 @@ void* init_network_file(const char *model_path) /* set omp, If you are considering high CPU usage during operation, you can turn off this api, set_openmp_opt_flag = false */ - aml_openmp_opt_t openmp_opt[] = + aml_openmp_opt_t openmp_opt[] = { { .operator_type = AML_Unknown, @@ -84,7 +84,7 @@ void* init_network_file(const char *model_path) config.forward_ctrl.softop_info.openmp_opt = openmp_opt; /* set neon */ - aml_neon_opt_t neon_opt[] = + aml_neon_opt_t neon_opt[] = { { .operator_type = AML_Unknown, @@ -193,11 +193,11 @@ nn_output* run_network_decoder_process(void *qcontext, Input_Decoder* input_data } inData.input_type = INPUT_DMA_DATA; - memcpy(mem_data[i].viraddr, i == 0 ? static_cast(input_data->input_0) : + memcpy(mem_data[i].viraddr, i == 0 ? static_cast(input_data->input_0) : static_cast(input_data->input_1), mem_config[i].mem_size); inData.input = NULL; } else { - inData.input = i == 0 ? reinterpret_cast(const_cast(input_data->input_0)) : + inData.input = i == 0 ? reinterpret_cast(const_cast(input_data->input_0)) : reinterpret_cast(const_cast(input_data->input_1)); inData.input_type = BINARY_RAW_DATA; @@ -266,7 +266,7 @@ int destroy_network(void *qcontext) { int ret = 0; - /* free encoder + /* free encoder encoder.use_dma = true encoder.malloc_buffer_once = false */ @@ -279,7 +279,7 @@ int destroy_network(void *qcontext) } encoder.use_dma = false; - /* free decoder + /* free decoder first use destroy_network, decoder.malloc_buffer_once is false, and set decoder.malloc_buffer_once is true */ diff --git a/examples/whisper/cpp/src/whisper_invoke.h b/examples/whisper/cpp/src/whisper_invoke.h index 1cebde9..e393200 100755 --- a/examples/whisper/cpp/src/whisper_invoke.h +++ b/examples/whisper/cpp/src/whisper_invoke.h @@ -1,39 +1,39 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef WHISPER_INVOKE_H -#define WHISPER_INVOKE_H - -#include -#include -#include -#include "nn_sdk.h" - -struct Input_Decoder { - float * input_0; - int input_0_size; - int64_t * input_1; - int input_1_size; -}; - -void* init_network_file(const char *model_path); -std::vector do_pre_process(std::string fname_inp); -std::vector run_network_encoder_process(void *qcontext, std::vector input_ids); -std::string run_network_decoder(void *qcontext_sec, Input_Decoder* input_data); -bool is_finish_end(); -int destroy_network(void *qcontext); - -#endif // WHISPER_INVOKE_H +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef WHISPER_INVOKE_H +#define WHISPER_INVOKE_H + +#include +#include +#include +#include "nn_sdk.h" + +struct Input_Decoder { + float * input_0; + int input_0_size; + int64_t * input_1; + int input_1_size; +}; + +void* init_network_file(const char *model_path); +std::vector do_pre_process(std::string fname_inp); +std::vector run_network_encoder_process(void *qcontext, std::vector input_ids); +std::string run_network_decoder(void *qcontext_sec, Input_Decoder* input_data); +bool is_finish_end(); +int destroy_network(void *qcontext); + +#endif // WHISPER_INVOKE_H diff --git a/examples/whisper/model/.gitkeep b/examples/whisper/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/whisper/py/.gitkeep b/examples/whisper/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloe/README.md b/examples/yoloe/README.md index b33f6d8..66f9365 100644 --- a/examples/yoloe/README.md +++ b/examples/yoloe/README.md @@ -1,20 +1,20 @@ -# YOLOE - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK r25c -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/yoloe/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated in `build/android/`. +# YOLOE + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK r25c +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/yoloe/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated in `build/android/`. diff --git a/examples/yoloe/cpp/.gitkeep b/examples/yoloe/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloe/cpp/src/main.cpp b/examples/yoloe/cpp/src/main.cpp index aeca7b8..cf1e1d7 100644 --- a/examples/yoloe/cpp/src/main.cpp +++ b/examples/yoloe/cpp/src/main.cpp @@ -174,7 +174,7 @@ int main(int argc, char** argv) { memset(&outconfig, 0, sizeof(aml_output_config_t)); outconfig.typeSize = sizeof(aml_output_config_t); outconfig.format = AML_OUTDATA_FLOAT32; - + nn_output* outdata = (nn_output*)aml_module_output_get(context, outconfig); if (!outdata) { std::cerr << "Failed to run network (get output)." << std::endl; @@ -191,10 +191,10 @@ int main(int argc, char** argv) { std::cout << "Objects:\n"; for (auto& box : boxes) { - std::cout << "[ " << box.x1 << ", " << box.y1 << ", " << box.x2 << ", " << box.y2 << " ]\n"; + std::cout << "[ " << box.x1 << ", " << box.y1 << ", " << box.x2 << ", " << box.y2 << " ]\n"; } std::cout << std::endl; - + uninit_network(context); return 0; diff --git a/examples/yoloe/model/.gitkeep b/examples/yoloe/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloe/py/.gitkeep b/examples/yoloe/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yolov11/README.md b/examples/yolov11/README.md index 2558b9e..945e48f 100755 --- a/examples/yolov11/README.md +++ b/examples/yolov11/README.md @@ -1,170 +1,170 @@ -# yolov11 - -## 1.Overview - -​ YOLOv11 was released by Ultralytics on October 2, 2024. It introduces significant architectural refinements and efficiency optimizations, delivering superior accuracy-speed trade-offs compared to previous generations. With enhanced feature extraction capabilities, YOLOv11 is designed for high-performance real-time applications—including object detection, instance segmentation, and pose estimation—to handle demanding tasks in a wide range of applications. - -## 2.Model Download - -- **Open Source model** - - - **Open Source projects:** https://github.com/ultralytics/ultralytics/tree/v8.3.0 - - - **Export Model Step:** - - - **Install ultralytics** - - pip install torch==2.4.1 - - pip install torchvision==0.19.1 - - pip install ultralytics==8.3.0 - - - **Download weights** - - wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11m.pt - - wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11s.pt - - wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt - - - **Export Model** - - ``` - from ultralytics import YOLO - - model = YOLO("yolo11n.pt") - model.export(format="onnx", opset=12, simplify=True, dynamic=False, imgsz=640) - ``` - - -- **Exported Model** - - ​ link to amlogic server( **onnx model or quantized tflite**) - - - -## 3. Model Conversion - -``` -cd model -Usage: ./adla_covnert.sh model_path adla_tookkit_path target_platform - -example - ./adla_covnert.sh yolov11m.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 - ./adla_covnert.sh yolov11s.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 - ./adla_covnert.sh yolov11n.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 -``` - -| Parameter | Discription | -| ----------------- | ------------------------------------------------------------ | -| model_path | onnx model path | -| adla_tookkit_path | path to adla_toolkit | -| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | - - - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/yolov11/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/yolo11_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/yolo11_demo /data/local/tmp/ -adb push model/yolov11n_int8_A311D2.adla /data/local/tmp/ -adb push imgs /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x yolo11_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./yolo11_demo -./yolo11_demo yolov11n_int8_A311D2.adla ./imgs -``` - -**Note:** Replace `yolov11n_int8_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `opencv-python`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -python yolov11.py \ - --model-path ./yolov11n_int8_A311D2.adla \ - --image-dir ./imgs \ - --run-cycles 1 \ - --loglevel INFO -``` - -Argument Descriptions: -| Argument | Description | -| ----------------- | ------------------------------------------------------------ | -| --board-work-path | Work path on board, default is /data/local/tmp | -| --model-path | path to .adla model | -| --image-dir | Directory containing test images | -| --run-cycles | Number of inference cycles, default is 1 | -| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. - -## 5.Results - -**Performance Feedback** - -By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: -- Hardware Information: System and ADLA library versions. -- Model Overview: Basic input/output configurations. -- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. - -**Detection Output** - -The program will print the detection count. The result image with bounding boxes will be saved to the specified output path (`{model_name}_result`). - -You can pull the result folder back to view it: -```bash -adb pull /data/local/tmp/yolov11n_int8_A311D2_result -``` -![alt text](result.jpg) - -**Profiling Visualization** - -After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: -- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. -- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. -- `pie_charts_distribution.html`: Overall resource allocation. - -You can pull the result folder back to view it: -```bash -adb pull /data/local/tmp/yolov11n_int8_A311D2 -``` - -Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. - +# yolov11 + +## 1.Overview + +​ YOLOv11 was released by Ultralytics on October 2, 2024. It introduces significant architectural refinements and efficiency optimizations, delivering superior accuracy-speed trade-offs compared to previous generations. With enhanced feature extraction capabilities, YOLOv11 is designed for high-performance real-time applications—including object detection, instance segmentation, and pose estimation—to handle demanding tasks in a wide range of applications. + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** https://github.com/ultralytics/ultralytics/tree/v8.3.0 + + - **Export Model Step:** + + - **Install ultralytics** + + pip install torch==2.4.1 + + pip install torchvision==0.19.1 + + pip install ultralytics==8.3.0 + + - **Download weights** + + wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11m.pt + + wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11s.pt + + wget https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11n.pt + + - **Export Model** + + ``` + from ultralytics import YOLO + + model = YOLO("yolo11n.pt") + model.export(format="onnx", opset=12, simplify=True, dynamic=False, imgsz=640) + ``` + + +- **Exported Model** + + ​ link to amlogic server( **onnx model or quantized tflite**) + + + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + ./adla_convert.sh yolov11m.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh yolov11s.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh yolov11n.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/yolov11/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/yolo11_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/yolo11_demo /data/local/tmp/ +adb push model/yolov11n_int8_A311D2.adla /data/local/tmp/ +adb push imgs /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x yolo11_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./yolo11_demo +./yolo11_demo yolov11n_int8_A311D2.adla ./imgs +``` + +**Note:** Replace `yolov11n_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python yolov11.py \ + --model-path ./yolov11n_int8_A311D2.adla \ + --image-dir ./imgs \ + --run-cycles 1 \ + --loglevel INFO +``` + +Argument Descriptions: +| Argument | Description | +| ----------------- | ------------------------------------------------------------ | +| --board-work-path | Work path on board, default is /data/local/tmp | +| --model-path | path to .adla model | +| --image-dir | Directory containing test images | +| --run-cycles | Number of inference cycles, default is 1 | +| --loglevel | Logging level: DEBUG / INFO / WARNING / ERROR, default is WARNING | + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results + +**Performance Feedback** + +By setting the loglevel to INFO, the program provides real-time performance metrics upon completion. The console log will display essential hardware and execution details, including: +- Hardware Information: System and ADLA library versions. +- Model Overview: Basic input/output configurations. +- NPU Metrics: Total inference time (latency) and total DRAM bandwidth consumption. + +**Detection Output** + +The program will print the detection count. The result image with bounding boxes will be saved to the specified output path (`{model_name}_result`). + +You can pull the result folder back to view it: +```bash +adb pull /data/local/tmp/yolov11n_int8_A311D2_result +``` +![alt text](result.jpg) + +**Profiling Visualization** + +After a successful run of the Python demo, a folder named after the model (e.g., `{model_name}`) will be generated in the script directory. This folder contains 5 HTML files that provide a visual and detailed breakdown of per-layer performance: +- `hard_op_chart.html` & `soft_op_chart.html`: Hardware/Software op execution details. +- `dram_rd_chart.html` & `dram_wr_chart.html`: Bandwidth read/write distribution. +- `pie_charts_distribution.html`: Overall resource allocation. + +You can pull the result folder back to view it: +```bash +adb pull /data/local/tmp/yolov11n_int8_A311D2 +``` + +Taking hard_op_chart.html as an example (shown below), each layer's ADLA operator name includes parentheses containing the index of the corresponding quantized .tflite layer(s); by default, these indices are suppressed, and operators are labeled generically as "hardware" or "software" without numerical suffixes. + ![alt text](Visualization.png) \ No newline at end of file diff --git a/examples/yolov11/cpp/src/CMakeLists.txt b/examples/yolov11/cpp/src/CMakeLists.txt index f3b7b85..932d206 100755 --- a/examples/yolov11/cpp/src/CMakeLists.txt +++ b/examples/yolov11/cpp/src/CMakeLists.txt @@ -1,35 +1,35 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(yolo11_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set dependency path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(yolo11_demo - main.cpp - postprocess.cpp - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(yolo11_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} +cmake_minimum_required(VERSION 3.10...3.27) +project(yolo11_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set dependency path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(yolo11_demo + main.cpp + postprocess.cpp + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(yolo11_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} ) \ No newline at end of file diff --git a/examples/yolov11/cpp/src/main.cpp b/examples/yolov11/cpp/src/main.cpp index 817e8aa..13687bd 100755 --- a/examples/yolov11/cpp/src/main.cpp +++ b/examples/yolov11/cpp/src/main.cpp @@ -58,9 +58,9 @@ int main(int argc, char** argv) { int nw = img.cols * scale, nh = img.rows * scale; int px = (kInputW - nw) / 2, py = (kInputH - nh) / 2; cv::Mat res, canvas = cv::Mat::zeros(kInputH, kInputW, CV_32FC3); - canvas.setTo(cv::Scalar(114.0/255.0, 114.0/255.0, 114.0/255.0)); + canvas.setTo(cv::Scalar(114.0/255.0, 114.0/255.0, 114.0/255.0)); cv::resize(img, res, {nw, nh}); - res.convertTo(res, CV_32FC3, 1.0 / 255.0); + res.convertTo(res, CV_32FC3, 1.0 / 255.0); res.copyTo(canvas(cv::Rect(px, py, nw, nh))); hwc_to_chw(canvas, chw_buffer.data()); @@ -108,7 +108,7 @@ int main(int argc, char** argv) { for (size_t i = 0; i < indices.size(); i++) { int idx = indices[i]; printf(" %zu. %s (%.2f)\n", i + 1, kClassNames[class_ids[idx]].c_str(), confs[idx]); - + cv::rectangle(img, bboxes[idx], {0, 255, 0}, 2); char text[256]; std::sprintf(text, "%s %.2f", kClassNames[class_ids[idx]].c_str(), confs[idx]); cv::putText(img, text, {bboxes[idx].x, bboxes[idx].y - 5}, cv::FONT_HERSHEY_SIMPLEX, 0.5, {0, 255, 0}, 1); diff --git a/examples/yolov11/cpp/src/postprocess.cpp b/examples/yolov11/cpp/src/postprocess.cpp index 5039d81..23ccd55 100755 --- a/examples/yolov11/cpp/src/postprocess.cpp +++ b/examples/yolov11/cpp/src/postprocess.cpp @@ -1,82 +1,82 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" -#include -#include -#include -#include - -const std::vector kClassNames = { - "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", - "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", - "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", - "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", - "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", - "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", - "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", - "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", - "scissors", "teddy bear", "hair drier", "toothbrush" -}; - -float decode_dfl(const float* dfl_ptr) { - float max_val = -FLT_MAX; - for (int i = 0; i < kDflChannels; i++) { - if (dfl_ptr[i] > max_val) max_val = dfl_ptr[i]; - } - float exp_sum = 0; - float exp_vals[kDflChannels]; - for (int i = 0; i < kDflChannels; i++) { - exp_vals[i] = std::exp(dfl_ptr[i] - max_val); - exp_sum += exp_vals[i]; - } - float res = 0; - for (int i = 0; i < kDflChannels; i++) { - res += (exp_vals[i] / exp_sum) * i; - } - return res; -} - -float calculate_iou(const cv::Rect& a, const cv::Rect& b) { - int xx1 = std::max(a.x, b.x); - int yy1 = std::max(a.y, b.y); - int xx2 = std::min(a.x + a.width, b.x + b.width); - int yy2 = std::min(a.y + a.height, b.y + b.height); - int w = std::max(0, xx2 - xx1); - int h = std::max(0, yy2 - yy1); - float inter = (float)w * h; - float areaA = (float)a.width * a.height; - float areaB = (float)b.width * b.height; - return inter / (areaA + areaB - inter); -} - -std::vector manual_nms(const std::vector& boxes, const std::vector& scores, float thresh) { - std::vector order(boxes.size()); - std::iota(order.begin(), order.end(), 0); - std::stable_sort(order.begin(), order.end(), [&](int a, int b) { - return scores[a] > scores[b]; - }); - std::vector keep; - while (!order.empty()) { - int i = order[0]; - keep.push_back(i); - order.erase(order.begin()); - order.erase(std::remove_if(order.begin(), order.end(), [&](int j) { - return calculate_iou(boxes[i], boxes[j]) > thresh; - }), order.end()); - } - return keep; +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" +#include +#include +#include +#include + +const std::vector kClassNames = { + "person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat", "traffic light", + "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat", "dog", "horse", "sheep", "cow", + "elephant", "bear", "zebra", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", + "skis", "snowboard", "sports ball", "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", + "tennis racket", "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple", + "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair", "couch", + "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse", "remote", "keyboard", + "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator", "book", "clock", "vase", + "scissors", "teddy bear", "hair drier", "toothbrush" +}; + +float decode_dfl(const float* dfl_ptr) { + float max_val = -FLT_MAX; + for (int i = 0; i < kDflChannels; i++) { + if (dfl_ptr[i] > max_val) max_val = dfl_ptr[i]; + } + float exp_sum = 0; + float exp_vals[kDflChannels]; + for (int i = 0; i < kDflChannels; i++) { + exp_vals[i] = std::exp(dfl_ptr[i] - max_val); + exp_sum += exp_vals[i]; + } + float res = 0; + for (int i = 0; i < kDflChannels; i++) { + res += (exp_vals[i] / exp_sum) * i; + } + return res; +} + +float calculate_iou(const cv::Rect& a, const cv::Rect& b) { + int xx1 = std::max(a.x, b.x); + int yy1 = std::max(a.y, b.y); + int xx2 = std::min(a.x + a.width, b.x + b.width); + int yy2 = std::min(a.y + a.height, b.y + b.height); + int w = std::max(0, xx2 - xx1); + int h = std::max(0, yy2 - yy1); + float inter = (float)w * h; + float areaA = (float)a.width * a.height; + float areaB = (float)b.width * b.height; + return inter / (areaA + areaB - inter); +} + +std::vector manual_nms(const std::vector& boxes, const std::vector& scores, float thresh) { + std::vector order(boxes.size()); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [&](int a, int b) { + return scores[a] > scores[b]; + }); + std::vector keep; + while (!order.empty()) { + int i = order[0]; + keep.push_back(i); + order.erase(order.begin()); + order.erase(std::remove_if(order.begin(), order.end(), [&](int j) { + return calculate_iou(boxes[i], boxes[j]) > thresh; + }), order.end()); + } + return keep; } \ No newline at end of file diff --git a/examples/yolov11/cpp/src/postprocess.h b/examples/yolov11/cpp/src/postprocess.h index fa48efa..d04e50b 100755 --- a/examples/yolov11/cpp/src/postprocess.h +++ b/examples/yolov11/cpp/src/postprocess.h @@ -1,42 +1,42 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef YOLO11_POSTPROCESS_H -#define YOLO11_POSTPROCESS_H - -#include -#include -#include - -static constexpr int kInputW = 640; -static constexpr int kInputH = 640; -static constexpr int kNumClasses = 80; -static constexpr int kDflChannels = 16; -static constexpr int kTotalChannels = 144; - -extern const std::vector kClassNames; - -struct Detection { - cv::Rect box; - float confidence; - int class_id; -}; - -float decode_dfl(const float* dfl_ptr); -float calculate_iou(const cv::Rect& a, const cv::Rect& b); -std::vector manual_nms(const std::vector& boxes, const std::vector& scores, float thresh); - +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef YOLO11_POSTPROCESS_H +#define YOLO11_POSTPROCESS_H + +#include +#include +#include + +static constexpr int kInputW = 640; +static constexpr int kInputH = 640; +static constexpr int kNumClasses = 80; +static constexpr int kDflChannels = 16; +static constexpr int kTotalChannels = 144; + +extern const std::vector kClassNames; + +struct Detection { + cv::Rect box; + float confidence; + int class_id; +}; + +float decode_dfl(const float* dfl_ptr); +float calculate_iou(const cv::Rect& a, const cv::Rect& b); +std::vector manual_nms(const std::vector& boxes, const std::vector& scores, float thresh); + #endif \ No newline at end of file diff --git a/examples/yolov11/py/yolov11.py b/examples/yolov11/py/yolov11.py index 1b5e8d0..b77ae95 100755 --- a/examples/yolov11/py/yolov11.py +++ b/examples/yolov11/py/yolov11.py @@ -23,7 +23,7 @@ import cv2 from pathlib import Path from amlnnlite.api import AMLNNLite -class_names = {0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant', 11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'donut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'} +class_names = {0: 'person', 1: 'bicycle', 2: 'car', 3: 'motorcycle', 4: 'airplane', 5: 'bus', 6: 'train', 7: 'truck', 8: 'boat', 9: 'traffic light', 10: 'fire hydrant', 11: 'stop sign', 12: 'parking meter', 13: 'bench', 14: 'bird', 15: 'cat', 16: 'dog', 17: 'horse', 18: 'sheep', 19: 'cow', 20: 'elephant', 21: 'bear', 22: 'zebra', 23: 'giraffe', 24: 'backpack', 25: 'umbrella', 26: 'handbag', 27: 'tie', 28: 'suitcase', 29: 'frisbee', 30: 'skis', 31: 'snowboard', 32: 'sports ball', 33: 'kite', 34: 'baseball bat', 35: 'baseball glove', 36: 'skateboard', 37: 'surfboard', 38: 'tennis racket', 39: 'bottle', 40: 'wine glass', 41: 'cup', 42: 'fork', 43: 'knife', 44: 'spoon', 45: 'bowl', 46: 'banana', 47: 'apple', 48: 'sandwich', 49: 'orange', 50: 'broccoli', 51: 'carrot', 52: 'hot dog', 53: 'pizza', 54: 'doughnut', 55: 'cake', 56: 'chair', 57: 'couch', 58: 'potted plant', 59: 'bed', 60: 'dining table', 61: 'toilet', 62: 'tv', 63: 'laptop', 64: 'mouse', 65: 'remote', 66: 'keyboard', 67: 'cell phone', 68: 'microwave', 69: 'oven', 70: 'toaster', 71: 'sink', 72: 'refrigerator', 73: 'book', 74: 'clock', 75: 'vase', 76: 'scissors', 77: 'teddy bear', 78: 'hair drier', 79: 'toothbrush'} def letterbox(img, new_shape=(640, 640), color=(114, 114, 114)): shape = img.shape[:2] diff --git a/examples/yolov8/README.md b/examples/yolov8/README.md index 3aecc48..e22da13 100644 --- a/examples/yolov8/README.md +++ b/examples/yolov8/README.md @@ -1,133 +1,133 @@ -# yolov8 - -## 1.Overview - -​ YOLOv8 was released by Ultralytics on January 10, 2023, offering cutting-edge performance in terms of accuracy and speed. Building upon the advancements of previous YOLO versions, YOLOv8 introduced new features and optimizations that make it an ideal choice for various [object detection](https://www.ultralytics.com/blog/a-guide-to-deep-dive-into-object-detection-in-2025) tasks in a wide range of applications. - -## 2.Model Download - -- **Open Source model** - - - **Open Source projects:** https://github.com/ultralytics/ultralytics/tree/v8.2.0 - - - **Export Model Step:** - - - **Install ultralytics** - - pip install torch==2.4.1 - - pip install torchvision==0.19.1 - - pip install ultralytics==8.2.0 - - - **Download weights** - - wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8m.pt - - wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8s.pt - - wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8n.pt - - - **Export Model** - - ``` - from ultralytics import YOLO - - model = YOLO("yolov8m.pt") - model.export(format="onnx", opset=12, simplify=True, dynamic=False, imgsz=640) - ``` - - -- **Exported Model** - - ​ link to amlogic server( **onnx model or quantized tflite**) - - - -## 3. Model Conversion - -``` -cd model -Usage: ./adla_covnert.sh model_path adla_tookkit_path target_platform - -example - ./adla_covnert.sh yolov8m.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 - ./adla_covnert.sh yolov8s.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 - ./adla_covnert.sh yolov8n.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 -``` - -| Parameter | Discription | -| ----------------- | ------------------------------------------------------------ | -| model_path | onnx model path | -| adla_tookkit_path | path to adla_toolkit | -| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003. for S905X5: PRODUCT_PID0XA005 | - - - -## 4. Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/yolov8/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/yolov8_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/yolov8_demo /data/local/tmp/ -adb push model/yolov8s_int8_A311D2.adla /data/local/tmp/ -adb push test_image.jpg /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x yolov8_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./yolo_world_demo -./yolov8_demo yolov8s_int8_A311D2.adla test_image.jpg" -``` - -**Note:** Replace `yolov8s_int8_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `opencv-python`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -python yolov8.py --model-path ./yolov8s_int8_A311D2.adla -``` - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. - -## 5.Results -The program will print the detection count and inference time. The result image with bounding boxes will be saved to the specified output path (`result.jpg` by default). - - -You can pull the result image back to view it: -```bash -adb pull result.jpg. -``` -![alt text](result.jpg) - +# yolov8 + +## 1.Overview + +​ YOLOv8 was released by Ultralytics on January 10, 2023, offering cutting-edge performance in terms of accuracy and speed. Building upon the advancements of previous YOLO versions, YOLOv8 introduced new features and optimizations that make it an ideal choice for various [object detection](https://www.ultralytics.com/blog/a-guide-to-deep-dive-into-object-detection-in-2025) tasks in a wide range of applications. + +## 2.Model Download + +- **Open Source model** + + - **Open Source projects:** https://github.com/ultralytics/ultralytics/tree/v8.2.0 + + - **Export Model Step:** + + - **Install ultralytics** + + pip install torch==2.4.1 + + pip install torchvision==0.19.1 + + pip install ultralytics==8.2.0 + + - **Download weights** + + wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8m.pt + + wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8s.pt + + wget https://github.com/ultralytics/assets/releases/download/v8.2.0/yolov8n.pt + + - **Export Model** + + ``` + from ultralytics import YOLO + + model = YOLO("yolov8m.pt") + model.export(format="onnx", opset=12, simplify=True, dynamic=False, imgsz=640) + ``` + + +- **Exported Model** + + ​ link to amlogic server( **onnx model or quantized tflite**) + + + +## 3. Model Conversion + +``` +cd model +Usage: ./adla_convert.sh model_path adla_toolkit_path target_platform + +example + ./adla_convert.sh yolov8m.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh yolov8s.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 + ./adla_convert.sh yolov8n.onnx /xxxx/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 +``` + +| Parameter | Description | +| ----------------- | ------------------------------------------------------------ | +| model_path | onnx model path | +| adla_toolkit_path | path to adla_toolkit | +| target_platform | Specify target platform. for A311D2 : PRODUCT_PID0XA003。for S905X5: PRODUCT_PID0XA005 | + + + +## 4. Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/yolov8/cpp +./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/yolov8_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/yolov8_demo /data/local/tmp/ +adb push model/yolov8s_int8_A311D2.adla /data/local/tmp/ +adb push test_image.jpg /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x yolov8_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./yolo_world_demo +./yolov8_demo yolov8s_int8_A311D2.adla test_image.jpg" +``` + +**Note:** Replace `yolov8s_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +python yolov8.py --model-path ./yolov8s_int8_A311D2.adla +``` + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the current directory and save results to a `{model_name}_result` folder. + +## 5.Results +The program will print the detection count and inference time. The result image with bounding boxes will be saved to the specified output path (`result.jpg` by default). + + +You can pull the result image back to view it: +```bash +adb pull result.jpg. +``` +![alt text](result.jpg) + diff --git a/examples/yolov8/cpp/src/CMakeLists.txt b/examples/yolov8/cpp/src/CMakeLists.txt index 0b41eb6..7b13419 100755 --- a/examples/yolov8/cpp/src/CMakeLists.txt +++ b/examples/yolov8/cpp/src/CMakeLists.txt @@ -1,36 +1,36 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(yolo_world_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set 3rdparty path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(yolov8_demo - main.cpp - postprocess.cpp - postprocess.h - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(yolov8_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} -) +cmake_minimum_required(VERSION 3.10...3.27) +project(yolo_world_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(yolov8_demo + main.cpp + postprocess.cpp + postprocess.h + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(yolov8_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} +) diff --git a/examples/yolov8/cpp/src/main.cpp b/examples/yolov8/cpp/src/main.cpp index 9f6328b..757bcb2 100755 --- a/examples/yolov8/cpp/src/main.cpp +++ b/examples/yolov8/cpp/src/main.cpp @@ -100,7 +100,7 @@ int main(int argc, char** argv) { float* outbuf2 = (float*)outdata->out[2].buf; const int channels = 144; // 64 DFL + 80 classes - + std::vector detections = postprocess( std::make_tuple(outbuf0, std::make_tuple(MODEL_INPUT_HEIGHT / 8, MODEL_INPUT_WIDTH / 8, channels), 8), std::make_tuple(outbuf1, std::make_tuple(MODEL_INPUT_HEIGHT / 16, MODEL_INPUT_WIDTH / 16, channels), 16), diff --git a/examples/yolov8/cpp/src/postprocess.cpp b/examples/yolov8/cpp/src/postprocess.cpp index 0bbe544..57ac8c8 100755 --- a/examples/yolov8/cpp/src/postprocess.cpp +++ b/examples/yolov8/cpp/src/postprocess.cpp @@ -59,7 +59,7 @@ static float compute_iou(const Detection& det1, const Detection& det2) { float area1 = (det1.x2 - det1.x1) * (det1.y2 - det1.y1); float area2 = (det2.x2 - det2.x1) * (det2.y2 - det2.y1); - + return inter / (area1 + area2 - inter); } @@ -96,7 +96,7 @@ static std::vector nms_by_class(const std::vector& detecti std::tuple> preprocess(cv::Mat img, std::tuple new_shape) { cv::Mat img_rgb; - + if (img.empty()) { LOGE("Preprocess received empty image"); return {}; @@ -128,7 +128,7 @@ std::tuple> preprocess(cv::Mat img, std::tu int pad_bottom = static_cast(round(pad_h / 2.0 + 0.1)); cv::Mat img_padded; - cv::copyMakeBorder(img_resized, img_padded, pad_top, pad_bottom, pad_left, pad_right, + cv::copyMakeBorder(img_resized, img_padded, pad_top, pad_bottom, pad_left, pad_right, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114)); cv::Mat img_float; @@ -155,14 +155,14 @@ cv::Mat quantize_input(const cv::Mat& float_img, float scale, int8_t zero_point) return quantized_img; } -static std::vector get_detections(float* output, std::tuple output_shape, +static std::vector get_detections(float* output, std::tuple output_shape, int stride, float conf_thresh) { std::vector detections; int grid_h = std::get<0>(output_shape); int grid_w = std::get<1>(output_shape); int channels = std::get<2>(output_shape); - + const int num_classes = 80; const int dfl_channels = 64; // 4 directions * 16 bins @@ -288,9 +288,9 @@ cv::Mat draw_detections(cv::Mat image, const std::vector& detections) cv::Scalar color(rgb.at(0, 0)[0], rgb.at(0, 0)[1], rgb.at(0, 0)[2]); // Draw bounding box - cv::rectangle(drawn_image, + cv::rectangle(drawn_image, cv::Point(static_cast(det.x1), static_cast(det.y1)), - cv::Point(static_cast(det.x2), static_cast(det.y2)), + cv::Point(static_cast(det.x2), static_cast(det.y2)), color, 2); // Draw label @@ -304,17 +304,17 @@ cv::Mat draw_detections(cv::Mat image, const std::vector& detections) label_y = static_cast(det.y1) + text_size.height + 5; // Draw label background - cv::rectangle(drawn_image, + cv::rectangle(drawn_image, cv::Point(label_x, label_y - text_size.height - baseline), - cv::Point(label_x + text_size.width, label_y + baseline), + cv::Point(label_x + text_size.width, label_y + baseline), color, cv::FILLED); // Determine text color based on background brightness int brightness = (color[0] + color[1] + color[2]) / 3; cv::Scalar text_color = brightness < 128 ? cv::Scalar(255, 255, 255) : cv::Scalar(0, 0, 0); - cv::putText(drawn_image, label, - cv::Point(label_x, label_y), + cv::putText(drawn_image, label, + cv::Point(label_x, label_y), cv::FONT_HERSHEY_SIMPLEX, 0.6, text_color, 1, cv::LINE_AA); } return drawn_image; diff --git a/examples/yolov8/model/.gitkeep b/examples/yolov8/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yolov8/model/adla_convert.sh b/examples/yolov8/model/adla_convert.sh index b7d0362..9f1c91a 100755 --- a/examples/yolov8/model/adla_convert.sh +++ b/examples/yolov8/model/adla_convert.sh @@ -1,8 +1,24 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + # 1. $1: set ADLA_TOOL_PATH -# 2. $2: set target-plaftorm +# 2. $2: set target-platform # for A311D2 target-platform is PRODUCT_PID0XA003 # for S905X5 target-platform is PRODUCT_PID0XA005 -# Usage: ./adla_covnert.sh yolov8m.onnx /XXX/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 +# Usage: ./adla_convert.sh yolov8m.onnx /XXX/adla-toolkit-binary-3.2.9.3 PRODUCT_PID0XA005 model_path=$1 ADLA_TOOL_PATH=$2 @@ -10,7 +26,7 @@ target_platform=$3 echo "model_path:[$model_path]" echo "ADLA_TOOL_PATH:[$ADLA_TOOL_PATH]" -echo "target-plaftorm:[$target_platform]" +echo "target-platform:[$target_platform]" adla_convert=${ADLA_TOOL_PATH}/bin/adla_convert diff --git a/examples/yolov8/py/.gitkeep b/examples/yolov8/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yolov8/py/yolov8.py b/examples/yolov8/py/yolov8.py index 6ae3d1e..d26adb0 100755 --- a/examples/yolov8/py/yolov8.py +++ b/examples/yolov8/py/yolov8.py @@ -1,3 +1,19 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + import numpy as np import os import glob @@ -206,7 +222,7 @@ def main(): # Initialize AMLNNLite amlnn = AMLNNLite() amlnn.config( - model_path=args.model_path, # Model file path, Support ADLD and quantized TFlite models + model_path=args.model_path, # Model file path, Support ADLA and quantized TFlite models run_cycles=args.run_cycles ) amlnn.init() diff --git a/examples/yoloworld/README.md b/examples/yoloworld/README.md index 8293378..c0f0908 100644 --- a/examples/yoloworld/README.md +++ b/examples/yoloworld/README.md @@ -1,72 +1,72 @@ -## Demo Run - -### CPP - -#### 1. Compile - -**Prerequisites:** -- Android NDK (r25e recommended) -- `ANDROID_NDK_PATH` environment variable set - -**Build:** -```bash -# Build for arm64-v8a -cd examples/yoloworld/cpp -AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a -``` - -The executable will be generated at `build/android/yolo_world_demo` (Note: executable name may vary, verify in build folder). - -#### 2. Run - -```bash -# Push executable to device -adb push build/android/yolo_world_demo /data/local/tmp/ -adb push model/yoloworld_int8_A311D2.adla /data/local/tmp/ -adb push test_image.jpg /data/local/tmp/ - -# Run on device -adb shell -cd /data/local/tmp -chmod +x yolo_world_demo -export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) - -# Usage: ./yolo_world_demo -./yolo_world_demo yoloworld_int8_A311D2.adla test_image.jpg -``` - -**Note:** Replace `yoloworld_int8_A311D2.adla` with your actual model file path. - -### Python - -**Prerequisites:** -- Python 3.10 -- Required packages: `numpy`, `opencv-python`, `amlnnlite` - -**Install dependencies:** -```bash -pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl -``` - -**Run on device:** -```bash -# Basic usage (process current directory) -python yoloworld.py --model-path ./yoloworld_int8_A311D2.adla - -# Specify image directory -python yoloworld.py --model-path ./yoloworld_int8_A311D2.adla --image-dir ./ -``` - -The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the specified directory and save results to a `{model_name}_result` folder. - -## Results - -The program will print the detection count and detected objects for each processed image. The result image with bounding boxes will be saved to the specified output directory. - -You can pull the result image back to view it: -```bash -adb pull result.jpg. -``` -![alt text](result.jpg) - -The program detects objects from predefined classes (handbag, backpack, wallet, watch, necklace, bracelet, earrings, finger ring, sunglass, hat, shoes, belt, makeup palette, lipstick tube, car, truck, bicycle, motorcycle, phone, laptop, camera, wine bottle, stuffed toy) and draws bounding boxes with class labels on the result images. +## Demo Run + +### CPP + +#### 1. Compile + +**Prerequisites:** +- Android NDK (r25e recommended) +- `ANDROID_NDK_PATH` environment variable set + +**Build:** +```bash +# Build for arm64-v8a +cd examples/yoloworld/cpp +AMLNN_HOME=/path/to/amlnn-toolkit ./build-android.sh -a arm64-v8a +``` + +The executable will be generated at `build/android/yolo_world_demo` (Note: executable name may vary, verify in build folder). + +#### 2. Run + +```bash +# Push executable to device +adb push build/android/yolo_world_demo /data/local/tmp/ +adb push model/yoloworld_int8_A311D2.adla /data/local/tmp/ +adb push test_image.jpg /data/local/tmp/ + +# Run on device +adb shell +cd /data/local/tmp +chmod +x yolo_world_demo +export LD_LIBRARY_PATH=/vendor/lib64 or (/vendor/lib) + +# Usage: ./yolo_world_demo +./yolo_world_demo yoloworld_int8_A311D2.adla test_image.jpg +``` + +**Note:** Replace `yoloworld_int8_A311D2.adla` with your actual model file path. + +### Python + +**Prerequisites:** +- Python 3.10 +- Required packages: `numpy`, `opencv-python`, `amlnnlite` + +**Install dependencies:** +```bash +pip install numpy opencv-python amlnnlite-1.0.0-cp310-cp310-linux_aarch64.whl +``` + +**Run on device:** +```bash +# Basic usage (process current directory) +python yoloworld.py --model-path ./yoloworld_int8_A311D2.adla + +# Specify image directory +python yoloworld.py --model-path ./yoloworld_int8_A311D2.adla --image-dir ./ +``` + +The script will automatically process all image files (`.jpg`, `.jpeg`, `.png`, `.bmp`) in the specified directory and save results to a `{model_name}_result` folder. + +## Results + +The program will print the detection count and detected objects for each processed image. The result image with bounding boxes will be saved to the specified output directory. + +You can pull the result image back to view it: +```bash +adb pull result.jpg. +``` +![alt text](result.jpg) + +The program detects objects from predefined classes (handbag, backpack, wallet, watch, necklace, bracelet, earrings, finger ring, sunglass, hat, shoes, belt, makeup palette, lipstick tube, car, truck, bicycle, motorcycle, phone, laptop, camera, wine bottle, stuffed toy) and draws bounding boxes with class labels on the result images. diff --git a/examples/yoloworld/cpp/.gitkeep b/examples/yoloworld/cpp/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloworld/cpp/src/CMakeLists.txt b/examples/yoloworld/cpp/src/CMakeLists.txt index 99ffda2..56555c2 100755 --- a/examples/yoloworld/cpp/src/CMakeLists.txt +++ b/examples/yoloworld/cpp/src/CMakeLists.txt @@ -1,35 +1,35 @@ -cmake_minimum_required(VERSION 3.10...3.27) -project(yolo_world_demo) - -set(CMAKE_CXX_STANDARD 17) - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") -find_package(AMLNN REQUIRED) -include_directories(${AMLNN_INCLUDE_DIR}) -link_directories(${AMLNN_LIBRARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/../../../../common) - -# Set 3rdparty path -set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") - -if(CMAKE_SYSTEM_NAME STREQUAL "Android") - # Android needs log - link_libraries(log) -endif() - -# Find OpenCV -find_package(OpenCV REQUIRED) -include_directories(${OpenCV_INCLUDE_DIRS}) - -add_executable(yolo_world_demo - main.cpp - postprocess.cpp - postprocess.h - ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp -) - -target_link_libraries(yolo_world_demo - ${OpenCV_LIBS} - ${AMLNN_LIBRARY} -) +cmake_minimum_required(VERSION 3.10...3.27) +project(yolo_world_demo) + +set(CMAKE_CXX_STANDARD 17) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../../../../cmake") +find_package(AMLNN REQUIRED) +include_directories(${AMLNN_INCLUDE_DIR}) +link_directories(${AMLNN_LIBRARY_DIR}) + +include_directories(${CMAKE_SOURCE_DIR}/../../../../common) + +# Set 3rdparty path +set(3RDPARTY_DIR "${CMAKE_SOURCE_DIR}/../../../../dependency") + +if(CMAKE_SYSTEM_NAME STREQUAL "Android") + # Android needs log + link_libraries(log) +endif() + +# Find OpenCV +find_package(OpenCV REQUIRED) +include_directories(${OpenCV_INCLUDE_DIRS}) + +add_executable(yolo_world_demo + main.cpp + postprocess.cpp + postprocess.h + ${CMAKE_SOURCE_DIR}/../../../../common/model_loader.cpp +) + +target_link_libraries(yolo_world_demo + ${OpenCV_LIBS} + ${AMLNN_LIBRARY} +) diff --git a/examples/yoloworld/cpp/src/main.cpp b/examples/yoloworld/cpp/src/main.cpp index 72ea516..e98be69 100755 --- a/examples/yoloworld/cpp/src/main.cpp +++ b/examples/yoloworld/cpp/src/main.cpp @@ -1,119 +1,119 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include "postprocess.h" -#include "model_loader.h" - -const std::string DEFAULT_OUTPUT_PATH = "./result.jpg"; -const int MODEL_INPUT_WIDTH = 640; -const int MODEL_INPUT_HEIGHT = 480; -const float SCORE_THRESHOLD = 0.4f; -const float NMS_THRESHOLD = 0.45f; - -const std::vector CLASS_NAMES = { - "handbag", "backpack", "wallet", - "watch", "necklace", "bracelet", "earrings", "finger ring", "sunglass", "hat", "shoes", "belt", - "makeup palette", "lipstick tube", - "car", "truck", "bicycle", "motorcycle", - "phone", "laptop", "camera", "wine bottle", "stuffed toy" -}; - -int main(int argc, char** argv) { - std::string model_path; - std::string image_path; - if (argc != 3) - { - printf("%s \n", argv[0]); - return -1; - } - - if (argc > 1) model_path = argv[1]; - if (argc > 2) image_path = argv[2]; - - std::cout << "YOLOWorld Native Demo" << std::endl; - std::cout << "Model: " << model_path << std::endl; - std::cout << "Image: " << image_path << std::endl; - std::cout << "Output: " << DEFAULT_OUTPUT_PATH << std::endl; - - // 1. Load Image - cv::Mat img = cv::imread(image_path); - if (img.empty()) { - std::cerr << "Failed to load image from " << image_path << std::endl; - return -1; - } - - // 2. Initialize Network - void* context = init_network(model_path.c_str()); - if (!context) { - std::cerr << "Failed to initialize network." << std::endl; - return -1; - } - - // 3. Preprocess - auto start_time = std::chrono::high_resolution_clock::now(); - - std::tuple> input_tuple = - preprocess(img, std::make_tuple(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH)); - - // 4. Run Network - void* output_ptr = run_network(context, {input_tuple}); - if (!output_ptr) { - std::cerr << "Failed to run network." << std::endl; - uninit_network(context); - return -1; - } - nn_output* outdata = (nn_output*)output_ptr; - - // 5. Postprocess - float* outbuf0 = (float*)outdata->out[0].buf; - float* outbuf1 = (float*)outdata->out[1].buf; - float* outbuf2 = (float*)outdata->out[2].buf; - - int num_classes = CLASS_NAMES.size(); - int channels = 87; - - std::vector detections = postprocess( - std::make_tuple(outbuf0, std::make_tuple(MODEL_INPUT_HEIGHT / 8, MODEL_INPUT_WIDTH / 8, channels), 8), - std::make_tuple(outbuf1, std::make_tuple(MODEL_INPUT_HEIGHT / 16, MODEL_INPUT_WIDTH / 16, channels), 16), - std::make_tuple(outbuf2, std::make_tuple(MODEL_INPUT_HEIGHT / 32, MODEL_INPUT_WIDTH / 32, channels), 32), - input_tuple, - SCORE_THRESHOLD, - NMS_THRESHOLD, - num_classes, - 1 // reverse=1 for YOLOWorld format - ); - - auto end_time = std::chrono::high_resolution_clock::now(); - std::chrono::duration inference_time = end_time - start_time; - std::cout << "Inference + Postprocess time: " << inference_time.count() << " ms" << std::endl; - std::cout << "Detections found: " << detections.size() << std::endl; - - // 6. Draw and Save - cv::Mat result_img = draw_detections(img, detections, CLASS_NAMES); - cv::imwrite(DEFAULT_OUTPUT_PATH, result_img); - std::cout << "Result saved to " << DEFAULT_OUTPUT_PATH << std::endl; - - // 7. Cleanup - uninit_network(context); - - return 0; -} +/* + * Copyright (C) 2024–2025 Amlogic, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include "postprocess.h" +#include "model_loader.h" + +const std::string DEFAULT_OUTPUT_PATH = "./result.jpg"; +const int MODEL_INPUT_WIDTH = 640; +const int MODEL_INPUT_HEIGHT = 480; +const float SCORE_THRESHOLD = 0.4f; +const float NMS_THRESHOLD = 0.45f; + +const std::vector CLASS_NAMES = { + "handbag", "backpack", "wallet", + "watch", "necklace", "bracelet", "earrings", "finger ring", "sunglass", "hat", "shoes", "belt", + "makeup palette", "lipstick tube", + "car", "truck", "bicycle", "motorcycle", + "phone", "laptop", "camera", "wine bottle", "stuffed toy" +}; + +int main(int argc, char** argv) { + std::string model_path; + std::string image_path; + if (argc != 3) + { + printf("%s \n", argv[0]); + return -1; + } + + if (argc > 1) model_path = argv[1]; + if (argc > 2) image_path = argv[2]; + + std::cout << "YOLOWorld Native Demo" << std::endl; + std::cout << "Model: " << model_path << std::endl; + std::cout << "Image: " << image_path << std::endl; + std::cout << "Output: " << DEFAULT_OUTPUT_PATH << std::endl; + + // 1. Load Image + cv::Mat img = cv::imread(image_path); + if (img.empty()) { + std::cerr << "Failed to load image from " << image_path << std::endl; + return -1; + } + + // 2. Initialize Network + void* context = init_network(model_path.c_str()); + if (!context) { + std::cerr << "Failed to initialize network." << std::endl; + return -1; + } + + // 3. Preprocess + auto start_time = std::chrono::high_resolution_clock::now(); + + std::tuple> input_tuple = + preprocess(img, std::make_tuple(MODEL_INPUT_HEIGHT, MODEL_INPUT_WIDTH)); + + // 4. Run Network + void* output_ptr = run_network(context, {input_tuple}); + if (!output_ptr) { + std::cerr << "Failed to run network." << std::endl; + uninit_network(context); + return -1; + } + nn_output* outdata = (nn_output*)output_ptr; + + // 5. Postprocess + float* outbuf0 = (float*)outdata->out[0].buf; + float* outbuf1 = (float*)outdata->out[1].buf; + float* outbuf2 = (float*)outdata->out[2].buf; + + int num_classes = CLASS_NAMES.size(); + int channels = 87; + + std::vector detections = postprocess( + std::make_tuple(outbuf0, std::make_tuple(MODEL_INPUT_HEIGHT / 8, MODEL_INPUT_WIDTH / 8, channels), 8), + std::make_tuple(outbuf1, std::make_tuple(MODEL_INPUT_HEIGHT / 16, MODEL_INPUT_WIDTH / 16, channels), 16), + std::make_tuple(outbuf2, std::make_tuple(MODEL_INPUT_HEIGHT / 32, MODEL_INPUT_WIDTH / 32, channels), 32), + input_tuple, + SCORE_THRESHOLD, + NMS_THRESHOLD, + num_classes, + 1 // reverse=1 for YOLOWorld format + ); + + auto end_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration inference_time = end_time - start_time; + std::cout << "Inference + Postprocess time: " << inference_time.count() << " ms" << std::endl; + std::cout << "Detections found: " << detections.size() << std::endl; + + // 6. Draw and Save + cv::Mat result_img = draw_detections(img, detections, CLASS_NAMES); + cv::imwrite(DEFAULT_OUTPUT_PATH, result_img); + std::cout << "Result saved to " << DEFAULT_OUTPUT_PATH << std::endl; + + // 7. Cleanup + uninit_network(context); + + return 0; +} diff --git a/examples/yoloworld/cpp/src/postprocess.cpp b/examples/yoloworld/cpp/src/postprocess.cpp index bc9d589..294ad60 100755 --- a/examples/yoloworld/cpp/src/postprocess.cpp +++ b/examples/yoloworld/cpp/src/postprocess.cpp @@ -1,291 +1,291 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "nn_sdk.h" - -#define LOGI(...) do { printf(__VA_ARGS__); printf("\n"); } while(0) -#define LOGE(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while(0) - -static float compute_iou(const Detection& det1, const Detection& det2) { - float xx1 = std::max(det1.x1, det2.x1); - float yy1 = std::max(det1.y1, det2.y1); - float xx2 = std::min(det1.x2, det2.x2); - float yy2 = std::min(det1.y2, det2.y2); - - float w = std::max(0.0f, xx2 - xx1); - float h = std::max(0.0f, yy2 - yy1); - float inter = w * h; - - float area1 = (det1.x2 - det1.x1) * (det1.y2 - det1.y1); - float area2 = (det2.x2 - det2.x1) * (det2.y2 - det2.y1); - - return inter / (area1 + area2 - inter); -} - -static std::vector nms_by_class(const std::vector& detections, float iou_threshold) { - if (detections.empty()) return {}; - - std::vector final_detections; - - std::unordered_map> class_detections; - for (const auto& det : detections) { - class_detections[det.class_id].push_back(det); - } - - for (auto& [class_id, cls_dets] : class_detections) { - std::sort(cls_dets.begin(), cls_dets.end(), [](const Detection& a, const Detection& b) { - return a.score > b.score; - }); - - std::vector removed(cls_dets.size(), false); - for (size_t i = 0; i < cls_dets.size(); ++i) { - if (removed[i]) continue; - final_detections.push_back(cls_dets[i]); - - for (size_t j = i + 1; j < cls_dets.size(); ++j) { - if (removed[j]) continue; - if (compute_iou(cls_dets[i], cls_dets[j]) > iou_threshold) { - removed[j] = true; - } - } - } - } - return final_detections; -} - -static std::vector suppress_cross_class_iou_conflicts(std::vector detections, float iou_threshold) { - std::sort(detections.begin(), detections.end(), [](const Detection& a, const Detection& b) { - return a.score > b.score; - }); - - std::vector removed(detections.size(), false); - std::vector final_detections; - - for (size_t i = 0; i < detections.size(); ++i) { - if (removed[i]) continue; - final_detections.push_back(detections[i]); - for (size_t j = i + 1; j < detections.size(); ++j) { - if (removed[j]) continue; - if (detections[i].class_id != detections[j].class_id && - compute_iou(detections[i], detections[j]) > iou_threshold) { - removed[j] = true; - } - } - } - return final_detections; -} - -static float sigmoid(float x) { - return 1.0f / (1.0f + std::exp(-x)); -} - -static std::vector get_detections(float* output, std::tuple output_shape, - int stride, float conf_thresh, int num_classes, int reverse) { - std::vector detections; - - int grid_h = std::get<0>(output_shape); - int grid_w = std::get<1>(output_shape); - int total_cells = grid_h * grid_w; - int coords = 4 * 16; // DFL coords: 64 - - // reverse=0: standard YOLO [classes + box] - // reverse>0: YOLOWorld [box + classes] - int cls_offset = (reverse > 0) ? coords : 0; - int dfl_offset = (reverse > 0) ? 0 : num_classes; - - for (int i = 0; i < grid_h; ++i) { - for (int j = 0; j < grid_w; ++j) { - int idx = (i * grid_w + j) * (num_classes + coords); - - float max_score = -1.0f; - int class_id = -1; - for (int c = 0; c < num_classes; ++c) { - int cls_idx = idx + cls_offset + c; - float score = sigmoid(output[cls_idx]); - if (score > max_score) { - max_score = score; - class_id = c; - } - } - - if (max_score < conf_thresh) continue; - - float exp_vals[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - for (int k = 0; k < 4; ++k) { - int dfl_idx = idx + dfl_offset + k * 16; - float exp_logits[16]; - float sum_exp = 0.0f; - - float max_logit = output[dfl_idx]; - for (int t = 1; t < 16; ++t) { - if (output[dfl_idx + t] > max_logit) max_logit = output[dfl_idx + t]; - } - - for (int t = 0; t < 16; ++t) { - exp_logits[t] = std::exp(output[dfl_idx + t] - max_logit); - sum_exp += exp_logits[t]; - } - - for (int t = 0; t < 16; ++t) { - exp_logits[t] /= sum_exp; - exp_vals[k] += t * exp_logits[t]; - } - } - - float x1 = (j + 0.5f - exp_vals[0]) * stride; - float y1 = (i + 0.5f - exp_vals[1]) * stride; - float x2 = (j + 0.5f + exp_vals[2]) * stride; - float y2 = (i + 0.5f + exp_vals[3]) * stride; - - detections.push_back({x1, y1, x2, y2, max_score, class_id}); - } - } - return detections; -} - - -std::tuple> preprocess(cv::Mat img, std::tuple new_shape) { - cv::Mat img_rgb; - // Check if image is valid - if (img.empty()) { - LOGE("Preprocess received empty image"); - return {}; - } - - if (img.channels() == 4) - cv::cvtColor(img, img_rgb, cv::COLOR_RGBA2RGB); - else if (img.channels() == 3) - img_rgb = img.clone(); - - if (img.channels() == 3) { - cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB); - } - - int orig_h = img.rows; - int orig_w = img.cols; - float scale = std::min(static_cast(std::get<0>(new_shape)) / orig_h, static_cast(std::get<1>(new_shape)) / orig_w); - int new_h = static_cast(round(orig_h * scale)); - int new_w = static_cast(round(orig_w * scale)); - - cv::Mat img_resized; - cv::resize(img_rgb, img_resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); - - int pad_h = std::get<0>(new_shape) - new_h; - int pad_w = std::get<1>(new_shape) - new_w; - int pad_left = static_cast(round(pad_w / 2. - 0.1f)); - int pad_right = static_cast(round(pad_w / 2. + 0.1f)); - int pad_top = static_cast(round(pad_h / 2. - 0.1f)); - int pad_bottom = static_cast(round(pad_h / 2. + 0.1f)); - - cv::Mat img_padded; - cv::copyMakeBorder(img_resized, img_padded, pad_top, pad_bottom, pad_left, pad_right, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114)); - - cv::Mat img_float; - img_padded.convertTo(img_float, CV_32F, 1.0 / 255.0); - - return std::make_tuple(img_float, scale, std::make_tuple(pad_left, pad_top)); -} - - - -std::vector postprocess(std::tuple, int> out0, - std::tuple, int> out1, - std::tuple, int> out2, - std::tuple> input_tuple, - float conf_thresh, float iou_threshold, int num_classes, int reverse) { - float scale = std::get<1>(input_tuple); - int pad_left = std::get<0>(std::get<2>(input_tuple)); - int pad_top = std::get<1>(std::get<2>(input_tuple)); - - std::vector detections; - - auto process_out = [&](auto& out) { - float* output = std::get<0>(out); - auto shape = std::get<1>(out); - int stride = std::get<2>(out); - std::vector dets = get_detections(output, shape, stride, conf_thresh, num_classes, reverse); - detections.insert(detections.end(), dets.begin(), dets.end()); - }; - - process_out(out0); - process_out(out1); - process_out(out2); - - std::vector detections_orig; - for (const auto& det : detections) { - float x1_orig = (det.x1 - pad_left) / scale; - float y1_orig = (det.y1 - pad_top) / scale; - float x2_orig = (det.x2 - pad_left) / scale; - float y2_orig = (det.y2 - pad_top) / scale; - detections_orig.push_back({x1_orig, y1_orig, x2_orig, y2_orig, det.score, det.class_id}); - } - - std::vector detections_nms = nms_by_class(detections_orig, iou_threshold); - return suppress_cross_class_iou_conflicts(detections_nms, 0.8f); -} - -cv::Mat draw_detections(cv::Mat image, const std::vector& detections, - const std::vector& classes, int seed_offset) { - int num_classes = classes.size(); - std::vector color_palette; - std::mt19937 rng(42 + seed_offset); - std::uniform_int_distribution color_dist(0, 255); - - for (int i = 0; i < num_classes; ++i) { - color_palette.emplace_back(color_dist(rng), color_dist(rng), color_dist(rng)); - } - - cv::Mat drawn_image = image.clone(); - - for (const auto& det : detections) { - int class_id = det.class_id; - if (class_id < 0 || class_id >= num_classes) continue; - - cv::Scalar color = color_palette[class_id]; - cv::rectangle(drawn_image, - cv::Point(static_cast(det.x1), static_cast(det.y1)), - cv::Point(static_cast(det.x2), static_cast(det.y2)), - color, 2); - - std::string label = classes[class_id] + ": " + cv::format("%.2f", det.score); - int baseline = 0; - cv::Size text_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline); - - int label_x = static_cast(det.x1); - int label_y = static_cast(det.y1) - 10; - if (label_y < text_size.height) label_y = static_cast(det.y1) + text_size.height + 10; - - cv::rectangle(drawn_image, - cv::Point(label_x, label_y - text_size.height - baseline), - cv::Point(label_x + text_size.width, label_y + baseline), - color, cv::FILLED); - - cv::putText(drawn_image, label, - cv::Point(label_x, label_y), - cv::FONT_HERSHEY_SIMPLEX, 0.5, - cv::Scalar(0, 0, 0), 1, cv::LINE_AA); - } - return drawn_image; -} +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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 "postprocess.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "nn_sdk.h" + +#define LOGI(...) do { printf(__VA_ARGS__); printf("\n"); } while(0) +#define LOGE(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } while(0) + +static float compute_iou(const Detection& det1, const Detection& det2) { + float xx1 = std::max(det1.x1, det2.x1); + float yy1 = std::max(det1.y1, det2.y1); + float xx2 = std::min(det1.x2, det2.x2); + float yy2 = std::min(det1.y2, det2.y2); + + float w = std::max(0.0f, xx2 - xx1); + float h = std::max(0.0f, yy2 - yy1); + float inter = w * h; + + float area1 = (det1.x2 - det1.x1) * (det1.y2 - det1.y1); + float area2 = (det2.x2 - det2.x1) * (det2.y2 - det2.y1); + + return inter / (area1 + area2 - inter); +} + +static std::vector nms_by_class(const std::vector& detections, float iou_threshold) { + if (detections.empty()) return {}; + + std::vector final_detections; + + std::unordered_map> class_detections; + for (const auto& det : detections) { + class_detections[det.class_id].push_back(det); + } + + for (auto& [class_id, cls_dets] : class_detections) { + std::sort(cls_dets.begin(), cls_dets.end(), [](const Detection& a, const Detection& b) { + return a.score > b.score; + }); + + std::vector removed(cls_dets.size(), false); + for (size_t i = 0; i < cls_dets.size(); ++i) { + if (removed[i]) continue; + final_detections.push_back(cls_dets[i]); + + for (size_t j = i + 1; j < cls_dets.size(); ++j) { + if (removed[j]) continue; + if (compute_iou(cls_dets[i], cls_dets[j]) > iou_threshold) { + removed[j] = true; + } + } + } + } + return final_detections; +} + +static std::vector suppress_cross_class_iou_conflicts(std::vector detections, float iou_threshold) { + std::sort(detections.begin(), detections.end(), [](const Detection& a, const Detection& b) { + return a.score > b.score; + }); + + std::vector removed(detections.size(), false); + std::vector final_detections; + + for (size_t i = 0; i < detections.size(); ++i) { + if (removed[i]) continue; + final_detections.push_back(detections[i]); + for (size_t j = i + 1; j < detections.size(); ++j) { + if (removed[j]) continue; + if (detections[i].class_id != detections[j].class_id && + compute_iou(detections[i], detections[j]) > iou_threshold) { + removed[j] = true; + } + } + } + return final_detections; +} + +static float sigmoid(float x) { + return 1.0f / (1.0f + std::exp(-x)); +} + +static std::vector get_detections(float* output, std::tuple output_shape, + int stride, float conf_thresh, int num_classes, int reverse) { + std::vector detections; + + int grid_h = std::get<0>(output_shape); + int grid_w = std::get<1>(output_shape); + int total_cells = grid_h * grid_w; + int coords = 4 * 16; // DFL coords: 64 + + // reverse=0: standard YOLO [classes + box] + // reverse>0: YOLOWorld [box + classes] + int cls_offset = (reverse > 0) ? coords : 0; + int dfl_offset = (reverse > 0) ? 0 : num_classes; + + for (int i = 0; i < grid_h; ++i) { + for (int j = 0; j < grid_w; ++j) { + int idx = (i * grid_w + j) * (num_classes + coords); + + float max_score = -1.0f; + int class_id = -1; + for (int c = 0; c < num_classes; ++c) { + int cls_idx = idx + cls_offset + c; + float score = sigmoid(output[cls_idx]); + if (score > max_score) { + max_score = score; + class_id = c; + } + } + + if (max_score < conf_thresh) continue; + + float exp_vals[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + for (int k = 0; k < 4; ++k) { + int dfl_idx = idx + dfl_offset + k * 16; + float exp_logits[16]; + float sum_exp = 0.0f; + + float max_logit = output[dfl_idx]; + for (int t = 1; t < 16; ++t) { + if (output[dfl_idx + t] > max_logit) max_logit = output[dfl_idx + t]; + } + + for (int t = 0; t < 16; ++t) { + exp_logits[t] = std::exp(output[dfl_idx + t] - max_logit); + sum_exp += exp_logits[t]; + } + + for (int t = 0; t < 16; ++t) { + exp_logits[t] /= sum_exp; + exp_vals[k] += t * exp_logits[t]; + } + } + + float x1 = (j + 0.5f - exp_vals[0]) * stride; + float y1 = (i + 0.5f - exp_vals[1]) * stride; + float x2 = (j + 0.5f + exp_vals[2]) * stride; + float y2 = (i + 0.5f + exp_vals[3]) * stride; + + detections.push_back({x1, y1, x2, y2, max_score, class_id}); + } + } + return detections; +} + + +std::tuple> preprocess(cv::Mat img, std::tuple new_shape) { + cv::Mat img_rgb; + // Check if image is valid + if (img.empty()) { + LOGE("Preprocess received empty image"); + return {}; + } + + if (img.channels() == 4) + cv::cvtColor(img, img_rgb, cv::COLOR_RGBA2RGB); + else if (img.channels() == 3) + img_rgb = img.clone(); + + if (img.channels() == 3) { + cv::cvtColor(img, img_rgb, cv::COLOR_BGR2RGB); + } + + int orig_h = img.rows; + int orig_w = img.cols; + float scale = std::min(static_cast(std::get<0>(new_shape)) / orig_h, static_cast(std::get<1>(new_shape)) / orig_w); + int new_h = static_cast(round(orig_h * scale)); + int new_w = static_cast(round(orig_w * scale)); + + cv::Mat img_resized; + cv::resize(img_rgb, img_resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); + + int pad_h = std::get<0>(new_shape) - new_h; + int pad_w = std::get<1>(new_shape) - new_w; + int pad_left = static_cast(round(pad_w / 2. - 0.1f)); + int pad_right = static_cast(round(pad_w / 2. + 0.1f)); + int pad_top = static_cast(round(pad_h / 2. - 0.1f)); + int pad_bottom = static_cast(round(pad_h / 2. + 0.1f)); + + cv::Mat img_padded; + cv::copyMakeBorder(img_resized, img_padded, pad_top, pad_bottom, pad_left, pad_right, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114)); + + cv::Mat img_float; + img_padded.convertTo(img_float, CV_32F, 1.0 / 255.0); + + return std::make_tuple(img_float, scale, std::make_tuple(pad_left, pad_top)); +} + + + +std::vector postprocess(std::tuple, int> out0, + std::tuple, int> out1, + std::tuple, int> out2, + std::tuple> input_tuple, + float conf_thresh, float iou_threshold, int num_classes, int reverse) { + float scale = std::get<1>(input_tuple); + int pad_left = std::get<0>(std::get<2>(input_tuple)); + int pad_top = std::get<1>(std::get<2>(input_tuple)); + + std::vector detections; + + auto process_out = [&](auto& out) { + float* output = std::get<0>(out); + auto shape = std::get<1>(out); + int stride = std::get<2>(out); + std::vector dets = get_detections(output, shape, stride, conf_thresh, num_classes, reverse); + detections.insert(detections.end(), dets.begin(), dets.end()); + }; + + process_out(out0); + process_out(out1); + process_out(out2); + + std::vector detections_orig; + for (const auto& det : detections) { + float x1_orig = (det.x1 - pad_left) / scale; + float y1_orig = (det.y1 - pad_top) / scale; + float x2_orig = (det.x2 - pad_left) / scale; + float y2_orig = (det.y2 - pad_top) / scale; + detections_orig.push_back({x1_orig, y1_orig, x2_orig, y2_orig, det.score, det.class_id}); + } + + std::vector detections_nms = nms_by_class(detections_orig, iou_threshold); + return suppress_cross_class_iou_conflicts(detections_nms, 0.8f); +} + +cv::Mat draw_detections(cv::Mat image, const std::vector& detections, + const std::vector& classes, int seed_offset) { + int num_classes = classes.size(); + std::vector color_palette; + std::mt19937 rng(42 + seed_offset); + std::uniform_int_distribution color_dist(0, 255); + + for (int i = 0; i < num_classes; ++i) { + color_palette.emplace_back(color_dist(rng), color_dist(rng), color_dist(rng)); + } + + cv::Mat drawn_image = image.clone(); + + for (const auto& det : detections) { + int class_id = det.class_id; + if (class_id < 0 || class_id >= num_classes) continue; + + cv::Scalar color = color_palette[class_id]; + cv::rectangle(drawn_image, + cv::Point(static_cast(det.x1), static_cast(det.y1)), + cv::Point(static_cast(det.x2), static_cast(det.y2)), + color, 2); + + std::string label = classes[class_id] + ": " + cv::format("%.2f", det.score); + int baseline = 0; + cv::Size text_size = cv::getTextSize(label, cv::FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseline); + + int label_x = static_cast(det.x1); + int label_y = static_cast(det.y1) - 10; + if (label_y < text_size.height) label_y = static_cast(det.y1) + text_size.height + 10; + + cv::rectangle(drawn_image, + cv::Point(label_x, label_y - text_size.height - baseline), + cv::Point(label_x + text_size.width, label_y + baseline), + color, cv::FILLED); + + cv::putText(drawn_image, label, + cv::Point(label_x, label_y), + cv::FONT_HERSHEY_SIMPLEX, 0.5, + cv::Scalar(0, 0, 0), 1, cv::LINE_AA); + } + return drawn_image; +} diff --git a/examples/yoloworld/cpp/src/postprocess.h b/examples/yoloworld/cpp/src/postprocess.h index 40239c4..af89516 100755 --- a/examples/yoloworld/cpp/src/postprocess.h +++ b/examples/yoloworld/cpp/src/postprocess.h @@ -1,43 +1,43 @@ -/* - * Copyright (C) 2024–2025 Amlogic, Inc. 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. - */ - -#ifndef _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ -#define _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ - -#include -#include -#include -#include -#include - -// Detection result structure -struct Detection { - float x1, y1, x2, y2; // Bounding box coordinates - float score; // Confidence score - int class_id; // Predicted class ID -}; - - -std::vector postprocess(std::tuple, int> out0, - std::tuple, int> out1, - std::tuple, int> out2, - std::tuple> input_tuple, - float conf_thresh, float iou_threshold, int num_classes, int reverse); - -cv::Mat draw_detections(cv::Mat image, const std::vector& detections, - const std::vector& classes, int seed_offset = 0); - -#endif // _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ +/* + * Copyright (C) 2024–2025 Amlogic, Inc. 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. + */ + +#ifndef _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ +#define _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ + +#include +#include +#include +#include +#include + +// Detection result structure +struct Detection { + float x1, y1, x2, y2; // Bounding box coordinates + float score; // Confidence score + int class_id; // Predicted class ID +}; + + +std::vector postprocess(std::tuple, int> out0, + std::tuple, int> out1, + std::tuple, int> out2, + std::tuple> input_tuple, + float conf_thresh, float iou_threshold, int num_classes, int reverse); + +cv::Mat draw_detections(cv::Mat image, const std::vector& detections, + const std::vector& classes, int seed_offset = 0); + +#endif // _AMLNN_YOLO_WORLD_DEMO_POSTPROCESS_H_ diff --git a/examples/yoloworld/model/.gitkeep b/examples/yoloworld/model/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloworld/py/.gitkeep b/examples/yoloworld/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yoloworld/py/yoloworld.py b/examples/yoloworld/py/yoloworld.py index 7b81d5b..bd93c43 100755 --- a/examples/yoloworld/py/yoloworld.py +++ b/examples/yoloworld/py/yoloworld.py @@ -1,3 +1,19 @@ +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# + import numpy as np import os import glob diff --git a/examples/yolox/cpp/build-linux.sh b/examples/yolox/cpp/build-linux.sh index 6463fcd..3091777 100755 --- a/examples/yolox/cpp/build-linux.sh +++ b/examples/yolox/cpp/build-linux.sh @@ -1,4 +1,21 @@ +#TODO #!/bin/bash + +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# set -e usage() { diff --git a/examples/yolox/cpp/src/main.cpp b/examples/yolox/cpp/src/main.cpp index c1c68f9..5abbf1c 100755 --- a/examples/yolox/cpp/src/main.cpp +++ b/examples/yolox/cpp/src/main.cpp @@ -85,7 +85,7 @@ int main(int argc, char** argv) { int pad_top = std::get<1>(pad); // 4. Run Network - std::tuple> input_tuple = + std::tuple> input_tuple = std::make_tuple(img, scale, pad); auto start_time = std::chrono::high_resolution_clock::now(); diff --git a/examples/yolox/cpp/src/postprocess.cpp b/examples/yolox/cpp/src/postprocess.cpp index 6880641..d9b7714 100755 --- a/examples/yolox/cpp/src/postprocess.cpp +++ b/examples/yolox/cpp/src/postprocess.cpp @@ -24,7 +24,7 @@ std::tuple> preproc(const cv::Mat& img, std // 2. BGR to RGB conversion // 3. Normalize to 0-1 (divide by 255.0) // Note: NNSDK's model_loader expects HWC format, so return HWC instead of CHW - + int input_height = std::get<0>(input_size); int input_width = std::get<1>(input_size); @@ -33,7 +33,7 @@ std::tuple> preproc(const cv::Mat& img, std static_cast(input_width) / img.cols); int new_w = static_cast(std::round(img.cols * scale)); int new_h = static_cast(std::round(img.rows * scale)); - + // resize cv::Mat resized_img; if (img.size() != cv::Size(new_w, new_h)) { @@ -41,7 +41,7 @@ std::tuple> preproc(const cv::Mat& img, std } else { resized_img = img.clone(); } - + // padding float pad_w = (input_width - new_w) / 2.0f; float pad_h = (input_height - new_h) / 2.0f; @@ -49,15 +49,15 @@ std::tuple> preproc(const cv::Mat& img, std int bottom = static_cast(std::round(pad_h + 0.1f)); int left = static_cast(std::round(pad_w - 0.1f)); int right = static_cast(std::round(pad_w + 0.1f)); - + cv::Mat padded_img; - cv::copyMakeBorder(resized_img, padded_img, top, bottom, left, right, + cv::copyMakeBorder(resized_img, padded_img, top, bottom, left, right, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114)); - + // BGR to RGB conversion cv::Mat rgb_img; cv::cvtColor(padded_img, rgb_img, cv::COLOR_BGR2RGB); - + // Normalize to 0-1 range (divide by 255.0) cv::Mat normalized_img; rgb_img.convertTo(normalized_img, CV_32F, 1.0 / 255.0); @@ -69,7 +69,7 @@ std::tuple> preproc(const cv::Mat& img, std cv::Scalar std(0.229f, 0.224f, 0.225f); normalized_img -= mean; cv::divide(normalized_img, std, normalized_img); - + // Return HWC format, ImageNet normalized float32 image (RGB format) // Also return scale and padding (left, top) for coordinate mapping return std::make_tuple(normalized_img, scale, std::make_tuple(left, top)); @@ -82,35 +82,35 @@ static float sigmoid(float x) { void demo_postprocess(float* outputs, int num_boxes, std::tuple img_size, bool p6) { int img_height = std::get<0>(img_size); int img_width = std::get<1>(img_size); - + std::vector strides; if (!p6) { strides = {8, 16, 32}; } else { strides = {8, 16, 32, 64}; } - + // Calculate grid count for each stride std::vector hsizes, wsizes; for (int stride : strides) { hsizes.push_back(img_height / stride); wsizes.push_back(img_width / stride); } - + // Build grids and expanded_strides std::vector> grids_list; std::vector> strides_list; - + int total_boxes = 0; for (size_t i = 0; i < strides.size(); ++i) { int hsize = hsizes[i]; int wsize = wsizes[i]; int stride = strides[i]; int grid_size = hsize * wsize; - + std::vector grid(grid_size * 2); std::vector expanded_stride(grid_size); - + for (int h = 0; h < hsize; ++h) { for (int w = 0; w < wsize; ++w) { int idx = h * wsize + w; @@ -119,16 +119,16 @@ void demo_postprocess(float* outputs, int num_boxes, std::tuple img_si expanded_stride[idx] = static_cast(stride); } } - + grids_list.push_back(grid); strides_list.push_back(expanded_stride); total_boxes += grid_size; } - + // Merge all grids and strides std::vector all_grids(total_boxes * 2); std::vector all_strides(total_boxes); - + int offset = 0; for (size_t i = 0; i < grids_list.size(); ++i) { int grid_size = grids_list[i].size() / 2; @@ -139,15 +139,15 @@ void demo_postprocess(float* outputs, int num_boxes, std::tuple img_si } offset += grid_size; } - + // Apply grid and stride decoding for (int i = 0; i < num_boxes && i < total_boxes; ++i) { float* box = outputs + i * 85; - + // outputs[..., :2] = (outputs[..., :2] + grids) * expanded_strides box[0] = (box[0] + all_grids[i * 2]) * all_strides[i]; box[1] = (box[1] + all_grids[i * 2 + 1]) * all_strides[i]; - + // outputs[..., 2:4] = np.exp(outputs[..., 2:4]) * expanded_strides box[2] = std::exp(box[2]) * all_strides[i]; box[3] = std::exp(box[3]) * all_strides[i]; @@ -156,70 +156,70 @@ void demo_postprocess(float* outputs, int num_boxes, std::tuple img_si std::vector nms(const std::vector& boxes, const std::vector& scores, float nms_thr) { if (boxes.empty()) return {}; - + // Create indices and sort std::vector indices(boxes.size()); std::iota(indices.begin(), indices.end(), 0); std::sort(indices.begin(), indices.end(), [&scores](int a, int b) { return scores[a] > scores[b]; }); - + std::vector keep; std::vector suppressed(boxes.size(), false); - + for (size_t i = 0; i < indices.size(); ++i) { int idx = indices[i]; if (suppressed[idx]) continue; - + keep.push_back(idx); - + float x1_i = boxes[idx].x; float y1_i = boxes[idx].y; float x2_i = boxes[idx].x + boxes[idx].width; float y2_i = boxes[idx].y + boxes[idx].height; float area_i = boxes[idx].width * boxes[idx].height; - + for (size_t j = i + 1; j < indices.size(); ++j) { int idx_j = indices[j]; if (suppressed[idx_j]) continue; - + float x1_j = boxes[idx_j].x; float y1_j = boxes[idx_j].y; float x2_j = boxes[idx_j].x + boxes[idx_j].width; float y2_j = boxes[idx_j].y + boxes[idx_j].height; - + float xx1 = std::max(x1_i, x1_j); float yy1 = std::max(y1_i, y1_j); float xx2 = std::min(x2_i, x2_j); float yy2 = std::min(y2_i, y2_j); - + float w = std::max(0.0f, xx2 - xx1); float h = std::max(0.0f, yy2 - yy1); float inter = w * h; - + float area_j = boxes[idx_j].width * boxes[idx_j].height; float ovr = inter / (area_i + area_j - inter); - + if (ovr > nms_thr) { suppressed[idx_j] = true; } } } - + return keep; } -std::vector multiclass_nms(const std::vector& boxes, +std::vector multiclass_nms(const std::vector& boxes, const std::vector>& scores, int num_classes, - float nms_thr, + float nms_thr, float score_thr) { if (boxes.empty() || scores.empty()) return {}; - + // Find max class score and class ID for each box std::vector cls_scores(boxes.size()); std::vector cls_inds(boxes.size()); - + for (size_t i = 0; i < boxes.size(); ++i) { float max_score = -1.0f; int max_idx = -1; @@ -232,13 +232,13 @@ std::vector multiclass_nms(const std::vector& boxes, cls_scores[i] = max_score; cls_inds[i] = max_idx; } - + // Filter low-score boxes std::vector valid_boxes; std::vector valid_scores; std::vector valid_cls_inds; std::vector valid_indices; - + for (size_t i = 0; i < boxes.size(); ++i) { if (cls_scores[i] > score_thr) { valid_boxes.push_back(boxes[i]); @@ -247,12 +247,12 @@ std::vector multiclass_nms(const std::vector& boxes, valid_indices.push_back(i); } } - + if (valid_boxes.empty()) return {}; - + // Execute NMS std::vector keep = nms(valid_boxes, valid_scores, nms_thr); - + // Build results std::vector dets; for (int idx : keep) { @@ -265,23 +265,23 @@ std::vector multiclass_nms(const std::vector& boxes, det.class_id = valid_cls_inds[idx]; dets.push_back(det); } - + return dets; } -cv::Mat vis(const cv::Mat& img, +cv::Mat vis(const cv::Mat& img, const std::vector& detections, float conf_thresh, const std::vector& class_names) { cv::Mat result = img.clone(); - + // Adjust font size based on image size int img_height = img.rows; int img_width = img.cols; - float font_scale = std::max(0.6f, std::min(1.2f, + float font_scale = std::max(0.6f, std::min(1.2f, static_cast(std::sqrt(img_height * img_height + img_width * img_width)) * 0.0015f)); int thickness = std::max(2, static_cast(font_scale * 2.5f)); - + // YOLOX color palette static const std::vector colors = { cv::Scalar(0, 114, 189), cv::Scalar(217, 83, 25), cv::Scalar(237, 177, 32), @@ -314,38 +314,38 @@ cv::Mat vis(const cv::Mat& img, for (const auto& det : detections) { if (det.score < conf_thresh) continue; if (det.class_id < 0 || det.class_id >= static_cast(class_names.size())) continue; - + int x0 = static_cast(det.x1); int y0 = static_cast(det.y1); int x1 = static_cast(det.x2); int y1 = static_cast(det.y2); - + cv::Scalar color = colors[det.class_id % colors.size()]; - + // Draw bounding box cv::rectangle(result, cv::Point(x0, y0), cv::Point(x1, y1), color, thickness); - + // Prepare text std::string text = class_names[det.class_id] + ":" + cv::format("%.1f%%", det.score * 100); - + // Calculate text size int baseline = 0; cv::Size txt_size = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, font_scale, thickness, &baseline); - + // Draw text background cv::Scalar txt_bk_color = color * 0.7; cv::rectangle(result, cv::Point(x0, y0 + 1), cv::Point(x0 + txt_size.width + 1, y0 + static_cast(1.5 * txt_size.height)), txt_bk_color, -1); - + // Draw text cv::Scalar txt_color = (cv::mean(color)[0] > 0.5) ? cv::Scalar(0, 0, 0) : cv::Scalar(255, 255, 255); cv::putText(result, text, cv::Point(x0, y0 + txt_size.height), cv::FONT_HERSHEY_SIMPLEX, font_scale, txt_color, thickness); } - + return result; } diff --git a/examples/yolox/cpp/src/postprocess.h b/examples/yolox/cpp/src/postprocess.h index 90c98f7..43e2086 100755 --- a/examples/yolox/cpp/src/postprocess.h +++ b/examples/yolox/cpp/src/postprocess.h @@ -94,16 +94,16 @@ std::vector nms(const std::vector& boxes, const std::vector multiclass_nms(const std::vector& boxes, +std::vector multiclass_nms(const std::vector& boxes, const std::vector>& scores, int num_classes, - float nms_thr, + float nms_thr, float score_thr); /** * Visualize detection results (consistent with Python version, supports adaptive font size) */ -cv::Mat vis(const cv::Mat& img, +cv::Mat vis(const cv::Mat& img, const std::vector& detections, float conf_thresh, const std::vector& class_names); diff --git a/examples/yolox/py/.gitkeep b/examples/yolox/py/.gitkeep old mode 100644 new mode 100755 diff --git a/examples/yolox/py/yolox.py b/examples/yolox/py/yolox.py index cc0b4e5..f629829 100755 --- a/examples/yolox/py/yolox.py +++ b/examples/yolox/py/yolox.py @@ -1,4 +1,20 @@ # -*- coding: utf-8 -*- + +# +# Copyright (C) 2026 Amlogic, Inc. 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. +# import numpy as np import os import glob @@ -369,7 +385,7 @@ def main(): # Initialize AMLNNLite amlnn = AMLNNLite() amlnn.config( - model_path=args.model_path, # Model file path, Support ADLD and quantized TFlite models + model_path=args.model_path, # Model file path, Support ADLA and quantized TFlite models run_cycles=args.run_cycles ) amlnn.init() diff --git a/resource/.gitkeep b/resource/.gitkeep old mode 100644 new mode 100755