├── .clang-format ├── .clangd ├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── linux-x64.yml │ ├── mac-x64.yml │ └── windows-x64.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build ├── addr2line.py ├── linux-x64-scan-build.sh ├── linux-x64.sh └── vs2022-x64.cmd ├── cmake ├── PVS-Studio.cmake ├── asan.cmake ├── deps.cmake ├── lldb-debug.cmake ├── ninja_colorful_output.cmake ├── output_dir.cmake ├── overlook.cmake ├── summary.cmake └── tsan.cmake ├── images ├── input │ ├── background.png │ └── frontground.png └── snapshots │ ├── snapshot_2022-05-29.png │ └── snapshot_2022-06-12.png ├── requirements.txt ├── sledpkg.json ├── sledpkg.py ├── sledpkg_run.py ├── src ├── kantu │ ├── CMakeLists.txt │ ├── app.cpp │ ├── app_design.hpp │ ├── compare.cpp │ ├── compare.hpp │ ├── image.hpp │ ├── image_inspect.h │ ├── image_io.cpp │ ├── image_io.hpp │ ├── image_render.cpp │ ├── image_render.hpp │ ├── image_viewer.cpp │ ├── log.hpp │ ├── string.cpp │ ├── string.hpp │ ├── transform_format.cpp │ └── transform_format.hpp └── kantu_tests │ ├── CMakeLists.txt │ └── test_image_compare.cpp └── xkcd-script.ttf /.clang-format: -------------------------------------------------------------------------------- 1 | # find src/ tools/ tests/ examples/ benchmark/ -type f -name '*.c' -o -name '*.cpp' -o -name '*.h' | xargs -i clang-format -i {} 2 | 3 | # need clang-format >= 10.0 4 | 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveAssignments: false 8 | # AlignConsecutiveBitFields: true 9 | AlignConsecutiveDeclarations: false 10 | AlignConsecutiveMacros: true 11 | AlignEscapedNewlines: Left 12 | # AlignOperands: AlignAfterOperator 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: true 15 | AllowAllConstructorInitializersOnNextLine: true 16 | AllowAllParametersOfDeclarationOnNextLine: true 17 | AllowShortBlocksOnASingleLine: Always 18 | AllowShortCaseLabelsOnASingleLine: true 19 | # AllowShortEnumsOnASingleLine: true 20 | AllowShortFunctionsOnASingleLine: None 21 | AllowShortIfStatementsOnASingleLine: WithoutElse 22 | AllowShortLambdasOnASingleLine: All 23 | AllowShortLoopsOnASingleLine: true 24 | AlwaysBreakAfterReturnType: None 25 | AlwaysBreakBeforeMultilineStrings: false 26 | AlwaysBreakTemplateDeclarations: Yes 27 | BinPackArguments: true 28 | BinPackParameters: true 29 | BraceWrapping: 30 | AfterCaseLabel: true 31 | AfterClass: true 32 | AfterControlStatement: Always 33 | AfterEnum: true 34 | AfterFunction: true 35 | AfterNamespace: false 36 | AfterObjCDeclaration: false 37 | AfterStruct: true 38 | AfterUnion: true 39 | AfterExternBlock: false 40 | BeforeCatch: true 41 | BeforeElse: true 42 | # BeforeLambdaBody: false 43 | # BeforeWhile: false 44 | IndentBraces: false 45 | SplitEmptyFunction: true 46 | SplitEmptyRecord: true 47 | SplitEmptyNamespace: false 48 | BreakAfterJavaFieldAnnotations: true 49 | BreakBeforeBinaryOperators: All 50 | BreakBeforeBraces: Custom 51 | BreakBeforeTernaryOperators: true 52 | BreakConstructorInitializers: BeforeColon 53 | BreakInheritanceList: BeforeColon 54 | BreakStringLiterals: false 55 | ColumnLimit: 0 56 | # CommentPragmas: 57 | CompactNamespaces: false 58 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 59 | ConstructorInitializerIndentWidth: 4 60 | ContinuationIndentWidth: 4 61 | Cpp11BracedListStyle: true 62 | DeriveLineEnding: false 63 | DerivePointerAlignment: false 64 | # DisableFormat: 65 | # ExperimentalAutoDetectBinPacking: 66 | FixNamespaceComments: true 67 | # ForEachMacros: 68 | IncludeBlocks: Regroup 69 | # IncludeCategories: 70 | # IncludeIsMainRegex: 71 | # IncludeIsMainSourceRegex: 72 | # IndentCaseBlocks: false 73 | IndentCaseLabels: false 74 | # IndentExternBlock: NoIndent 75 | IndentGotoLabels: false 76 | IndentPPDirectives: None 77 | IndentWidth: 4 78 | # IndentWrappedFunctionNames: 4 79 | # InsertTrailingCommas: None 80 | # JavaImportGroups: 81 | # JavaScriptQuotes 82 | # JavaScriptWrapImports: 83 | KeepEmptyLinesAtTheStartOfBlocks: false 84 | Language: Cpp 85 | # MacroBlockBegin: 86 | # MacroBlockEnd: 87 | MaxEmptyLinesToKeep: 1 88 | NamespaceIndentation: None 89 | # NamespaceMacros: 90 | # ObjCBinPackProtocolList: 91 | # ObjCBlockIndentWidth: 92 | # ObjCBreakBeforeNestedBlockParam: 93 | # ObjCSpaceAfterProperty: 94 | # ObjCSpaceBeforeProtocolList: 95 | # PenaltyBreakAssignment: 96 | # PenaltyBreakBeforeFirstCallParameter: 97 | # PenaltyBreakComment: 98 | # PenaltyBreakFirstLessLess: 99 | # PenaltyBreakString: 100 | # PenaltyBreakTemplateDeclaration: 101 | # PenaltyExcessCharacter: 102 | # PenaltyReturnTypeOnItsOwnLine: 103 | PointerAlignment: Left 104 | # RawStringFormats: 105 | ReflowComments: false 106 | SortIncludes: false 107 | SortUsingDeclarations: true 108 | SpaceAfterCStyleCast: false 109 | SpaceAfterLogicalNot: false 110 | SpaceAfterTemplateKeyword: false 111 | SpaceBeforeAssignmentOperators: true 112 | SpaceBeforeCpp11BracedList: false 113 | SpaceBeforeCtorInitializerColon: true 114 | SpaceBeforeInheritanceColon: true 115 | SpaceBeforeParens: ControlStatements 116 | SpaceBeforeRangeBasedForLoopColon: true 117 | SpaceBeforeSquareBrackets: false 118 | SpaceInEmptyBlock: false 119 | SpaceInEmptyParentheses: false 120 | SpacesBeforeTrailingComments: 1 121 | SpacesInAngles: false 122 | SpacesInCStyleCastParentheses: false 123 | SpacesInConditionalStatement: false 124 | SpacesInContainerLiterals: false 125 | SpacesInParentheses: false 126 | SpacesInSquareBrackets: false 127 | Standard: c++20 128 | #StatementMacros: 129 | TabWidth: 4 130 | # TypenameMacros: 131 | UseCRLF: false 132 | UseTab: Never 133 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: [-D=STR_IMPLEMENTATION] -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | ##end_of_line = lf 7 | charset = utf-8 8 | ##trim_trailing_whitespace = true 9 | ##insert_final_newline = true 10 | indent_style = space 11 | indent_size = 4 12 | 13 | [{CMakeLists.*,*.cmake}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [Makefile] 18 | indent_style = tab 19 | 20 | [*.{bat,cmd,cmd.*}] 21 | end_of_line = crlf 22 | indent_style = space 23 | indent_size = 2 24 | 25 | [*.{ps1,ps1.*}] 26 | end_of_line = crlf 27 | indent_style = space 28 | indent_size = 4 29 | 30 | [*.{md,markdown}] 31 | indent_size = 4 32 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /.github/workflows/linux-x64.yml: -------------------------------------------------------------------------------- 1 | name: linux-x64 2 | on: [push, pull_request] 3 | 4 | env: 5 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 6 | BUILD_TYPE: Release 7 | BUILD_DIR: linux-x64 8 | GITHUB_ACTION: true 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.image }} 13 | strategy: 14 | matrix: 15 | image: [ 'ubuntu-latest' ] 16 | 17 | steps: 18 | - name: cancel-previous-runs 19 | uses: styfle/cancel-workflow-action@0.11.0 20 | with: 21 | access_token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Clone repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Ninja 27 | id: ninja 28 | uses: turtlesec-no/get-ninja@main 29 | 30 | - name: Apt packages 31 | shell: bash 32 | run: | 33 | sudo apt-get install libxrandr-dev -y 34 | sudo apt-get install xorg-dev libglu1-mesa-dev -y 35 | 36 | - name: cache-deps 37 | id: cache-deps 38 | uses: actions/cache@v3.3.1 39 | with: 40 | path: deps 41 | key: deps-linux-x64-v2 42 | 43 | - name: checkout-and-build-deps 44 | if: steps.cache-deps.outputs.cache-hit != 'true' 45 | shell: bash 46 | run: | 47 | pip3 install -r requirements.txt 48 | python3 sledpkg_run.py 49 | 50 | - name: Configure the project 51 | working-directory: ${{github.workspace}}/build 52 | run: cmake -S .. -B ${{env.BUILD_DIR}} -G Ninja 53 | 54 | - name: Build 55 | working-directory: ${{github.workspace}}/build 56 | run: cmake --build ${{env.BUILD_DIR}} 57 | 58 | - name: Test 59 | working-directory: ${{github.workspace}}/build/${{env.BUILD_DIR}} 60 | run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure 61 | 62 | - name: Install 63 | working-directory: ${{github.workspace}}/build 64 | run: cmake --install ${{env.BUILD_DIR}} 65 | 66 | - name: Upload 67 | uses: actions/upload-artifact@v3 68 | with: 69 | name: KantuCompareApp-${{runner.os}}-x64 70 | path: ${{github.workspace}}/build/${{env.BUILD_DIR}}/install 71 | -------------------------------------------------------------------------------- /.github/workflows/mac-x64.yml: -------------------------------------------------------------------------------- 1 | name: mac-x64 2 | on: [push, pull_request] 3 | 4 | env: 5 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 6 | BUILD_TYPE: Release 7 | BUILD_DIR: mac-x64 8 | GITHUB_ACTION: true 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.image }} 13 | strategy: 14 | matrix: 15 | image: [ 'macos-latest' ] 16 | 17 | steps: 18 | - name: cancel-previous-runs 19 | uses: styfle/cancel-workflow-action@0.11.0 20 | with: 21 | access_token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Clone repository 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Ninja 27 | id: ninja 28 | uses: turtlesec-no/get-ninja@main 29 | 30 | - name: cache-deps 31 | id: cache-deps 32 | uses: actions/cache@v3.3.1 33 | with: 34 | path: deps 35 | key: deps-mac-x64-v2 36 | 37 | - name: checkout-and-build-deps 38 | if: steps.cache-deps.outputs.cache-hit != 'true' 39 | shell: bash 40 | run: | 41 | pip3 install -r requirements.txt 42 | python3 sledpkg_run.py 43 | 44 | - name: Configure the project 45 | working-directory: ${{github.workspace}}/build 46 | run: cmake -S .. -B ${{env.BUILD_DIR}} -G Ninja 47 | 48 | - name: Build 49 | working-directory: ${{github.workspace}}/build 50 | run: cmake --build ${{env.BUILD_DIR}} 51 | 52 | - name: Test 53 | working-directory: ${{github.workspace}}/build/${{env.BUILD_DIR}} 54 | run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure 55 | 56 | - name: Install 57 | working-directory: ${{github.workspace}}/build 58 | run: cmake --install ${{env.BUILD_DIR}} 59 | 60 | - name: Upload 61 | uses: actions/upload-artifact@v3 62 | with: 63 | name: KantuCompareApp-${{runner.os}}-x64 64 | path: ${{github.workspace}}/build/${{env.BUILD_DIR}}/install -------------------------------------------------------------------------------- /.github/workflows/windows-x64.yml: -------------------------------------------------------------------------------- 1 | name: windows-x64 2 | on: [push, pull_request] 3 | 4 | env: 5 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 6 | BUILD_TYPE: Release 7 | BUILD_DIR: windows-x64 8 | GITHUB_ACTION: true 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.image }} 13 | strategy: 14 | matrix: 15 | image: [ 'windows-latest' ] 16 | 17 | steps: 18 | - name: cancel-previous-runs 19 | uses: styfle/cancel-workflow-action@0.11.0 20 | with: 21 | access_token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | - name: Clone repository 24 | uses: actions/checkout@v4 25 | 26 | - name: cache-deps 27 | id: cache-deps 28 | uses: actions/cache@v3.3.1 29 | with: 30 | path: deps 31 | key: deps-windows-x64-v2 32 | 33 | - name: checkout-and-build-deps 34 | if: steps.cache-deps.outputs.cache-hit != 'true' 35 | shell: bash 36 | run: | 37 | pip3 install -r requirements.txt 38 | python3 sledpkg_run.py 39 | 40 | - name: Configure the project 41 | working-directory: ${{github.workspace}}/build 42 | run: cmake -S .. -B ${{env.BUILD_DIR}} 43 | 44 | - name: Build 45 | working-directory: ${{github.workspace}}/build 46 | run: cmake --build ${{env.BUILD_DIR}} --config ${{env.BUILD_TYPE}} -j2 47 | 48 | - name: Test 49 | working-directory: ${{github.workspace}}/build/${{env.BUILD_DIR}} 50 | run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure 51 | 52 | - name: Install 53 | working-directory: ${{github.workspace}}/build 54 | run: cmake --install ${{env.BUILD_DIR}} --config ${{env.BUILD_TYPE}} 55 | 56 | - name: Upload 57 | uses: actions/upload-artifact@v3 58 | with: 59 | name: KantuCompareApp-${{runner.os}}-x64 60 | path: ${{github.workspace}}/build/${{env.BUILD_DIR}}/install 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # All sub directories under build 2 | **/build/*/ 3 | 4 | # IDE generated folder 5 | .vscode/settings.json 6 | .idea/ 7 | cmake-build-debug/ 8 | 9 | # Xmake cache 10 | .xmake/ 11 | 12 | # clangd cache 13 | **/.cache/ 14 | 15 | # c/c++ object files 16 | *.o 17 | *.out 18 | 19 | # debug symbol file 20 | *.out.dSYM 21 | *.pdb 22 | 23 | # python Byte-compiled / optimized / DLL files 24 | __pycache__/ 25 | *.py[cod] 26 | *$py.class 27 | 28 | # images, models, etc 29 | assets/ 30 | 31 | # tex 32 | *.aux 33 | *.log 34 | *.synctex.gz 35 | *.toc 36 | 37 | # log files 38 | build/log.txt 39 | 40 | # gdb 41 | .gdb_history 42 | 43 | # MacOS Cache 44 | .DS_Store 45 | 46 | # Dear ImGui config file 47 | **/imgui.ini 48 | 49 | # build output 50 | output/** 51 | 52 | # 3rd-party source, build and install files 53 | deps/* -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "local-gdb", // 用 gdb 调试本地程序。可以顺畅使用。若安装了 lldb, 会用 lldb。 在调试控制台 help 验证。 7 | // "type": "cppdbg", // cpptools plugin 8 | "type": "lldb", // clangd + codelldb plugins 9 | "request": "launch", 10 | "program": "${workspaceFolder}/build/linux-x64/ImageCompare", 11 | "args": [], 12 | "stopAtEntry": false, 13 | "cwd": "${workspaceFolder}", 14 | // "cwd": "${workspaceFolder}/build/linux-x64", // 若读取文件使用了相对路径,为保持一致性,可修改为此行 15 | "environment": [], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "为 gdb 启用整齐打印", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ], 25 | "preLaunchTask": "linux-x64-build", // 依赖于 tasks.json 中名为 cmake build 的步骤, 用于避免修改代码后忘记编译导致调试失败。 26 | }, 27 | { 28 | // 调试远程 android arm64 程序. https://github.com/vadimcn/vscode-lldb/blob/master/MANUAL.md#remote-debugging 29 | // NOTE: 若断点无法停留, 请检查是否开启了调试符号编译选项 -g . 可参考 04_global_configuration/debug_symbol_example 30 | "name": "remote-lldb-arm64", 31 | "type": "lldb", 32 | "request": "launch", 33 | "program": "${workspaceFolder}/build/android-arm64/testbed", 34 | "initCommands": [ 35 | "platform select remote-android", 36 | "platform connect connect://4af156d2:10086", 37 | "settings set target.inherit-env false", // 设备上跑的testbed,不要继承 host 的环境变量 38 | ], 39 | "preLaunchTask": "android-arm64-build", // 依赖于 tasks.json 中名为 cmake build 的步骤, 用于避免修改代码后忘记编译导致调试失败。 40 | }, 41 | { 42 | "name": "remote-lldb-arm32", // 调试远程 android arm32 程序 43 | "type": "lldb", 44 | "request": "launch", 45 | "program": "${workspaceFolder}/build/android-arm64/testbed", 46 | "initCommands": [ 47 | "platform select remote-android", 48 | "platform connect connect://4af156d2:10086", 49 | "settings set target.inherit-env false", // 设备上跑的testbed,不要继承 host 的环境变量 50 | ], 51 | "preLaunchTask": "android-arm32-build", // 依赖于 tasks.json 中名为 android-arm32-build 的步骤, 用于避免修改代码后忘记编译导致调试失败。 52 | }, 53 | { 54 | "name": "local-lldb", 55 | // "type": "cppdbg", // cpptools plugin 56 | "type": "lldb", // clangd + codelldb plugins 57 | "request": "launch", 58 | "program": "${workspaceFolder}/build/linux-x64/KantuCompareApp", 59 | "args": [], 60 | "stopAtEntry": false, 61 | "cwd": "${workspaceFolder}", 62 | "environment": [], 63 | "externalConsole": false, 64 | "MIMode": "lldb", 65 | "setupCommands": [ 66 | { 67 | "description": "为 lldb 启用整齐打印", 68 | "text": "-enable-pretty-printing", 69 | "ignoreFailures": true 70 | } 71 | ], 72 | "preLaunchTask": "linux-x64-build", 73 | "miDebuggerPath": "/home/zz/soft/lldb-mi/bin/lldb-mi", 74 | //"miDebuggerPath": "/usr/bin/lldb-mi", 75 | }, 76 | 77 | { 78 | "name": "debug-single-file", // 用于(本地)调试单个可执行文件。适合(Linux/MacOSX)快速验证, 或无源码情况下的调试 79 | // "type": "cppdbg", // cpptools plugin 80 | "type": "lldb", // clangd + codelldb plugins 81 | "request": "launch", 82 | "program": "${workspaceFolder}/a.out", // 指定可执行文件路径 83 | "args": [], 84 | "stopAtEntry": false, 85 | "stopAtConnect": true, 86 | "cwd": "${workspaceFolder}", 87 | "environment": [], 88 | "externalConsole": false, 89 | "MIMode": "gdb", 90 | "setupCommands": [ 91 | { 92 | "description": "为 gdb 启用整齐打印", 93 | "text": "-enable-pretty-printing", 94 | "ignoreFailures": true 95 | } 96 | ], 97 | "preRunCommands": [ // 指定在 lldb 执行 run 之前的 lldb 命令, 例如在 main 设置断点 98 | "b main" 99 | ], 100 | "preLaunchTask": "compile single C++", // 若只有 a.out 而无源码, 则可以注释掉此步骤 101 | }, 102 | { 103 | "name": "cl.exe build and debug active file", // 编译并调试单个文件(当前打开) 104 | "type": "cppvsdbg", 105 | "request": "launch", 106 | "program": "${fileDirname}\\${fileBasenameNoExtension}.exe", 107 | "args": [], 108 | "stopAtEntry": false, 109 | "cwd": "${workspaceFolder}", 110 | "environment": [], 111 | "externalConsole": false, 112 | "preLaunchTask": "cl.exe build active file" 113 | }, 114 | { 115 | "name": "vs2022-x64", // 编译并调试基于 CMake 的一整个工程 116 | "type": "cppvsdbg", // cppdbg 无效 117 | "request": "launch", 118 | "program": "${workspaceRoot}/build/vs2022-x64/Debug/testbed.exe", 119 | "args": [], 120 | "stopAtEntry": false, 121 | "cwd": "${workspaceFolder}", 122 | //"cwd": "${workspaceFolder}/build/vs2022-x64", 123 | "environment": [], 124 | "externalConsole": false, 125 | "miDebuggerPath": "/usr/bin/gdb", 126 | "preLaunchTask": "vs2022-x64 build with cmake" 127 | }, 128 | ] 129 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "windows": { 4 | "options": { 5 | "shell": { 6 | "executable": "cmd.exe", 7 | "args": [ 8 | "/C", 9 | // The path to VsDevCmd.bat depends on the version of Visual Studio you have installed. 10 | //"\"C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/Tools/VsDevCmd.bat\"", 11 | "\"C:/Program Files/Microsoft Visual Studio/2022/Community/Common7/Tools/VsDevCmd.bat\"", 12 | "&&" 13 | ] 14 | } 15 | } 16 | }, 17 | "tasks": [ 18 | { 19 | "label": "linux-x64-build", // 构建整个工程 20 | "type": "process", 21 | "command": "./linux-x64.sh", 22 | "group": "build", 23 | "options": { 24 | "cwd": "${workspaceFolder}/build" 25 | }, 26 | "problemMatcher": [ 27 | "$gcc" 28 | ] 29 | }, 30 | { 31 | "label": "android-arm64-build", // 构建整个工程 32 | "type": "process", 33 | "command": "./android-arm64-build.sh", 34 | "group": "build", 35 | "options": { 36 | "cwd": "${workspaceFolder}/build" 37 | }, 38 | "problemMatcher": [ 39 | "$gcc" 40 | ] 41 | }, 42 | { 43 | "label": "android-arm32-build", // 构建整个工程 44 | "type": "process", 45 | "command": "./android-arm32-build.sh", 46 | "group": "build", 47 | "options": { 48 | "cwd": "${workspaceFolder}/build" 49 | }, 50 | "problemMatcher": [ 51 | "$gcc" 52 | ] 53 | }, 54 | { 55 | "label": "cmake clean", // 清理cmake缓存 56 | "type": "process", 57 | "command": "rm", 58 | "args": [ 59 | "CMakeCache.txt" 60 | ], 61 | "group": "build", 62 | "options": { 63 | "cwd": "${workspaceFolder}/build/linux-x64" 64 | }, 65 | "problemMatcher": [ 66 | "$gcc" 67 | ] 68 | }, 69 | { 70 | "label": "make", // 执行make,编译出二进制 71 | "type": "process", 72 | "command": "make", 73 | "args": [ 74 | "-j4" 75 | ], 76 | "group": "build", 77 | "options": { 78 | "cwd": "${workspaceFolder}/build/linux-x64" 79 | }, 80 | "problemMatcher": [ 81 | "$gcc" 82 | ] 83 | }, 84 | { 85 | "label": "make clean", // 执行make,编译出二进制 86 | "type": "process", 87 | "command": "make clean", 88 | "args": [ 89 | "-j4" 90 | ], 91 | "group": "build", 92 | "options": { 93 | "cwd": "${workspaceFolder}/build/linux-x64" 94 | }, 95 | "problemMatcher": [ 96 | "$gcc" 97 | ] 98 | }, 99 | { 100 | "label": "run", // 运行程序;调试程序在launch.json里配置 101 | "type": "shell", 102 | "command": "./testbed", 103 | "options": { 104 | "cwd": "${workspaceFolder}/build/linux-x64" 105 | }, 106 | "group": { 107 | "kind": "test", 108 | "isDefault": true 109 | }, 110 | "problemMatcher": [ 111 | "$gcc" 112 | ] 113 | }, 114 | { 115 | "label": "compile single C", 116 | "type": "shell", 117 | "command": "gcc a.c -g", 118 | "options": { 119 | "cwd": "${workspaceFolder}" 120 | } 121 | }, 122 | { 123 | "label": "compile single C++", 124 | "type": "shell", 125 | "command": "g++ a.c -g", 126 | "options": { 127 | "cwd": "${workspaceFolder}" 128 | } 129 | }, 130 | { 131 | "type": "shell", 132 | "label": "cl.exe build active file", 133 | "command": "cl.exe", 134 | "args": [ 135 | "/Zi", 136 | "/EHsc", 137 | "/Fe:", 138 | "${fileDirname}\\${fileBasenameNoExtension}.exe", 139 | "${file}" 140 | ], 141 | "problemMatcher": ["$msCompile"], 142 | "group": { 143 | "kind": "build", 144 | "isDefault": true 145 | } 146 | }, 147 | { 148 | "label": "vs2022-x64 build with cmake", 149 | "type": "process", 150 | "command": "./vs2022-x64.cmd", 151 | "group": { 152 | "kind": "build", 153 | }, 154 | "options": { 155 | "cwd": "${workspaceFolder}/build" 156 | }, 157 | "problemMatcher": [ 158 | "$msCompile" 159 | ] 160 | }, 161 | ] 162 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | 3 | if(NOT DEFINED CMAKE_INSTALL_PREFIX) 4 | set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory") 5 | endif() 6 | 7 | project(KantuCompare) 8 | 9 | set(CMAKE_CXX_STANDARD 17) 10 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 11 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 12 | 13 | option(KANTU_USE_PVS "Use PVS-Studio for analysis?" OFF) 14 | option(KANTU_TESTING "Build unit test?" ON) 15 | 16 | include("cmake/output_dir.cmake") 17 | include("cmake/deps.cmake") 18 | include("cmake/lldb-debug.cmake") 19 | include("cmake/overlook.cmake") 20 | include("cmake/ninja_colorful_output.cmake") 21 | #include("cmake/asan.cmake") 22 | #include("cmake/tsan.cmake") 23 | 24 | add_subdirectory(src/kantu) 25 | 26 | if(KANTU_TESTING) 27 | enable_testing() 28 | add_subdirectory(src/kantu_tests) 29 | endif() 30 | 31 | include(cmake/summary.cmake) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Zhuo Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KantuCompare 2 | 3 | GitHub [![linux-x64](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/linux-x64.yml/badge.svg)](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/linux-x64.yml) [![windows-x64](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/windows-x64.yml/badge.svg)](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/windows-x64.yml) [![mac-x64](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/mac-x64.yml/badge.svg)](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/mac-x64.yml) 4 | 5 | A GUI for image difference visualization. 6 | 7 | ![](images/snapshots/snapshot_2022-06-12.png) 8 | 9 | ## Features 10 | - Support various image formats: encoded and Fourcc images (`[prefix_]WIDTHxHEIGHT.ext`): 11 | - `.jpg`, `.png`, `.bmp`, `.jpeg` 12 | - `test_1280x720.NV21`, `test_1280x720.NV12`, `hello_7680x4320.nv21`, `yes_640x480.i420` 13 | - `lena_1280x720.rgb24`, `lena_1280x720.bgr24`, `lena_1280x720.gray` 14 | - Automatically compare and display difference image, just like Beyond Compare 15 | 16 | ## Usage 17 | Click "Load" buttons to load images. Once both two input images loaded, the diff image is computed and displayed. 18 | 19 | - Change `Tolerance` slider to get different compare result. 20 | - Change `Zoom` slider or use mouse wheel to scale images. 21 | 22 | See [images](https://github.com/zchrissirhcz/KantuCompare/tree/main/images) directory for testing images. 23 | 24 | ## Installation 25 | 26 | ### Download Prebuilt 27 | 28 | There are prebuilt executable packages in the [Releases page](https://github.com/zchrissirhcz/KantuCompare/releases), support Windows-x64, Linux-x64, MacOSX-x64. 29 | 30 | ### Build from source 31 | ```bash 32 | # clone repo 33 | https://github.com/zchrissirhcz/KantuCompare 34 | cd KantuCompare 35 | 36 | # clone, build and install dependencies by provided script 37 | pip install -r requirements.txt 38 | python sledpkg_run.py 39 | # alternatively, go to cmake/deps.cmake and modify it 40 | 41 | # build the project itself 42 | cd build 43 | ./vs2022-x64.cmd # for Windows 44 | ./linux-x64.sh # for Linux/MacOSX 45 | 46 | # start the app 47 | cd linux-x64 48 | ./KantuCompareApp 49 | ``` 50 | 51 | ## Remarks 52 | The [initial trial](https://github.com/zchrissirhcz/KantuCompare/releases/tag/v0) was based on Qt. 53 | 54 | ## References 55 | - https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples 56 | - https://github.com/aang7/Pix 57 | - https://github.com/shangchiwu/advanced-image-processor 58 | - https://www.youtube.com/watch?v=OYQp0GuoByM 59 | - https://github.com/Smorodov/imgui_image_viewer -------------------------------------------------------------------------------- /build/addr2line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | 5 | NDK_DIR="/home/zz/soft/android-ndk-r21e" 6 | NDK_ADDR2LINE="{:s}/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line".format(NDK_DIR) 7 | 8 | raw_lines = """ 9 | #0 0x4abac8 in calloc (/home/zz/work/image-compare/build/linux-x64/ImageCompare+0x4abac8) 10 | #1 0x7f64ec68bbdf in _XimOpenIM (/usr/lib/x86_64-linux-gnu/libX11.so.6+0x65bdf) 11 | """.strip().split("\n") 12 | 13 | exe_file = 'linux-x64/ImageCompare' 14 | exe_file_name = exe_file.split('/')[-1] 15 | addr2line = 'addr2line' 16 | #addr2line = NDK_ADDR2LINE 17 | 18 | 19 | for raw_line in raw_lines: 20 | pos = raw_line.find("+0x") 21 | addr_str = raw_line[pos+1:-1] 22 | #$NDK_ADDR2LINE 0x5f5a559d90 -C -f -e ./android-arm64/testbed 23 | #print(remain) 24 | 25 | cmd = "{:s} {:s} -C -f -e {:s}".format(addr2line, addr_str, exe_file) 26 | #cmd = "$NDK_ADDR2LINE {:s} -C -f -e {:s}".format(addr_str, exe_file) 27 | 28 | process = subprocess.Popen(cmd, 29 | shell=True, 30 | stdout=subprocess.PIPE, 31 | ) 32 | output = process.communicate()[0].decode('utf-8').strip() 33 | outlines = output.split('\n') 34 | 35 | # ignore no address info 36 | if (outlines[0] == "??" or outlines[1] == "??:0"): 37 | continue 38 | 39 | # ignore LLVM compiler(STL) stuffs 40 | if ('llvm' in outlines[1]): 41 | continue 42 | # ignore google test stuffs 43 | if ('buildbot' in outlines[1]): 44 | continue 45 | 46 | #print("[command] \n" + cmd) 47 | print(cmd) 48 | print(output) 49 | print("") 50 | 51 | -------------------------------------------------------------------------------- /build/linux-x64-scan-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCAN_BUILD=scan-build-15 4 | BUILD_DIR=linux-x64-scan-build 5 | $SCAN_BUILD cmake -S .. -B $BUILD_DIR -DCMAKE_BUILD_TYPE=Debugs #-GNinja 6 | $SCAN_BUILD cmake --build $BUILD_DIR -j4 7 | -------------------------------------------------------------------------------- /build/linux-x64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BUILD_DIR=linux-x64 4 | cmake -S .. -B $BUILD_DIR -DCMAKE_BUILD_TYPE=Debug -GNinja 5 | cmake --build $BUILD_DIR -j$(nproc) -------------------------------------------------------------------------------- /build/vs2022-x64.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set BUILD_DIR=vs2022-x64 4 | 5 | cmake -S .. -B %BUILD_DIR% -G "Visual Studio 17 2022" -A x64 -DKANTU_TESTING=ON 6 | 7 | @REM build 8 | @REM cmake --build %BUILD_DIR% --config Debug 9 | @REM cmake --build %BUILD_DIR% --config Release 10 | 11 | @REM install 12 | @REM --prefix xxx is optional 13 | @REM cmake --install %BUILD_DIR% --config Debug --prefix d:/lib/xxx 14 | @REM cmake --install %BUILD_DIR% --config Release --prefix d:/lib/xxx 15 | 16 | pause -------------------------------------------------------------------------------- /cmake/PVS-Studio.cmake: -------------------------------------------------------------------------------- 1 | # 2006-2008 (c) Viva64.com Team 2 | # 2008-2018 (c) OOO "Program Verification Systems" 3 | # 4 | # Version 12 5 | 6 | cmake_minimum_required(VERSION 2.8.12) 7 | cmake_policy(SET CMP0054 NEW) 8 | 9 | if (PVS_STUDIO_AS_SCRIPT) 10 | # This code runs at build time. 11 | # It executes pvs-studio-analyzer and propagates its return value. 12 | 13 | set(in_cl_params FALSE) 14 | set(additional_args) 15 | 16 | foreach (arg ${PVS_STUDIO_COMMAND}) 17 | if (NOT in_cl_params) 18 | if ("${arg}" STREQUAL "--cl-params") 19 | set(in_cl_params TRUE) 20 | endif () 21 | else () 22 | # A workaround for macOS frameworks (e.g. QtWidgets.framework) 23 | # You can test this workaround on this project: https://github.com/easyaspi314/MidiEditor/tree/gba 24 | if (APPLE AND "${arg}" MATCHES "^-I(.*)\\.framework$") 25 | STRING(REGEX REPLACE "^-I(.*)\\.framework$" "\\1.framework" framework "${arg}") 26 | if (IS_ABSOLUTE "${framework}") 27 | get_filename_component(framework "${framework}" DIRECTORY) 28 | list(APPEND additional_args "-iframework") 29 | list(APPEND additional_args "${framework}") 30 | endif () 31 | endif () 32 | endif () 33 | endforeach () 34 | 35 | execute_process(COMMAND ${PVS_STUDIO_COMMAND} ${additional_args} 36 | RESULT_VARIABLE result 37 | OUTPUT_VARIABLE output 38 | ERROR_VARIABLE error) 39 | 40 | if (result AND NOT output MATCHES "^No compilation units were found\\.") 41 | message(FATAL_ERROR "PVS-Studio exited with non-zero code.\nStdout:\n${output}\nStderr:\n${error}\n") 42 | endif() 43 | 44 | return() 45 | endif () 46 | 47 | if(__PVS_STUDIO_INCLUDED) 48 | return() 49 | endif() 50 | set(__PVS_STUDIO_INCLUDED TRUE) 51 | 52 | set(PVS_STUDIO_SCRIPT "${CMAKE_CURRENT_LIST_FILE}") 53 | 54 | function (pvs_studio_log TEXT) 55 | if (PVS_STUDIO_DEBUG) 56 | message("PVS-Studio: ${TEXT}") 57 | endif () 58 | endfunction () 59 | 60 | function (pvs_studio_relative_path VAR ROOT FILEPATH) 61 | if (WIN32) 62 | STRING(REGEX REPLACE "\\\\" "/" ROOT ${ROOT}) 63 | STRING(REGEX REPLACE "\\\\" "/" FILEPATH ${FILEPATH}) 64 | endif() 65 | set("${VAR}" "${FILEPATH}" PARENT_SCOPE) 66 | if (IS_ABSOLUTE "${FILEPATH}") 67 | file(RELATIVE_PATH RPATH "${ROOT}" "${FILEPATH}") 68 | if (NOT IS_ABSOLUTE "${RPATH}") 69 | set("${VAR}" "${RPATH}" PARENT_SCOPE) 70 | endif() 71 | endif() 72 | endfunction () 73 | 74 | function (pvs_studio_join_path VAR DIR1 DIR2) 75 | if ("${DIR2}" MATCHES "^(/|~|.:/).*$" OR "${DIR1}" STREQUAL "") 76 | set("${VAR}" "${DIR2}" PARENT_SCOPE) 77 | else () 78 | set("${VAR}" "${DIR1}/${DIR2}" PARENT_SCOPE) 79 | endif () 80 | endfunction () 81 | 82 | macro (pvs_studio_append_flags_from_property CXX C DIR PREFIX) 83 | if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") 84 | foreach (PROP ${PROPERTY}) 85 | pvs_studio_join_path(PROP "${DIR}" "${PROP}") 86 | 87 | if (APPLE AND "${PREFIX}" STREQUAL "-I" AND IS_ABSOLUTE "${PROP}" AND "${PROP}" MATCHES "\\.framework$") 88 | get_filename_component(FRAMEWORK "${PROP}" DIRECTORY) 89 | list(APPEND "${CXX}" "-iframework") 90 | list(APPEND "${CXX}" "${FRAMEWORK}") 91 | list(APPEND "${C}" "-iframework") 92 | list(APPEND "${C}" "${FRAMEWORK}") 93 | pvs_studio_log("framework: ${FRAMEWORK}") 94 | elseif (NOT "${PROP}" STREQUAL "") 95 | list(APPEND "${CXX}" "${PREFIX}${PROP}") 96 | list(APPEND "${C}" "${PREFIX}${PROP}") 97 | endif() 98 | endforeach () 99 | endif () 100 | endmacro () 101 | 102 | macro (pvs_studio_append_standard_flag FLAGS STANDARD) 103 | if ("${STANDARD}" MATCHES "^(99|11|14|17|20)$") 104 | if ("${PVS_STUDIO_PREPROCESSOR}" MATCHES "gcc|clang") 105 | list(APPEND "${FLAGS}" "-std=c++${STANDARD}") 106 | endif () 107 | endif () 108 | endmacro () 109 | 110 | function (pvs_studio_set_directory_flags DIRECTORY CXX C) 111 | set(CXX_FLAGS "${${CXX}}") 112 | set(C_FLAGS "${${C}}") 113 | 114 | get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" INCLUDE_DIRECTORIES) 115 | pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "${DIRECTORY}" "-I") 116 | 117 | get_directory_property(PROPERTY DIRECTORY "${DIRECTORY}" COMPILE_DEFINITIONS) 118 | pvs_studio_append_flags_from_property(CXX_FLAGS C_FLAGS "" "-D") 119 | 120 | set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) 121 | set("${C}" "${C_FLAGS}" PARENT_SCOPE) 122 | endfunction () 123 | 124 | function (pvs_studio_set_target_flags TARGET CXX C) 125 | set(CXX_FLAGS "${${CXX}}") 126 | set(C_FLAGS "${${C}}") 127 | 128 | if (NOT MSVC) 129 | list(APPEND CXX_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") 130 | list(APPEND C_FLAGS "$<$:--sysroot=${CMAKE_SYSROOT}>") 131 | endif () 132 | 133 | set(prop_incdirs "$") 134 | list(APPEND CXX_FLAGS "$<$:-I$-I>>") 135 | list(APPEND C_FLAGS "$<$:-I$-I>>") 136 | 137 | set(prop_compdefs "$") 138 | list(APPEND CXX_FLAGS "$<$:-D$-D>>") 139 | list(APPEND C_FLAGS "$<$:-D$-D>>") 140 | 141 | set(prop_compopt "$") 142 | list(APPEND CXX_FLAGS "$<$:$>>") 143 | list(APPEND C_FLAGS "$<$:$>>") 144 | 145 | set("${CXX}" "${CXX_FLAGS}" PARENT_SCOPE) 146 | set("${C}" "${C_FLAGS}" PARENT_SCOPE) 147 | endfunction () 148 | 149 | function (pvs_studio_set_source_file_flags SOURCE) 150 | set(LANGUAGE "") 151 | 152 | string(TOLOWER "${SOURCE}" SOURCE_LOWER) 153 | if ("${LANGUAGE}" STREQUAL "" AND "${SOURCE_LOWER}" MATCHES "^.*\\.(c|cpp|cc|cx|cxx|cp|c\\+\\+)$") 154 | if ("${SOURCE}" MATCHES "^.*\\.c$") 155 | set(LANGUAGE C) 156 | else () 157 | set(LANGUAGE CXX) 158 | endif () 159 | endif () 160 | 161 | if ("${LANGUAGE}" STREQUAL "C") 162 | set(CL_PARAMS ${PVS_STUDIO_C_FLAGS} ${PVS_STUDIO_TARGET_C_FLAGS} -DPVS_STUDIO) 163 | elseif ("${LANGUAGE}" STREQUAL "CXX") 164 | set(CL_PARAMS ${PVS_STUDIO_CXX_FLAGS} ${PVS_STUDIO_TARGET_CXX_FLAGS} -DPVS_STUDIO) 165 | endif () 166 | 167 | set(PVS_STUDIO_LANGUAGE "${LANGUAGE}" PARENT_SCOPE) 168 | set(PVS_STUDIO_CL_PARAMS "${CL_PARAMS}" PARENT_SCOPE) 169 | endfunction () 170 | 171 | function (pvs_studio_analyze_file SOURCE SOURCE_DIR BINARY_DIR) 172 | set(PLOGS ${PVS_STUDIO_PLOGS}) 173 | pvs_studio_set_source_file_flags("${SOURCE}") 174 | 175 | get_filename_component(SOURCE "${SOURCE}" REALPATH) 176 | 177 | get_source_file_property(PROPERTY "${SOURCE}" HEADER_FILE_ONLY) 178 | if (PROPERTY) 179 | return() 180 | endif () 181 | 182 | pvs_studio_relative_path(SOURCE_RELATIVE "${SOURCE_DIR}" "${SOURCE}") 183 | pvs_studio_join_path(SOURCE "${SOURCE_DIR}" "${SOURCE}") 184 | 185 | set(LOG "${BINARY_DIR}/PVS-Studio/${SOURCE_RELATIVE}.plog") 186 | get_filename_component(LOG "${LOG}" REALPATH) 187 | get_filename_component(PARENT_DIR "${LOG}" DIRECTORY) 188 | 189 | if (EXISTS "${SOURCE}" AND NOT TARGET "${LOG}" AND NOT "${PVS_STUDIO_LANGUAGE}" STREQUAL "") 190 | # A workaround to support implicit dependencies for ninja generators. 191 | set(depPvsArg) 192 | set(depCommandArg) 193 | if (CMAKE_VERSION VERSION_GREATER 3.6 AND "${CMAKE_GENERATOR}" STREQUAL "Ninja") 194 | pvs_studio_relative_path(relLog "${CMAKE_BINARY_DIR}" "${LOG}") 195 | set(depPvsArg --dep-file "${LOG}.d" --dep-file-target "${relLog}") 196 | set(depCommandArg DEPFILE "${LOG}.d") 197 | endif () 198 | 199 | # https://public.kitware.com/Bug/print_bug_page.php?bug_id=14353 200 | # https://public.kitware.com/Bug/file/5436/expand_command.cmake 201 | # 202 | # It is a workaround to expand generator expressions. 203 | set(cmdline "${PVS_STUDIO_BIN}" analyze 204 | --output-file "${LOG}" 205 | --source-file "${SOURCE}" 206 | ${depPvsArg} 207 | ${PVS_STUDIO_ARGS} 208 | --cl-params "${PVS_STUDIO_CL_PARAMS}" "${SOURCE}") 209 | 210 | string(REPLACE ";" "$" cmdline "${cmdline}") 211 | set(pvscmd "${CMAKE_COMMAND}" 212 | -D PVS_STUDIO_AS_SCRIPT=TRUE 213 | -D "PVS_STUDIO_COMMAND=${cmdline}" 214 | -P "${PVS_STUDIO_SCRIPT}" 215 | ) 216 | 217 | add_custom_command(OUTPUT "${LOG}" 218 | COMMAND "${CMAKE_COMMAND}" -E make_directory "${PARENT_DIR}" 219 | COMMAND "${CMAKE_COMMAND}" -E remove_directory "${LOG}" 220 | COMMAND ${pvscmd} 221 | WORKING_DIRECTORY "${BINARY_DIR}" 222 | DEPENDS "${SOURCE}" "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" 223 | IMPLICIT_DEPENDS "${PVS_STUDIO_LANGUAGE}" "${SOURCE}" 224 | ${depCommandArg} 225 | VERBATIM 226 | COMMENT "Analyzing ${PVS_STUDIO_LANGUAGE} file ${SOURCE_RELATIVE}") 227 | list(APPEND PLOGS "${LOG}") 228 | endif () 229 | set(PVS_STUDIO_PLOGS "${PLOGS}" PARENT_SCOPE) 230 | endfunction () 231 | 232 | function (pvs_studio_analyze_target TARGET DIR) 233 | set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") 234 | set(PVS_STUDIO_TARGET_CXX_FLAGS "") 235 | set(PVS_STUDIO_TARGET_C_FLAGS "") 236 | 237 | get_target_property(PROPERTY "${TARGET}" SOURCES) 238 | pvs_studio_relative_path(BINARY_DIR "${CMAKE_SOURCE_DIR}" "${DIR}") 239 | if ("${BINARY_DIR}" MATCHES "^/.*$") 240 | pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "PVS-Studio/__${BINARY_DIR}") 241 | else () 242 | pvs_studio_join_path(BINARY_DIR "${CMAKE_BINARY_DIR}" "${BINARY_DIR}") 243 | endif () 244 | 245 | file(MAKE_DIRECTORY "${BINARY_DIR}") 246 | 247 | pvs_studio_set_directory_flags("${DIR}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) 248 | pvs_studio_set_target_flags("${TARGET}" PVS_STUDIO_TARGET_CXX_FLAGS PVS_STUDIO_TARGET_C_FLAGS) 249 | 250 | if (NOT "${PROPERTY}" STREQUAL "NOTFOUND" AND NOT "${PROPERTY}" STREQUAL "PROPERTY-NOTFOUND") 251 | foreach (SOURCE ${PROPERTY}) 252 | pvs_studio_join_path(SOURCE "${DIR}" "${SOURCE}") 253 | pvs_studio_analyze_file("${SOURCE}" "${DIR}" "${BINARY_DIR}") 254 | endforeach () 255 | endif () 256 | 257 | set(PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}" PARENT_SCOPE) 258 | endfunction () 259 | 260 | set(PVS_STUDIO_RECURSIVE_TARGETS) 261 | set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) 262 | 263 | macro(pvs_studio_get_recursive_targets TARGET) 264 | get_target_property(libs "${TARGET}" LINK_LIBRARIES) 265 | foreach (lib IN LISTS libs) 266 | list(FIND PVS_STUDIO_RECURSIVE_TARGETS "${lib}" index) 267 | if (TARGET "${lib}" AND "${index}" STREQUAL -1) 268 | get_target_property(target_type "${lib}" TYPE) 269 | if (NOT "${target_type}" STREQUAL "INTERFACE_LIBRARY") 270 | list(APPEND PVS_STUDIO_RECURSIVE_TARGETS "${lib}") 271 | list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${lib}") 272 | pvs_studio_get_recursive_targets("${lib}") 273 | endif () 274 | endif () 275 | endforeach () 276 | endmacro() 277 | 278 | option(PVS_STUDIO_DISABLE OFF "Disable PVS-Studio targets") 279 | option(PVS_STUDIO_DEBUG OFF "Add debug info") 280 | 281 | # pvs_studio_add_target 282 | # Target options: 283 | # ALL add PVS-Studio target to default build (default: off) 284 | # TARGET target name of analysis target (default: pvs) 285 | # ANALYZE targets... targets to analyze 286 | # RECURSIVE analyze target's dependencies (requires CMake 3.5+) 287 | # COMPILE_COMMANDS use compile_commands.json instead of targets (specified by the 'ANALYZE' option) to determine files for analysis 288 | # (set CMAKE_EXPORT_COMPILE_COMMANDS, available only for Makefile and Ninja generators) 289 | # 290 | # Output options: 291 | # OUTPUT prints report to stdout 292 | # LOG path path to report (default: ${CMAKE_CURRENT_BINARY_DIR}/PVS-Studio.log) 293 | # FORMAT format format of report 294 | # MODE mode analyzers/levels filter (default: GA:1,2) 295 | # HIDE_HELP do not print help message 296 | # 297 | # Analyzer options: 298 | # PLATFORM name linux32/linux64 (default: linux64) 299 | # PREPROCESSOR name preprocessor type: gcc/clang (default: auto detected) 300 | # LICENSE path path to PVS-Studio.lic (default: ~/.config/PVS-Studio/PVS-Studio.lic) 301 | # CONFIG path path to PVS-Studio.cfg 302 | # CFG_TEXT text embedded PVS-Studio.cfg 303 | # SUPPRESS_BASE path to suppress base file 304 | # KEEP_COMBINED_PLOG do not delete combined plog file *.pvs.raw for further processing with plog-converter 305 | # 306 | # Misc options: 307 | # DEPENDS targets.. additional target dependencies 308 | # SOURCES path... list of source files to analyze 309 | # BIN path path to pvs-studio-analyzer (Unix) or CompilerCommandsAnalyzer.exe (Windows) 310 | # CONVERTER path path to plog-converter (Unix) or HtmlGenerator.exe (Windows) 311 | # C_FLAGS flags... additional C_FLAGS 312 | # CXX_FLAGS flags... additional CXX_FLAGS 313 | # ARGS args... additional pvs-studio-analyzer/CompilerCommandsAnalyzer.exe flags 314 | # CONVERTER_ARGS args... additional plog-converter/HtmlGenerator.exe flags 315 | function (pvs_studio_add_target) 316 | macro (default VAR VALUE) 317 | if ("${${VAR}}" STREQUAL "") 318 | set("${VAR}" "${VALUE}") 319 | endif () 320 | endmacro () 321 | 322 | set(PVS_STUDIO_SUPPORTED_PREPROCESSORS "gcc|clang|visualcpp") 323 | if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") 324 | set(DEFAULT_PREPROCESSOR "clang") 325 | elseif (MSVC) 326 | set(DEFAULT_PREPROCESSOR "visualcpp") 327 | else () 328 | set(DEFAULT_PREPROCESSOR "gcc") 329 | endif () 330 | 331 | set(OPTIONAL OUTPUT ALL RECURSIVE HIDE_HELP KEEP_COMBINED_PLOG COMPILE_COMMANDS KEEP_INTERMEDIATE_FILES) 332 | set(SINGLE LICENSE CONFIG TARGET LOG FORMAT BIN CONVERTER PLATFORM PREPROCESSOR CFG_TEXT SUPPRESS_BASE) 333 | set(MULTI SOURCES C_FLAGS CXX_FLAGS ARGS DEPENDS ANALYZE MODE CONVERTER_ARGS) 334 | cmake_parse_arguments(PVS_STUDIO "${OPTIONAL}" "${SINGLE}" "${MULTI}" ${ARGN}) 335 | 336 | 337 | default(PVS_STUDIO_C_FLAGS "") 338 | default(PVS_STUDIO_CXX_FLAGS "") 339 | default(PVS_STUDIO_TARGET "pvs") 340 | default(PVS_STUDIO_LOG "PVS-Studio.log") 341 | 342 | set(PATHS) 343 | if (WIN32) 344 | set(ROOT "PROGRAMFILES(X86)") 345 | set(ROOT "$ENV{${ROOT}}/PVS-Studio") 346 | string(REPLACE \\ / ROOT "${ROOT}") 347 | 348 | if (EXISTS "${ROOT}") 349 | set(PATHS "${ROOT}") 350 | endif () 351 | 352 | default(PVS_STUDIO_BIN "CompilerCommandsAnalyzer.exe") 353 | default(PVS_STUDIO_CONVERTER "HtmlGenerator.exe") 354 | else () 355 | default(PVS_STUDIO_BIN "pvs-studio-analyzer") 356 | default(PVS_STUDIO_CONVERTER "plog-converter") 357 | endif () 358 | 359 | find_program(PVS_STUDIO_BIN_PATH "${PVS_STUDIO_BIN}" ${PATHS}) 360 | set(PVS_STUDIO_BIN "${PVS_STUDIO_BIN_PATH}") 361 | 362 | if (NOT EXISTS "${PVS_STUDIO_BIN}") 363 | message(FATAL_ERROR "pvs-studio-analyzer is not found") 364 | endif () 365 | 366 | find_program(PVS_STUDIO_CONVERTER_PATH "${PVS_STUDIO_CONVERTER}" ${PATHS}) 367 | set(PVS_STUDIO_CONVERTER "${PVS_STUDIO_CONVERTER_PATH}") 368 | 369 | if (NOT EXISTS "${PVS_STUDIO_CONVERTER}") 370 | message(FATAL_ERROR "plog-converter is not found") 371 | endif () 372 | 373 | default(PVS_STUDIO_MODE "GA:1,2") 374 | default(PVS_STUDIO_PREPROCESSOR "${DEFAULT_PREPROCESSOR}") 375 | if (WIN32) 376 | default(PVS_STUDIO_PLATFORM "x64") 377 | else () 378 | default(PVS_STUDIO_PLATFORM "linux64") 379 | endif () 380 | 381 | string(REPLACE ";" "+" PVS_STUDIO_MODE "${PVS_STUDIO_MODE}") 382 | 383 | if ("${PVS_STUDIO_CONFIG}" STREQUAL "" AND NOT "${PVS_STUDIO_CFG_TEXT}" STREQUAL "") 384 | set(PVS_STUDIO_CONFIG "${CMAKE_BINARY_DIR}/PVS-Studio.cfg") 385 | 386 | set(PVS_STUDIO_CONFIG_COMMAND "${CMAKE_COMMAND}" -E echo "${PVS_STUDIO_CFG_TEXT}" > "${PVS_STUDIO_CONFIG}") 387 | 388 | add_custom_command(OUTPUT "${PVS_STUDIO_CONFIG}" 389 | COMMAND ${PVS_STUDIO_CONFIG_COMMAND} 390 | WORKING_DIRECTORY "${BINARY_DIR}" 391 | COMMENT "Generating PVS-Studio.cfg") 392 | 393 | list(APPEND PVS_STUDIO_DEPENDS "${PVS_STUDIO_CONFIG}") 394 | endif () 395 | if (NOT "${PVS_STUDIO_PREPROCESSOR}" MATCHES "^${PVS_STUDIO_SUPPORTED_PREPROCESSORS}$") 396 | message(FATAL_ERROR "Preprocessor ${PVS_STUDIO_PREPROCESSOR} isn't supported. Available options: ${PVS_STUDIO_SUPPORTED_PREPROCESSORS}.") 397 | endif () 398 | 399 | pvs_studio_append_standard_flag(PVS_STUDIO_CXX_FLAGS "${CMAKE_CXX_STANDARD}") 400 | pvs_studio_set_directory_flags("${CMAKE_CURRENT_SOURCE_DIR}" PVS_STUDIO_CXX_FLAGS PVS_STUDIO_C_FLAGS) 401 | 402 | if (NOT "${PVS_STUDIO_LICENSE}" STREQUAL "") 403 | list(APPEND PVS_STUDIO_ARGS --lic-file "${PVS_STUDIO_LICENSE}") 404 | endif () 405 | 406 | if (NOT ${PVS_STUDIO_CONFIG} STREQUAL "") 407 | list(APPEND PVS_STUDIO_ARGS --cfg "${PVS_STUDIO_CONFIG}") 408 | endif () 409 | 410 | list(APPEND PVS_STUDIO_ARGS --platform "${PVS_STUDIO_PLATFORM}" 411 | --preprocessor "${PVS_STUDIO_PREPROCESSOR}") 412 | 413 | if (NOT "${PVS_STUDIO_SUPPRESS_BASE}" STREQUAL "") 414 | pvs_studio_join_path(PVS_STUDIO_SUPPRESS_BASE "${CMAKE_CURRENT_SOURCE_DIR}" "${PVS_STUDIO_SUPPRESS_BASE}") 415 | list(APPEND PVS_STUDIO_ARGS --suppress-file "${PVS_STUDIO_SUPPRESS_BASE}") 416 | endif () 417 | 418 | if (NOT "${CMAKE_CXX_COMPILER}" STREQUAL "") 419 | list(APPEND PVS_STUDIO_ARGS --cxx "${CMAKE_CXX_COMPILER}") 420 | endif () 421 | 422 | if (NOT "${CMAKE_C_COMPILER}" STREQUAL "") 423 | list(APPEND PVS_STUDIO_ARGS --cc "${CMAKE_C_COMPILER}") 424 | endif () 425 | 426 | if (PVS_STUDIO_KEEP_INTERMEDIATE_FILES) 427 | list(APPEND PVS_STUDIO_ARGS --dump-files) 428 | endif() 429 | 430 | string(REGEX REPLACE [123,:] "" ANALYZER_MODE ${PVS_STUDIO_MODE}) 431 | if (NOT "$ANALYZER_MODE" STREQUAL "GA") 432 | list (APPEND PVS_STUDIO_ARGS -a "${ANALYZER_MODE}") 433 | endif () 434 | 435 | set(PVS_STUDIO_PLOGS "") 436 | 437 | set(PVS_STUDIO_RECURSIVE_TARGETS_NEW) 438 | if (${PVS_STUDIO_RECURSIVE}) 439 | foreach (TARGET IN LISTS PVS_STUDIO_ANALYZE) 440 | list(APPEND PVS_STUDIO_RECURSIVE_TARGETS_NEW "${TARGET}") 441 | pvs_studio_get_recursive_targets("${TARGET}") 442 | endforeach () 443 | endif () 444 | 445 | set(inc_path) 446 | 447 | foreach (TARGET ${PVS_STUDIO_ANALYZE}) 448 | set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") 449 | string(FIND "${TARGET}" ":" DELIM) 450 | if ("${DELIM}" GREATER "-1") 451 | math(EXPR DELIMI "${DELIM}+1") 452 | string(SUBSTRING "${TARGET}" "${DELIMI}" "-1" DIR) 453 | string(SUBSTRING "${TARGET}" "0" "${DELIM}" TARGET) 454 | pvs_studio_join_path(DIR "${CMAKE_CURRENT_SOURCE_DIR}" "${DIR}") 455 | else () 456 | get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) 457 | if (EXISTS "${TARGET_SOURCE_DIR}") 458 | set(DIR "${TARGET_SOURCE_DIR}") 459 | endif () 460 | endif () 461 | pvs_studio_analyze_target("${TARGET}" "${DIR}") 462 | list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") 463 | 464 | if ("${inc_path}" STREQUAL "") 465 | set(inc_path "$") 466 | else () 467 | set(inc_path "${inc_path}$$") 468 | endif () 469 | endforeach () 470 | 471 | foreach (TARGET ${PVS_STUDIO_RECURSIVE_TARGETS_NEW}) 472 | set(DIR "${CMAKE_CURRENT_SOURCE_DIR}") 473 | get_target_property(TARGET_SOURCE_DIR "${TARGET}" SOURCE_DIR) 474 | if (EXISTS "${TARGET_SOURCE_DIR}") 475 | set(DIR "${TARGET_SOURCE_DIR}") 476 | endif () 477 | pvs_studio_analyze_target("${TARGET}" "${DIR}") 478 | list(APPEND PVS_STUDIO_DEPENDS "${TARGET}") 479 | endforeach () 480 | 481 | set(PVS_STUDIO_TARGET_CXX_FLAGS "") 482 | set(PVS_STUDIO_TARGET_C_FLAGS "") 483 | foreach (SOURCE ${PVS_STUDIO_SOURCES}) 484 | pvs_studio_analyze_file("${SOURCE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}") 485 | endforeach () 486 | 487 | if (PVS_STUDIO_COMPILE_COMMANDS) 488 | set(COMPILE_COMMANDS_LOG "${PVS_STUDIO_LOG}.pvs.analyzer.raw") 489 | if (NOT CMAKE_EXPORT_COMPILE_COMMANDS) 490 | message(FATAL_ERROR "You should set CMAKE_EXPORT_COMPILE_COMMANDS to TRUE") 491 | endif () 492 | add_custom_command( 493 | OUTPUT "${COMPILE_COMMANDS_LOG}" 494 | COMMAND "${PVS_STUDIO_BIN}" analyze -i 495 | --output-file "${COMPILE_COMMANDS_LOG}.always" 496 | ${PVS_STUDIO_ARGS} 497 | COMMENT "Analyzing with PVS-Studio" 498 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" 499 | DEPENDS "${PVS_STUDIO_SUPPRESS_BASE}" "${PVS_STUDIO_DEPENDS}" 500 | ) 501 | list(APPEND PVS_STUDIO_PLOGS_LOGS "${COMPILE_COMMANDS_LOG}.always") 502 | list(APPEND PVS_STUDIO_PLOGS_DEPENDENCIES "${COMPILE_COMMANDS_LOG}") 503 | endif () 504 | 505 | pvs_studio_relative_path(LOG_RELATIVE "${CMAKE_BINARY_DIR}" "${PVS_STUDIO_LOG}") 506 | if (PVS_STUDIO_PLOGS OR PVS_STUDIO_COMPILE_COMMANDS) 507 | if (WIN32) 508 | string(REPLACE / \\ PVS_STUDIO_PLOGS "${PVS_STUDIO_PLOGS}") 509 | endif () 510 | if (WIN32) 511 | set(COMMANDS COMMAND type ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>nul || cd .) 512 | else () 513 | set(COMMANDS COMMAND cat ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_LOGS} > "${PVS_STUDIO_LOG}" 2>/dev/null || true) 514 | endif () 515 | set(COMMENT "Generating ${LOG_RELATIVE}") 516 | if (NOT "${PVS_STUDIO_FORMAT}" STREQUAL "" OR PVS_STUDIO_OUTPUT) 517 | if ("${PVS_STUDIO_FORMAT}" STREQUAL "") 518 | set(PVS_STUDIO_FORMAT "errorfile") 519 | endif () 520 | set(converter_no_help "") 521 | if (PVS_STUDIO_HIDE_HELP) 522 | set(converter_no_help "--noHelpMessages") 523 | endif() 524 | list(APPEND COMMANDS 525 | COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw" 526 | COMMAND "${CMAKE_COMMAND}" -E rename "${PVS_STUDIO_LOG}" "${PVS_STUDIO_LOG}.pvs.raw" 527 | COMMAND "${PVS_STUDIO_CONVERTER}" "${PVS_STUDIO_CONVERTER_ARGS}" ${converter_no_help} -t "${PVS_STUDIO_FORMAT}" "${PVS_STUDIO_LOG}.pvs.raw" -o "${PVS_STUDIO_LOG}" -a "${PVS_STUDIO_MODE}" 528 | ) 529 | if(NOT PVS_STUDIO_KEEP_COMBINED_PLOG) 530 | list(APPEND COMMANDS COMMAND "${CMAKE_COMMAND}" -E remove -f "${PVS_STUDIO_LOG}.pvs.raw") 531 | endif() 532 | endif () 533 | else () 534 | set(COMMANDS COMMAND "${CMAKE_COMMAND}" -E touch "${PVS_STUDIO_LOG}") 535 | set(COMMENT "Generating ${LOG_RELATIVE}: no sources found") 536 | endif () 537 | 538 | if (WIN32) 539 | string(REPLACE / \\ PVS_STUDIO_LOG "${PVS_STUDIO_LOG}") 540 | endif () 541 | 542 | add_custom_command(OUTPUT "${PVS_STUDIO_LOG}" 543 | ${COMMANDS} 544 | COMMENT "${COMMENT}" 545 | DEPENDS ${PVS_STUDIO_PLOGS} ${PVS_STUDIO_PLOGS_DEPENDENCIES} 546 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") 547 | 548 | if (PVS_STUDIO_ALL) 549 | set(ALL "ALL") 550 | else () 551 | set(ALL "") 552 | endif () 553 | 554 | if (PVS_STUDIO_OUTPUT) 555 | if (WIN32) 556 | set(COMMANDS COMMAND type "${PVS_STUDIO_LOG}" 1>&2) 557 | else () 558 | set(COMMANDS COMMAND cat "${PVS_STUDIO_LOG}" 1>&2) 559 | endif() 560 | else () 561 | set(COMMANDS "") 562 | endif () 563 | 564 | add_custom_target("${PVS_STUDIO_TARGET}" ${ALL} ${COMMANDS} 565 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" 566 | DEPENDS ${PVS_STUDIO_DEPENDS} "${PVS_STUDIO_LOG}") 567 | 568 | # A workaround to add implicit dependencies of source files from include directories 569 | set_target_properties("${PVS_STUDIO_TARGET}" PROPERTIES INCLUDE_DIRECTORIES "${inc_path}") 570 | endfunction () 571 | -------------------------------------------------------------------------------- /cmake/asan.cmake: -------------------------------------------------------------------------------- 1 | # author: ChrisZZ 2 | # last update: 2023-04-25 11:38:04 3 | 4 | option(USE_ASAN "Use Address Sanitizer?" ON) 5 | option(VS2022_ASAN_DISABLE_VECTOR_ANNOTATION "Disable vector annotation for VS2022 ASan?" ON) 6 | 7 | #-------------------------------------------------- 8 | # globally 9 | #-------------------------------------------------- 10 | # https://stackoverflow.com/a/65019152/2999096 11 | # https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170#enable-addresssanitizer-for-windows-and-linux 12 | if(USE_ASAN) 13 | if((CMAKE_C_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")) 14 | set(ASAN_OPTIONS "/fsanitize=address") 15 | elseif(MSVC AND ((CMAKE_C_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))) 16 | message(FATAL_ERROR "Clang-CL not support setup AddressSanitizer via CMakeLists.txt") 17 | elseif((CMAKE_C_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 18 | OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) 19 | set(ASAN_OPTIONS -fsanitize=address -fno-omit-frame-pointer -g) 20 | endif() 21 | message(STATUS ">>> USE_ASAN: YES") 22 | message(STATUS ">>> CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 23 | add_compile_options(${ASAN_OPTIONS}) 24 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 25 | add_link_options("/ignore:4300") # /INCREMENTAL 26 | add_link_options("/DEBUG") # LNK4302 27 | if(CMAKE_CXX_COMPILER_VERSION STRGREATER_EQUAL 17.2) 28 | if(VS2022_ASAN_DISABLE_VECTOR_ANNOTATION) 29 | # https://learn.microsoft.com/en-us/cpp/sanitizers/error-container-overflow?view=msvc-170 30 | add_definitions(-D_DISABLE_VECTOR_ANNOTATION) 31 | message(STATUS ">>> VS2022_ASAN_DISABLE_VECTOR_ANNOTATION: YES") 32 | else() 33 | message(STATUS ">>> VS2022_ASAN_DISABLE_VECTOR_ANNOTATION: NO") 34 | endif() 35 | endif() 36 | else() 37 | add_link_options(${ASAN_OPTIONS}) 38 | endif() 39 | else() 40 | message(STATUS ">>> USE_ASAN: NO") 41 | endif() 42 | 43 | 44 | #-------------------------------------------------- 45 | # per-target 46 | #-------------------------------------------------- 47 | # https://developer.android.com/ndk/guides/asan?hl=zh-cn#cmake 48 | #target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer) 49 | #set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address) 50 | -------------------------------------------------------------------------------- /cmake/deps.cmake: -------------------------------------------------------------------------------- 1 | #---------------------------------------------------------------------- 2 | # OpenCV 3 | #---------------------------------------------------------------------- 4 | # using the prebuilt 5 | # sudo apt install libopencv-dev # ubuntu 6 | # brew install opencv # mac 7 | #---------------------------------------------------------------------- 8 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/opencv/install") 9 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 10 | set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/deps/opencv/install/x64/vc17/staticlib") 11 | else() 12 | set(OpenCV_DIR "${CMAKE_SOURCE_DIR}/deps/opencv/install/lib/cmake/opencv4") 13 | endif() 14 | else() 15 | #set(OpenCV_DIR "D:/artifacts/opencv/windows/OpenCV-4.6.0-vs2015-x64-static") 16 | endif() 17 | message(STATUS "OpenCV_DIR: ${OpenCV_DIR}") 18 | find_package(OpenCV REQUIRED) 19 | 20 | 21 | #---------------------------------------------------------------------- 22 | # Dear ImGui 23 | #---------------------------------------------------------------------- 24 | # git clone https://github.com/ocornut/imgui -b docking imgui-docking 25 | # mirror: https://gitee.com/mirrors/imgui 26 | #---------------------------------------------------------------------- 27 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/imgui") 28 | set(IMGUI_DIR "${CMAKE_SOURCE_DIR}/deps/imgui") 29 | else() 30 | #set(IMGUI_DIR "$ENV{HOME}/work/github/imgui") 31 | #set(IMGUI_DIR "$ENV{HOME}/.sled/work_repos/imgui/docking") 32 | #set(IMGUI_DIR "$ENV{HOME}/work/imgui-docking") 33 | endif() 34 | #add_definitions(-DIMGUI_WITH_DOCKING) 35 | include_directories( 36 | ${IMGUI_DIR} 37 | ${IMGUI_DIR}/backends 38 | ) 39 | 40 | 41 | #---------------------------------------------------------------------- 42 | # GLFW 43 | #---------------------------------------------------------------------- 44 | # using the latest source code 45 | # git clone https://github.com/glfw/glfw 46 | # mirror: https://gitee.com/mirrors/glfw 47 | #---------------------------------------------------------------------- 48 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/glfw") 49 | set(glfw_src_dir "${CMAKE_SOURCE_DIR}/deps/glfw") 50 | else() 51 | #set(glfw_src_dir "$ENV{HOME}/.sled/work_repos/glfw/master") 52 | endif() 53 | # find_package(glfw3 REQUIRED) 54 | #set(glfw_src_dir "$ENV{HOME}/work/github/glfw") # Set this to point to an up-to-date GLFW repo 55 | option(GLFW_BUILD_EXAMPLES "Build the GLFW example programs" OFF) 56 | option(GLFW_BUILD_TESTS "Build the GLFW test programs" OFF) 57 | option(GLFW_BUILD_DOCS "Build the GLFW documentation" OFF) 58 | option(GLFW_INSTALL "Generate installation target" OFF) 59 | option(GLFW_DOCUMENT_INTERNALS "Include internals in documentation" OFF) 60 | add_subdirectory(${glfw_src_dir} ${CMAKE_BINARY_DIR}/glfw EXCLUDE_FROM_ALL) 61 | 62 | 63 | #---------------------------------------------------------------------- 64 | # portable-file-dialogs 65 | #---------------------------------------------------------------------- 66 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/portable-file-dialogs") 67 | set(portable_file_dialogs_src_dir "${CMAKE_SOURCE_DIR}/deps/portable-file-dialogs") 68 | else() 69 | #set(portable_file_dialogs_src_dir "$ENV{HOME}/.sled/work_repos/portable-file-dialogs/master") 70 | endif() 71 | add_subdirectory(${portable_file_dialogs_src_dir} ${CMAKE_BINARY_DIR}/portable_file_dialogs) 72 | 73 | 74 | #---------------------------------------------------------------------- 75 | # OpenGL 76 | #---------------------------------------------------------------------- 77 | # using the system bundled 78 | #---------------------------------------------------------------------- 79 | find_package(OpenGL REQUIRED) 80 | message(STATUS "OPENGL_LIBRARIES: ${OPENGL_LIBRARIES}") 81 | 82 | 83 | #---------------------------------------------------------------------- 84 | # Str 85 | #---------------------------------------------------------------------- 86 | add_library(str INTERFACE 87 | ${CMAKE_SOURCE_DIR}/deps/str/Str.h 88 | ) 89 | target_include_directories(str INTERFACE ${CMAKE_SOURCE_DIR}/deps) 90 | 91 | 92 | #---------------------------------------------------------------------- 93 | # Googletest 94 | #---------------------------------------------------------------------- 95 | if(KANTU_TESTING) 96 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/googletest") 97 | set(GTest_DIR "${CMAKE_SOURCE_DIR}/deps/googletest/install/lib/cmake/GTest") 98 | else() 99 | #set(GTest_DIR "$ENV{HOME}/.sled/artifacts/googletest/release-1.11.0/lib/cmake/GTest/") 100 | endif() 101 | message(STATUS "GTest_DIR: ${GTest_DIR}") 102 | find_package(GTest REQUIRED) 103 | endif() 104 | 105 | 106 | #---------------------------------------------------------------------- 107 | # MLCC 108 | #---------------------------------------------------------------------- 109 | # Not using mlcc currently. Maybe one day I'll use it again. 110 | add_library(mlcc INTERFACE 111 | ${CMAKE_SOURCE_DIR}/deps/mlcc/fmt1.h 112 | ) 113 | target_include_directories(mlcc INTERFACE ${CMAKE_SOURCE_DIR}/deps) 114 | 115 | 116 | #---------------------------------------------------------------------- 117 | # fmt 118 | #---------------------------------------------------------------------- 119 | if(EXISTS "${CMAKE_SOURCE_DIR}/deps/fmt") 120 | set(fmt_src_dir "${CMAKE_SOURCE_DIR}/deps/fmt") 121 | else() 122 | # use your one 123 | endif() 124 | add_subdirectory(${fmt_src_dir} ${CMAKE_BINARY_DIR}/fmt EXCLUDE_FROM_ALL) -------------------------------------------------------------------------------- /cmake/lldb-debug.cmake: -------------------------------------------------------------------------------- 1 | # last update: 2023/04/22 2 | 3 | if((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) 4 | set(lldb_required_debug_flag -g -fstandalone-debug) 5 | add_compile_options(${lldb_required_debug_flag}) 6 | add_link_options(${lldb_required_debug_flag}) 7 | message(STATUS ">>> USE_LLDB_DEBUG: YES") 8 | else() 9 | message(STATUS ">>> USE_LLDB_DEBUG: NO") 10 | endif() 11 | -------------------------------------------------------------------------------- /cmake/ninja_colorful_output.cmake: -------------------------------------------------------------------------------- 1 | # author: ChrisZZ 2 | # last update: 2023.04.30 16:36:00 3 | 4 | option(NINJA_COLORFUL_OUTPUT "Always produce ANSI-colored output (GNU/Clang only)." ON) 5 | 6 | if(${NINJA_COLORFUL_OUTPUT}) 7 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 8 | set(NINJA_COLORFUL_OUTPUT_OPTION -fdiagnostics-color=always) 9 | elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 10 | set(NINJA_COLORFUL_OUTPUT_OPTION -fcolor-diagnostics) 11 | else() 12 | message(WARNING "Ninja colorful output not handled or not supported for current compiler") 13 | return() 14 | endif() 15 | 16 | message(STATUS ">>> NINJA_COLORFUL_OUTPUT: YES") 17 | add_compile_options(${NINJA_COLORFUL_OUTPUT_OPTION}) 18 | endif() 19 | -------------------------------------------------------------------------------- /cmake/output_dir.cmake: -------------------------------------------------------------------------------- 1 | # Save libs and executables in the same place 2 | if(NOT EXECUTABLE_OUTPUT_PATH) 3 | set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}" CACHE PATH "Output directory for applications") 4 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} CACHE INTERNAL "") # static 5 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} CACHE INTERNAL "") # shared 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH} CACHE INTERNAL "") # exe 7 | endif() -------------------------------------------------------------------------------- /cmake/overlook.cmake: -------------------------------------------------------------------------------- 1 | ############################################################### 2 | # 3 | # OverLook: Amplify C/C++ warnings that shouldn't be ignored. 4 | # 5 | # Author: Zhuo Zhang 6 | # Homepage: https://github.com/zchrissirhcz/overlook 7 | # 8 | ############################################################### 9 | 10 | cmake_minimum_required(VERSION 3.1) 11 | 12 | # Only included once 13 | if(OVERLOOK_INCLUDE_GUARD) 14 | return() 15 | endif() 16 | set(OVERLOOK_INCLUDE_GUARD TRUE) 17 | 18 | set(OVERLOOK_VERSION "2023.02.08") 19 | 20 | option(OVERLOOK_FLAGS_GLOBAL "use safe compilation flags?" ON) 21 | option(OVERLOOK_STRICT_FLAGS "strict c/c++ flags checking?" ON) 22 | option(OVERLOOK_USE_CPPCHECK "use cppcheck for static checking?" OFF) 23 | option(OVERLOOK_VERBOSE "verbose output?" OFF) 24 | 25 | set(OVERLOOK_C_FLAGS "") 26 | set(OVERLOOK_CXX_FLAGS "") 27 | 28 | # Append element to list with space as seperator 29 | function(overlook_list_append __string __element) 30 | # set(__list ${${__string}}) 31 | # set(__list "${__list} ${__element}") 32 | # set(${__string} ${__list} PARENT_SCOPE) 33 | #set(__list ${${__string}}) 34 | set(${__string} "${${__string}} ${__element}" PARENT_SCOPE) 35 | endfunction() 36 | 37 | # Print overlook information 38 | message(STATUS "------------------------------------------------------------") 39 | message(STATUS " Using overlook, the CMake plugin for safer C/C++ code") 40 | message(STATUS " Author: Zhuo Zhang (imzhuo@foxmail.com)") 41 | message(STATUS " OVERLOOK_VERSION: ${OVERLOOK_VERSION}") 42 | message(STATUS "------------------------------------------------------------") 43 | 44 | # If `-w` specified for GCC/Clang, report an error 45 | if((CMAKE_C_COMPILER_ID MATCHES "GNU") OR (CMAKE_C_COMPILER_ID MATCHES "Clang")) 46 | get_directory_property(overlook_detected_global_compile_options COMPILE_OPTIONS) 47 | message(STATUS "Overlook Detected Global Compile Options: ${overlook_detected_global_compile_options}") 48 | string(REGEX MATCH "-w" ignore_all_warnings "${overlook_detected_global_compile_options}" ) 49 | if(ignore_all_warnings) 50 | message(FATAL_ERROR "!! You have `-w` compile option specified, which mean ignore all warnings, thus Overlook won't work.\n!! Please remove it (in `add_compile_options)`!") 51 | endif() 52 | endif() 53 | 54 | # 检查编译器版本。同一编译器的不同版本,编译选项的支持情况可能不同。 55 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CLANG_VERSION_STRING) 56 | message(STATUS "--- CLANG_VERSION_MAJOR is: ${CLANG_VERSION_MAJOR}") 57 | message(STATUS "--- CLANG_VERSION_MINOR is: ${CLANG_VERSION_MINOR}") 58 | message(STATUS "--- CLANG_VERSION_PATCHLEVEL is: ${CLANG_VERSION_PATCHLEVEL}") 59 | message(STATUS "--- CLANG_VERSION_STRING is: ${CLANG_VERSION_STRING}") 60 | endif() 61 | 62 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 63 | message(STATUS "--- CMAKE_CXX_COMPILER_VERSION is: ${CMAKE_CXX_COMPILER_VERSION}") 64 | # if(CMAKE_CXX_COMPILER_VERSION GREATER 9.1) # when >= 9.2, not support this option 65 | # message(STATUS "---- DEBUG INFO HERE !!!") 66 | # endif() 67 | endif() 68 | 69 | if(CMAKE_C_COMPILER_ID) 70 | set(OVERLOOK_WITH_C TRUE) 71 | else() 72 | set(OVERLOOK_WITH_C FALSE) 73 | endif() 74 | 75 | if(CMAKE_CXX_COMPILER_ID) 76 | set(OVERLOOK_WITH_CXX TRUE) 77 | else() 78 | set(OVERLOOK_WITH_CXX FALSE) 79 | endif() 80 | 81 | # Project LANGUAGE not including C and CXX so we return 82 | if((NOT OVERLOOK_WITH_C) AND (NOT OVERLOOK_WITH_CXX)) 83 | message("OverLook WARNING: neither C nor CXX compilers available. No OVERLOOK C/C++ flags will be set") 84 | message(" NOTE: You many consider add C and CXX in `project()` command") 85 | return() 86 | endif() 87 | 88 | # Rule 1. 函数没有声明就使用 89 | # 解决bug:地址截断;内存泄漏 90 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 91 | overlook_list_append(OVERLOOK_C_FLAGS /we4013) 92 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4013) 93 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 94 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=implicit-function-declaration) 95 | if(CMAKE_CXX_COMPILER_VERSION LESS 9.1) 96 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=implicit-function-declaration) 97 | endif() 98 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 99 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=implicit-function-declaration) 100 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=implicit-function-declaration) 101 | endif() 102 | 103 | # Rule 2. 函数虽然有声明,但是声明不完整,没有写出返回值类型 104 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 105 | overlook_list_append(OVERLOOK_C_FLAGS /we4431) 106 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4431) 107 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 108 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=implicit-int) 109 | if(CMAKE_CXX_COMPILER_VERSION LESS 9.1) 110 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=implicit-int) 111 | endif() 112 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 113 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=implicit-int) 114 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=implicit-int) 115 | endif() 116 | 117 | # Rule 3. 指针类型不兼容 118 | # 解决bug:crash或结果异常 119 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 120 | overlook_list_append(OVERLOOK_C_FLAGS /we4133) 121 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4133) 122 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 123 | if(CMAKE_CXX_COMPILER_VERSION GREATER 4.8) # gcc/g++ 4.8.3 not ok 124 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=incompatible-pointer-types) 125 | if(CMAKE_CXX_COMPILER_VERSION LESS 9.1) 126 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=incompatible-pointer-types) 127 | endif() 128 | endif() 129 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 130 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=incompatible-pointer-types) 131 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=incompatible-pointer-types) 132 | endif() 133 | 134 | # Rule 4. 函数应该有返回值但没有 return 返回值;或不是所有路径都有返回值 135 | # 解决bug:lane detect; vpdt for循环无法跳出(android输出trap); lane calib库读取到随机值导致获取非法格式asvl,开asan则表现为读取NULL指针 136 | # -O3时输出内容和其他优化等级不一样(from 三老师) 137 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 138 | overlook_list_append(OVERLOOK_C_FLAGS /we4716 /we4715) 139 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4716 /we4715) 140 | else() 141 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=return-type) 142 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=return-type) 143 | endif() 144 | 145 | # Rule 5. 避免使用影子(shadow)变量 146 | # 有时候会误伤,例如eigen等开源项目,可以手动关掉 147 | if(0) 148 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 149 | overlook_list_append(OVERLOOK_C_FLAGS /we6244 /we6246 /we4457 /we4456) 150 | overlook_list_append(OVERLOOK_CXX_FLAGS /we6244 /we6246 /we4457 /we4456) 151 | else() 152 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=shadow) 153 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=shadow) 154 | endif() 155 | endif() 156 | 157 | # Rule 6. 函数不应该返回局部变量的地址 158 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 159 | overlook_list_append(OVERLOOK_C_FLAGS /we4172) 160 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4172) 161 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 162 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=return-local-addr) 163 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=return-local-addr) 164 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 165 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=return-stack-address) 166 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=return-stack-address) 167 | endif() 168 | 169 | # Rule 7. 变量没初始化就使用,要避免 170 | if(0) 171 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 172 | overlook_list_append(OVERLOOK_C_FLAGS "/we4700 /we26495") 173 | overlook_list_append(OVERLOOK_CXX_FLAGS "/we4700 /we26495") 174 | else() 175 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=uninitialized) 176 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=uninitialized) 177 | endif() 178 | endif() 179 | 180 | # Rule 8. printf 等语句中的格式串和实参类型不匹配,要避免 181 | if(0) 182 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 183 | overlook_list_append(OVERLOOK_C_FLAGS /we4477) 184 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4477) 185 | else() 186 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=format) 187 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=format) 188 | endif() 189 | endif() 190 | 191 | # Rule 9. 避免把 unsigned int 和 int 直接比较 192 | # 通常会误伤,例如 for 循环中。可以考虑关掉 193 | if(OVERLOOK_STRICT_FLAGS) 194 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 195 | overlook_list_append(OVERLOOK_C_FLAGS /we4018) 196 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4018) 197 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 198 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=sign-compare) 199 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=sign-compare) 200 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 201 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=sign-compare) 202 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=sign-compare) 203 | endif() 204 | endif() 205 | 206 | # Rule 10. 避免把 int 指针赋值给 int 类型变量 207 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 208 | overlook_list_append(OVERLOOK_C_FLAGS /we4047) 209 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4047) 210 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 211 | if(CMAKE_CXX_COMPILER_VERSION GREATER 4.8) 212 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=int-conversion) 213 | if(CMAKE_CXX_COMPILER_VERSION LESS 9.1) 214 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=int-conversion) 215 | endif() 216 | endif() 217 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 218 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=int-conversion) 219 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=int-conversion) 220 | endif() 221 | 222 | # Rule 11. 检查数组下标越界访问 223 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 224 | overlook_list_append(OVERLOOK_C_FLAGS "/we6201 /we6386 /we4789") 225 | overlook_list_append(OVERLOOK_CXX_FLAGS "/we6201 /we6386 /we4789") 226 | else() 227 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=array-bounds) 228 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=array-bounds) 229 | endif() 230 | 231 | # Rule 12. 函数声明中的参数列表和定义中不一样。在 MSVC C 下为警告,Linux Clang 下报错 232 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 233 | overlook_list_append(OVERLOOK_C_FLAGS /we4029) 234 | endif() 235 | 236 | # Rule 13. 实参太多,比函数定义或声明中的要多。只在MSVC C 下为警告,Linux Clang下报错 237 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 238 | overlook_list_append(OVERLOOK_C_FLAGS /we4020) 239 | endif() 240 | 241 | # 14. 避免 void* 类型的指针参参与算术运算 242 | # MSVC C/C++ 默认会报错,Linux gcc 不报 warning 和 error,Linux g++ 只报 warning 243 | # Linux 下 Clang 开 -Wpedentric 才报 warning,Clang++ 报 error 244 | if(CMAKE_C_COMPILER_ID MATCHES "GNU") 245 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=pointer-arith) 246 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=pointer-arith) 247 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 248 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=pointer-arith) 249 | endif() 250 | 251 | # Rule 15. 避免符号重复定义(变量对应的强弱符号)。只在 C 中出现。 252 | # 暂时没找到 MSVC 的对应编译选项 253 | if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") 254 | overlook_list_append(OVERLOOK_C_FLAGS -fno-common) 255 | endif() 256 | 257 | # Rule 16. Windows下,源码已经是 UTF-8 编码,但输出中文到 stdout 时 258 | # 要么编译报错,要么乱码。解决办法是编译输出为 GBK 格式 259 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 260 | overlook_list_append(OVERLOOK_C_FLAGS "/source-charset:utf-8 /execution-charset:gbk") 261 | overlook_list_append(OVERLOOK_CXX_FLAGS "/source-charset:utf-8 /execution-charset:gbk") 262 | endif() 263 | 264 | # Rule 17. 释放非堆内存 265 | # TODO: 检查 MSVC 266 | # Linux Clang8.0 无法检测到 267 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 268 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=free-nonheap-object) 269 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=free-nonheap-object) 270 | endif() 271 | 272 | # Rule 18. 形参与声明不同。场景:静态库(.h/.c),集成时换库但没换头文件,且函数形参有变化(类型或数量) 273 | # 只报 warning 不报 error。仅 VS 出现 274 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 275 | overlook_list_append(OVERLOOK_C_FLAGS /we4028) 276 | endif() 277 | 278 | # Rule 19. 宏定义重复 279 | # gcc5~gcc9 无法检查 280 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 281 | overlook_list_append(OVERLOOK_C_FLAGS /we4005) 282 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4005) 283 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 284 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=macro-redefined) 285 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=macro-redefined) 286 | endif() 287 | 288 | # Rule 20. pragma init_seg 指定了非法(不能识别的)section名字 289 | # VC++ 特有。Linux 下的 gcc/clang 没有 290 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 291 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4075) 292 | endif() 293 | 294 | # Rule 21. size_t 类型被转为更窄类型 295 | # VC/VC++ 特有。 Linux 下的 gcc/clang 没有 296 | # 有点过于严格了 297 | if(OVERLOOK_STRICT_FLAGS) 298 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 299 | overlook_list_append(OVERLOOK_C_FLAGS /we4267) 300 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4267) 301 | endif() 302 | endif() 303 | 304 | # Rule 22. “类型强制转换”: 例如从 int 转换到更大的 void * 305 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 306 | overlook_list_append(OVERLOOK_C_FLAGS /we4312) 307 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4312) 308 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 309 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=int-to-pointer-cast) 310 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=int-to-pointer-cast) 311 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 312 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=int-to-pointer-cast) 313 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=int-to-pointer-cast) 314 | endif() 315 | 316 | # Rule 23. 不可识别的字符转义序列 317 | # GCC5.4 能显示 warning 但无别名,因而无法视为 error 318 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 319 | overlook_list_append(OVERLOOK_C_FLAGS /we4129) 320 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4129) 321 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 322 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=unknown-escape-sequence) 323 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=unknown-escape-sequence) 324 | endif() 325 | 326 | # Rule 24. 类函数宏的调用 参数过多 327 | # VC/VC++ 报警告。Linux 下的 GCC/Clang 报 error 328 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 329 | overlook_list_append(OVERLOOK_C_FLAGS /we4002) 330 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4002) 331 | endif() 332 | 333 | # Rule 25. 类函数宏的调用 参数不足 334 | # VC/VC++ 同时会报 error C2059 335 | # Linux GCC/Clang 直接报错 336 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 337 | overlook_list_append(OVERLOOK_C_FLAGS /we4003) 338 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4003) 339 | endif() 340 | 341 | # Rule 26. #undef 没有跟一个标识符 342 | # Linux GCC/Clang 直接报错 343 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 344 | overlook_list_append(OVERLOOK_C_FLAGS /we4006) 345 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4006) 346 | endif() 347 | 348 | # Rule 27. 单行注释包含行继续符 349 | # 可能会导致下一行代码报错,而问题根源在包含继续符的这行注释 350 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 351 | overlook_list_append(OVERLOOK_C_FLAGS /we4006) 352 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4006) 353 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 354 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=comment) 355 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=comment) 356 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 357 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=comment) 358 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=comment) 359 | endif() 360 | 361 | # Rule 28. 没有使用到表达式结果(无用代码行,应删除) 362 | # 感觉容易被误伤,可以考虑关掉 363 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 364 | overlook_list_append(OVERLOOK_C_FLAGS "/we4552 /we4555") 365 | overlook_list_append(OVERLOOK_CXX_FLAGS "/we4552 /we4555") 366 | elseif(CMAKE_C_COMPILER_ID MATCHES "GNU") 367 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=unused-value) 368 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=unused-value) 369 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 370 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=unused-value) 371 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=unused-value) 372 | endif() 373 | 374 | # Rule 29. “==”: 未使用表达式结果;是否打算使用“=”? 375 | # Linux GCC 没有对应的编译选项 376 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 377 | overlook_list_append(OVERLOOK_C_FLAGS /we4553) 378 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4553) 379 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 380 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=unused-comparison) 381 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=unused-comparison) 382 | endif() 383 | 384 | # Rule 30. C++中,禁止把字符串常量赋值给 char* 变量 385 | # VS2019 开启 /Wall 后也查不到 386 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 387 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=write-strings) 388 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 389 | # Linux Clang 和 AppleClang 不太一样 390 | if(CMAKE_SYSTEM_NAME MATCHES "Linux") 391 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=writable-strings) 392 | elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") 393 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=c++11-compat-deprecated-writable-strings) 394 | endif() 395 | endif() 396 | 397 | # Rule 31. 所有的控件路径(if/else)必须都有返回值 398 | # NDK21 Clang / Linux Clang/GCC/G++ 默认都报 error 399 | if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 400 | overlook_list_append(OVERLOOK_C_FLAGS /we4715) 401 | overlook_list_append(OVERLOOK_CXX_FLAGS /we4715) 402 | endif() 403 | 404 | # Rule 32. multi-char constant 405 | # MSVC 没有对应的选项 406 | if(CMAKE_C_COMPILER_ID MATCHES "GNU") 407 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=multichar) 408 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=multichar) 409 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 410 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=multichar) 411 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=multichar) 412 | endif() 413 | 414 | # Rule 33. 用 memset 等 C 函数设置 非 POD class 对象 415 | # Linux下,GCC9.3 能发现此问题,但clang10 不能发现 416 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 417 | if(CMAKE_CXX_COMPILER_VERSION GREATER 7.5) 418 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=class-memaccess) 419 | endif() 420 | endif() 421 | 422 | ## Rule 34. 括号里面是单个等号而不是双等号 423 | # Linux下, Clang14 可以发现问题,但 GCC9.3 无法发现;android clang 可以发现 424 | if(CMAKE_C_COMPILER_ID MATCHES "Clang") 425 | overlook_list_append(OVERLOOK_C_FLAGS -Werror=parentheses) 426 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=parentheses) 427 | endif() 428 | 429 | ## Rule 35. double 型转 float 型,可能有精度丢失(尤其在 float 较大时) 430 | # MSVC 默认是放在 /W3 431 | # if(OVERLOOK_STRICT_FLAGS) 432 | # if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") 433 | # overlook_list_append(OVERLOOK_C_FLAGS /we4244) 434 | # overlook_list_append(OVERLOOK_CXX_FLAGS /we4244) 435 | # endif() 436 | # endif() 437 | 438 | ## Rule 36. 父类有 virtual 的成员函数,但析构函数是 public 并且不是 virtual,会导致 UB 439 | # https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c35-a-base-class-destructor-should-be-either-public-and-virtual-or-protected-and-non-virtual 440 | # -Wnon-virtual-dtor (C++ and Objective-C++ only) 441 | # Warn when a class has virtual functions and an accessible non-virtual destructor itself or in an accessible polymorphic base 442 | # class, in which case it is possible but unsafe to delete an instance of a derived class through a pointer to the class 443 | # itself or base class. This warning is automatically enabled if -Weffc++ is specified. 444 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 445 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=non-virtual-dtor) 446 | elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") 447 | overlook_list_append(OVERLOOK_CXX_FLAGS -Werror=non-virtual-dtor) 448 | endif() 449 | 450 | # 将上述定制的 FLAGS 追加到 CMAKE 默认的编译选项中 451 | # 为什么是添加而不是直接设定呢?因为 xxx-toolchain.cmake 中可能会设置一些默认值 (如 Android NDK), 需要避免这些默认值被覆盖 452 | if(OVERLOOK_FLAGS_GLOBAL) 453 | overlook_list_append(CMAKE_C_FLAGS "${OVERLOOK_C_FLAGS}") 454 | overlook_list_append(CMAKE_CXX_FLAGS "${OVERLOOK_CXX_FLAGS}") 455 | 456 | overlook_list_append(CMAKE_C_FLAGS_DEBUG "${OVERLOOK_C_FLAGS}") 457 | overlook_list_append(CMAKE_CXX_FLAGS_DEBUG "${OVERLOOK_CXX_FLAGS}") 458 | 459 | overlook_list_append(CMAKE_C_FLAGS_RELEASE "${OVERLOOK_C_FLAGS}") 460 | overlook_list_append(CMAKE_CXX_FLAGS_RELEASE "${OVERLOOK_CXX_FLAGS}") 461 | 462 | overlook_list_append(CMAKE_C_FLAGS_MINSIZEREL "${OVERLOOK_C_FLAGS}") 463 | overlook_list_append(CMAKE_CXX_FLAGS_MINSIZEREL "${OVERLOOK_CXX_FLAGS}") 464 | 465 | overlook_list_append(CMAKE_C_FLAGS_RELWITHDEBINFO "${OVERLOOK_C_FLAGS}") 466 | overlook_list_append(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${OVERLOOK_CXX_FLAGS}") 467 | endif() 468 | 469 | if(OVERLOOK_VERBOSE) 470 | message(STATUS "--- OVERLOOK_C_FLAGS are: ${OVERLOOK_C_FLAGS}") 471 | message(STATUS "--- OVERLOOK_CXX_FLAGS are: ${OVERLOOK_CXX_FLAGS}") 472 | endif() 473 | 474 | 475 | ################################################################################## 476 | # Add whole archive when build static library 477 | # Usage: 478 | # overlook_add_whole_archive_flag( ) 479 | # Example: 480 | # add_library(foo foo.hpp foo.cpp) 481 | # add_executable(bar bar.cpp) 482 | # overlook_add_whole_archive_flag(foo safe_foo) 483 | # target_link_libraries(bar ${safe_foo}) 484 | ################################################################################## 485 | function(overlook_add_whole_archive_flag lib output_var) 486 | if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") 487 | if(MSVC_VERSION GREATER 1900) 488 | set(${output_var} -WHOLEARCHIVE:$ PARENT_SCOPE) 489 | else() 490 | message(WARNING "MSVC version is ${MSVC_VERSION}, /WHOLEARCHIVE flag cannot be set") 491 | set(${output_var} ${lib} PARENT_SCOPE) 492 | endif() 493 | elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") 494 | set(${output_var} -Wl,--whole-archive ${lib} -Wl,--no-whole-archive PARENT_SCOPE) 495 | elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND CMAKE_SYSTEM_NAME MATCHES "Linux") 496 | set(${output_var} -Wl,--whole-archive ${lib} -Wl,--no-whole-archive PARENT_SCOPE) 497 | elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" AND NOT ANDROID) 498 | set(${output_var} -Wl,-force_load ${lib} PARENT_SCOPE) 499 | elseif(ANDROID) 500 | #即使是 NDK21 并且手动传入 ANDROID_LD=lld,依然要用ld的查重复符号的链接选项 501 | set(${output_var} -Wl,--whole-archive ${lib} -Wl,--no-whole-archive PARENT_SCOPE) 502 | else() 503 | message(FATAL_ERROR ">>> add_whole_archive_flag not supported yet for current compiler: ${CMAKE_CXX_COMPILER_ID}") 504 | endif() 505 | endfunction() 506 | 507 | 508 | ############################################################### 509 | # 510 | # cppcheck,开启静态代码检查,主要是检查编译器检测不到的UB 511 | # 注: 目前只有终端下能看到对应输出,其中NDK下仅第一次输出 512 | # 513 | ############################################################### 514 | if(OVERLOOK_USE_CPPCHECK) 515 | find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) 516 | if(CMAKE_CXX_CPPCHECK) 517 | message(STATUS "cppcheck found") 518 | list(APPEND CMAKE_CXX_CPPCHECK 519 | "--enable=warning" 520 | "--inconclusive" 521 | "--force" 522 | "--inline-suppr" 523 | ) 524 | else() 525 | message(STATUS "cppcheck not found. ignore it") 526 | endif() 527 | endif() 528 | -------------------------------------------------------------------------------- /cmake/summary.cmake: -------------------------------------------------------------------------------- 1 | message(STATUS "================================================================================") 2 | message(STATUS " CMake Configure Summary ") 3 | message(STATUS "--------------------------------------------------------------------------------") 4 | message(STATUS " Author: Zhuo Zhang (imzhuo#foxmail.com)") 5 | message(STATUS " Create: 2023.02.15") 6 | message(STATUS " Modified: 2023.05.08") 7 | message(STATUS " Usage: include(summary.cmake) # put in bottom of Root CMakeLists.txt") 8 | message(STATUS "================================================================================") 9 | 10 | #------------------------------ 11 | # CMake information 12 | #------------------------------ 13 | message(STATUS "CMake information:") 14 | message(STATUS " - CMake version: ${CMAKE_VERSION}") 15 | message(STATUS " - CMake generator: ${CMAKE_GENERATOR}") 16 | message(STATUS " - CMake building tools: ${CMAKE_BUILD_TOOL}") 17 | message(STATUS " - Target System: ${CMAKE_SYSTEM_NAME}") 18 | message(STATUS "") 19 | 20 | #------------------------------ 21 | # C/C++ Compiler information 22 | #------------------------------ 23 | message(STATUS "Toolchain information:") 24 | message(STATUS " Cross compiling: ${CMAKE_CROSSCOMPILING}") 25 | message(STATUS " C/C++ compiler:") 26 | message(STATUS " - C standard version: C${CMAKE_C_STANDARD}") 27 | message(STATUS " - C standard required: ${CMAKE_C_STANDARD_REQUIRED}") 28 | message(STATUS " - C standard extensions: ${CMAKE_C_EXTENSIONS}") 29 | message(STATUS " - C compiler version: ${CMAKE_C_COMPILER_VERSION}") 30 | message(STATUS " - C compiler: ${CMAKE_C_COMPILER}") 31 | message(STATUS " - C++ standard version: C++${CMAKE_CXX_STANDARD}") 32 | message(STATUS " - C++ standard required: ${CMAKE_CXX_STANDARD_REQUIRED}") 33 | message(STATUS " - C++ standard extensions: ${CMAKE_CXX_EXTENSIONS}") 34 | message(STATUS " - C++ compiler version: ${CMAKE_CXX_COMPILER_VERSION}") 35 | message(STATUS " - C++ compiler: ${CMAKE_CXX_COMPILER}") 36 | message(STATUS "") 37 | 38 | #------------------------------ 39 | # C/C++ Compilation Information 40 | #------------------------------ 41 | string(TOUPPER "${CMAKE_BUILD_TYPE}" capitalized_build_type) 42 | 43 | message(STATUS "C/C++ Compilation information") 44 | message(STATUS " - CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 45 | message(STATUS " - CONFIG: ${capitalized_build_type}") 46 | message(STATUS " - CMAKE_CXX_FLAGS(for all build types): ${CMAKE_CXX_FLAGS}") 47 | set(summary_cxx_flags "${CMAKE_CXX_FLAGS}") 48 | 49 | if(NOT (CMAKE_BUILD_TYPE EQUAL "None" OR NOT CMAKE_BUILD_TYPE)) 50 | message(STATUS " - CMAKE_CXX_FLAGS_: CMAKE_CXX_FLAGS_${capitalized_build_type} : ${CMAKE_CXX_FLAGS_${capitalized_build_type}}") 51 | set(summary_cxx_flags "${summary_cxx_flags} ${CMAKE_CXX_FLAGS_${capitalized_build_type}}") 52 | endif() 53 | 54 | # e.g. -Werror=return-type 55 | get_directory_property(summary_detected_global_compile_options COMPILE_OPTIONS) 56 | message(STATUS " - Global Compile Options(via `add_compile_options()`): ${summary_detected_global_compile_options}") 57 | set(summary_cxx_flags "${summary_cxx_flags} ${summary_detected_global_compile_options}") 58 | 59 | # e.g. -I/some/dir 60 | get_directory_property(summary_detected_global_include_directories INCLUDE_DIRECTORIES) 61 | set(styled_global_include_directories "") 62 | foreach(include_directory ${summary_detected_global_include_directories}) 63 | set(styled_global_include_directories "-I${include_directory} ${styled_global_include_directories}") 64 | endforeach() 65 | set(summary_cxx_flags "${styled_global_include_directories} ${summary_cxx_flags}") 66 | message(STATUS " - Global Include Directories(via `include_directories()`): ${summary_detected_global_include_directories}") 67 | 68 | # e.g. -Dfoo=123 69 | get_directory_property(summary_detected_global_compile_definitions COMPILE_DEFINITIONS) 70 | set(styled_global_compile_definitions "") 71 | foreach(compile_definition ${summary_detected_global_compile_definitions}) 72 | #message("-D${compile_definition}") 73 | set(styled_global_compile_definitions "-D${compile_definition} ${styled_global_compile_definitions}") 74 | endforeach() 75 | set(summary_cxx_flags "${styled_global_compile_definitions} ${summary_cxx_flags}") 76 | message(STATUS " - Global Compile Definitions: ${styled_global_compile_definitions}") 77 | message(STATUS " (via `add_compile_definitions()`, `add_definitions()`)") 78 | 79 | message(STATUS " - Final CXX FLAGS: ${summary_cxx_flags}") 80 | message(STATUS " (which consists of {add_compile_definitions(), CMAKE_CXX_FLAGS, CMAKE_CXX_FLAGS_, add_compile_options()}") 81 | message(STATUS " See ${CMAKE_BINARY_DIR}/compile_commands.json for full details") 82 | message(STATUS "") 83 | 84 | #------------------------------ 85 | # C/C++ Linking Information 86 | #------------------------------ 87 | message(STATUS "C/C++ Linking information") 88 | # e.g. -L/some/dir 89 | get_directory_property(summary_detected_global_link_directories LINK_DIRECTORIES) 90 | message(STATUS " - Global Link Directories(via `link_directories()`): ${summary_detected_global_link_directories}") 91 | 92 | # e.g. -llibname 93 | get_directory_property(summary_detected_global_link_options LINK_OPTIONS) 94 | message(STATUS " - Global Link Options(via `add_link_options()`): ${summary_detected_global_link_options}") 95 | 96 | message(STATUS " See ${CMAKE_BINARY_DIR}/CMakeFiles/\${target_name}.dir/link.txt) for full details") 97 | message(STATUS "") 98 | 99 | #------------------------------ 100 | # Target list 101 | #------------------------------ 102 | function(summary_get_all_targets var) 103 | set(targets) 104 | summary_get_all_targets_recursive(targets ${CMAKE_CURRENT_SOURCE_DIR}) 105 | set(${var} ${targets} PARENT_SCOPE) 106 | endfunction() 107 | 108 | macro(summary_get_all_targets_recursive targets dir) 109 | get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES) 110 | foreach(subdir ${subdirectories}) 111 | summary_get_all_targets_recursive(${targets} ${subdir}) 112 | endforeach() 113 | 114 | get_property(current_targets DIRECTORY ${dir} PROPERTY BUILDSYSTEM_TARGETS) 115 | list(APPEND ${targets} ${current_targets}) 116 | endmacro() 117 | 118 | summary_get_all_targets(all_targets) 119 | message(STATUS "List of targets (name, type, link command file):") 120 | foreach(target_name ${all_targets}) 121 | get_target_property(target_type ${target_name} TYPE) 122 | message(STATUS " ${target_name}") 123 | message(STATUS " - target type: ${target_type}") 124 | 125 | get_property(tgt_binary_dir TARGET ${target_name} PROPERTY BINARY_DIR) 126 | message(STATUS " - binary dir: ${tgt_binary_dir}") 127 | 128 | if(${CMAKE_GENERATOR} STREQUAL "Unix Makefiles") 129 | message(STATUS " - link command: ${tgt_binary_dir}/CMakeFiles/${target_name}.dir/link.txt") 130 | elseif(${CMAKE_GENERATOR} STREQUAL "Ninja") 131 | message(STATUS " - link command: ${CMAKE_BINARY_DIR}/build.ninja") 132 | endif() 133 | 134 | get_property(tgt_compile_flags TARGET ${target_name} PROPERTY COMPILE_FLAGS) 135 | if(tgt_compile_flags) 136 | message(STATUS " - compile flags: ${tgt_compile_flags}") 137 | endif() 138 | 139 | get_property(tgt_compile_options TARGET ${target_name} PROPERTY COMPILE_OPTIONS) 140 | if(tgt_compile_options) 141 | message(STATUS " - compile options: ${tgt_compile_options}") 142 | endif() 143 | 144 | get_property(tgt_compile_definitions TARGET ${target_name} PROPERTY COMPILE_DEFINITIONS) 145 | if(tgt_compile_definitions) 146 | message(STATUS " - compile definitions: ${tgt_compile_definitions}") 147 | endif() 148 | 149 | get_property(tgt_link_options TARGET ${target_name} PROPERTY LINK_OPTIONS) 150 | if(tgt_link_options) 151 | message(STATUS " - link options: ${tgt_link_options}") 152 | endif() 153 | 154 | get_property(tgt_link_flags TARGET ${target_name} PROPERTY LINK_FLAGS) 155 | if(tgt_link_flags) 156 | message(STATUS " - link flags: ${tgt_link_flags}") 157 | endif() 158 | endforeach() 159 | message(STATUS "") 160 | 161 | 162 | #------------------------------ 163 | # Misc stuffs 164 | #------------------------------ 165 | message(STATUS "Other information:") 166 | # show building install path 167 | message(STATUS " Package install path: ${CMAKE_INSTALL_PREFIX}") 168 | message(STATUS "") 169 | 170 | message(STATUS " OpenMP:") 171 | if(OpenMP_FOUND) 172 | message(STATUS " - OpenMP was found: YES") 173 | message(STATUS " - OpenMP version: ${OpenMP_C_VERSION}") 174 | else() 175 | message(STATUS " - OpenMP was found: NO") 176 | endif() 177 | message(STATUS "") 178 | 179 | -------------------------------------------------------------------------------- /cmake/tsan.cmake: -------------------------------------------------------------------------------- 1 | # last update: 2022/05/02 2 | 3 | option(USE_TSAN "Use Address Sanitizer?" ON) 4 | #-------------------------------------------------- 5 | # globally setting 6 | #-------------------------------------------------- 7 | # https://stackoverflow.com/a/65019152/2999096 8 | # https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-170#enable-addresssanitizer-for-windows-and-linux 9 | if(USE_TSAN) 10 | if((CMAKE_C_COMPILER_ID STREQUAL "MSVC") OR (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 11 | OR ((MSVC AND ((CMAKE_C_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))))) 12 | message(FATAL_ERROR "Neither MSVC nor Clang-CL support thread sanitizer") 13 | elseif((CMAKE_C_COMPILER_ID MATCHES "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 14 | OR (CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) 15 | # Note: `-g` is explicitly required here for debug symbol information such line number and file name 16 | # There is NDK's android.toolchain.cmake's `-g`, in case user removed it, explicitly add `-g` here. 17 | set(TSAN_FLAGS "-g -fno-omit-frame-pointer -fsanitize=thread") 18 | endif() 19 | 20 | message(STATUS ">>> USE_TSAN: YES") 21 | message(STATUS ">>> CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 22 | if(CMAKE_BUILD_TYPE MATCHES "Debug") 23 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${TSAN_FLAGS}") 24 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TSAN_FLAGS}") 25 | set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} ${TSAN_FLAGS}") 26 | elseif(CMAKE_BUILD_TYPE MATCHES "Release") 27 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${TSAN_FLAGS}") 28 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${TSAN_FLAGS}") 29 | set(CMAKE_LINKER_FLAGS_RELEASE "${CMAKE_LINKER_FLAGS_RELEASE} ${TSAN_FLAGS}") 30 | elseif(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo") 31 | set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} ${TSAN_FLAGS}") 32 | set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} ${TSAN_FLAGS}") 33 | set(CMAKE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_LINKER_FLAGS_RELWITHDEBINFO} ${TSAN_FLAGS}") 34 | elseif(CMAKE_BUILD_TYPE MATCHES "MinSizeRel") 35 | set(CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} ${TSAN_FLAGS}") 36 | set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} ${TSAN_FLAGS}") 37 | set(CMAKE_LINKER_FLAGS_MINSIZEREL "${CMAKE_LINKER_FLAGS_MINSIZEREL} ${TSAN_FLAGS}") 38 | elseif(CMAKE_BUILD_TYPE EQUAL "None" OR NOT CMAKE_BUILD_TYPE) 39 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${TSAN_FLAGS}") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TSAN_FLAGS}") 41 | set(CMAKE_LINKER_FLAGS "${CMAKE_LINKER_FLAGS} ${TSAN_FLAGS}") 42 | else() 43 | message(FATAL_ERROR "Unsupported CMAKE_BUILD_TYPE for asan setup: ${CMAKE_BUILD_TYPE}") 44 | endif() 45 | else() 46 | message(STATUS ">>> USE_TSAN: NO") 47 | endif() 48 | -------------------------------------------------------------------------------- /images/input/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zchrissirhcz/KantuCompare/2b15c04a1787331282633aad9f48105719853779/images/input/background.png -------------------------------------------------------------------------------- /images/input/frontground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zchrissirhcz/KantuCompare/2b15c04a1787331282633aad9f48105719853779/images/input/frontground.png -------------------------------------------------------------------------------- /images/snapshots/snapshot_2022-05-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zchrissirhcz/KantuCompare/2b15c04a1787331282633aad9f48105719853779/images/snapshots/snapshot_2022-05-29.png -------------------------------------------------------------------------------- /images/snapshots/snapshot_2022-06-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zchrissirhcz/KantuCompare/2b15c04a1787331282633aad9f48105719853779/images/snapshots/snapshot_2022-06-12.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | gitpython -------------------------------------------------------------------------------- /sledpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.2", 3 | "packages": [ 4 | { 5 | "name": "imgui", 6 | "url": "https://github.com/ocornut/imgui", 7 | "license": "Copyright (c) 2014-2022 Omar Cornut, MIT License", 8 | "version": 9 | { 10 | "branch": "docking" 11 | }, 12 | "mirror": 13 | { 14 | "use": true, 15 | "url": "https://gitee.com/mirrors/imgui" 16 | } 17 | }, 18 | { 19 | "name": "portable-file-dialogs", 20 | "url": "https://github.com/samhocevar/portable-file-dialogs", 21 | "license": "WTFPL license", 22 | "version": 23 | { 24 | "branch": "master" 25 | } 26 | }, 27 | { 28 | "name": "glfw", 29 | "url": "https://github.com/glfw/glfw", 30 | "license": "Zlib license", 31 | "version": 32 | { 33 | "branch": "master" 34 | }, 35 | "mirror": 36 | { 37 | "use": true, 38 | "url": "https://gitee.com/mirrors/glfw" 39 | } 40 | }, 41 | { 42 | "name": "googletest", 43 | "url": "https://github.com/google/googletest", 44 | "version": 45 | { 46 | "tag": "release-1.11.0" 47 | }, 48 | "mirror": 49 | { 50 | "use": false, 51 | "url": "https://gitee.com/mirrors/googletest" 52 | }, 53 | "cmake_args": { 54 | "configure": 55 | [ 56 | "-DCMAKE_BUILD_TYPE=Release", 57 | "-DBUILD_GMOCK=OFF", 58 | "-DCMAKE_POSITION_INDEPENDENT_CODE=ON" 59 | ] 60 | } 61 | }, 62 | { 63 | "name": "opencv", 64 | "url": "https://github.com/opencv/opencv", 65 | "license": "Apache License 2.0", 66 | "version": 67 | { 68 | "tag": "4.5.5" 69 | }, 70 | "mirror": 71 | { 72 | "use": true, 73 | "url": "https://gitee.com/mirrors/opencv" 74 | }, 75 | "cmake_args": { 76 | "configure": 77 | [ 78 | "-G Ninja", 79 | "-D CMAKE_BUILD_TYPE=Release", 80 | "-D BUILD_SHARED_LIBS=OFF", 81 | "-D OPENCV_GENERATE_PKGCONFIG=ON", 82 | "-D BUILD_LIST=core,imgproc,highgui,gapi", 83 | "-D BUILD_TESTS=OFF", 84 | "-D BUILD_PERF_TESTS=OFF", 85 | "-D WITH_CUDA=OFF", 86 | "-D WITH_VTK=OFF", 87 | "-D WITH_MATLAB=OFF", 88 | "-D BUILD_DOCS=OFF", 89 | "-D BUILD_opencv_python3=OFF", 90 | "-D BUILD_opencv_python2=OFF", 91 | "-D WITH_IPP=OFF", 92 | "-D WITH_PROTOBUF=OFF", 93 | "-D WITH_QUIRC=OFF", 94 | "-D WITH_EIGEN=OFF", 95 | "-D CV_DISABLE_OPTIMIZATION=OFF" 96 | ] 97 | } 98 | } 99 | ] 100 | } -------------------------------------------------------------------------------- /sledpkg.py: -------------------------------------------------------------------------------- 1 | # sledpkg: Semi-precise package manager 2 | # Author: Zhuo Zhang 3 | # Created: 2023.04.28 00:00:00 4 | # Modified: 2023.04.30 16:15:00 5 | 6 | import git 7 | from git import RemoteProgress 8 | import os 9 | import subprocess 10 | import platform 11 | 12 | 13 | def is_wsl(): 14 | return 'microsoft-standard' in platform.uname().release 15 | 16 | def is_windows(): 17 | return platform.system().lower() == "windows" 18 | 19 | def is_linux(): 20 | return platform.system().lower() == "linux" 21 | 22 | def is_github_action(): 23 | s = os.environ.get('GITHUB_ACTION', None) 24 | if (s is not None): 25 | return True 26 | return False 27 | 28 | class CommandRunner(object): 29 | @staticmethod 30 | def run(cmd, verbose = True): 31 | if (verbose): 32 | print('Executing cmd:', cmd) 33 | process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) 34 | if is_windows(): 35 | encoding = 'ISO-8859-1' 36 | else: 37 | encoding = 'utf-8' 38 | output = process.communicate()[0].decode(encoding) 39 | if (verbose): 40 | print('Execution output:\n', output) 41 | return output 42 | 43 | 44 | class CloneProgress(RemoteProgress): 45 | def update(self, op_code, cur_count, max_count=None, message=''): 46 | if message: 47 | print(message) 48 | 49 | 50 | def ninja_available(): 51 | out = subprocess.getstatusoutput('ninja --version') 52 | if (out[0] == 0): 53 | return True 54 | return False 55 | 56 | 57 | class SledPackage(object): 58 | def __init__(self, name): 59 | self.name = name 60 | self.src_dir = None 61 | 62 | def clone_repo(self, git_url, branch=None, tag=None, commit_id=None, mirror_url=None, shallow=True, save_dir='deps'): 63 | if (not is_github_action()) and (mirror_url is not None): 64 | git_url = mirror_url 65 | repo_name = git_url.split('/')[-1] 66 | to_path = '{:s}/{:s}'.format(save_dir, repo_name) 67 | self.src_dir = to_path 68 | self.build_dir = to_path + "/build" 69 | self.install_dir = to_path + "/install" # the default one. you may override this 70 | self.build_type = "Release" 71 | 72 | if (commit_id is not None): 73 | shallow = False 74 | 75 | kwargs = dict() 76 | 77 | print("[git clone] {:s}".format(git_url)) 78 | print(" - to_path:", to_path) 79 | print(" - branch:", str(branch)) 80 | print(" - tag:", str(tag)) 81 | print(" - commit_id:", commit_id) 82 | if (shallow): 83 | print(" - shallow: True (depth=1)") 84 | kwargs['depth'] = 1 85 | else: 86 | print(" - shallow: False (full clone)") 87 | 88 | if ((branch is not None) and (tag is not None)): 89 | print(" Error: confusion. you can't specify both branch and tag.") 90 | return 91 | 92 | to_path_dot_git = to_path + "/.git" 93 | if (os.path.exists(to_path_dot_git)): 94 | print(" Warning: directory {:s} already exist, skip git clone".format(to_path_dot_git)) 95 | return 96 | 97 | if (branch is not None): 98 | kwargs['branch'] = branch 99 | elif (tag is not None): 100 | kwargs['branch'] = tag 101 | 102 | repo = git.Repo.clone_from(git_url, to_path = to_path, progress=CloneProgress(), **kwargs) 103 | 104 | if (commit_id is not None): 105 | commit = repo.commit(commit_id) 106 | repo.head.reference = commit 107 | repo.head.reset(index=True, working_tree=True) 108 | 109 | def cmake_configure(self, cmake_configure_args=[]): 110 | print("[configure] {:s}".format(self.name)) 111 | found_install_prefix = False 112 | found_build_type = False 113 | for item in cmake_configure_args: 114 | if ('CMAKE_INSTALL_PREFIX' in item): 115 | found_install_prefix = True 116 | self.install_dir = item.split('=')[-1] 117 | if ('CMAKE_BUILD_TYPE' in item): 118 | found_build_type = True 119 | self.build_type = item.split('=')[-1] 120 | 121 | cmd = "cmake -S {:s} -B {:s}".format(self.src_dir, self.build_dir) 122 | for item in cmake_configure_args: 123 | cmd += " " + item 124 | if (not found_install_prefix): 125 | cmd += " -DCMAKE_INSTALL_PREFIX={:s}".format(self.install_dir) 126 | if (not found_build_type): 127 | cmd += " -DCMAKE_BUILD_TYPE={:s}".format(self.build_type) 128 | if is_windows(): 129 | cmd += ' -G "Visual Studio 17 2022" -A x64' 130 | elif ninja_available(): 131 | cmd += ' -G Ninja' 132 | 133 | print(" cmake configure command is: {:s}".format(cmd)) 134 | CommandRunner.run(cmd) 135 | 136 | def cmake_build(self, build_type = None): 137 | print("[build] {:s}".format(self.name)) 138 | cmd = "cmake --build {:s} -j{:d}".format(self.build_dir, os.cpu_count()) 139 | if (build_type is not None): 140 | cmd += " --config {:s}".format(build_type) 141 | print(" cmake install cmd: ", cmd) 142 | # if (os.path.exists(self.build_dir)): 143 | # print(" build dir {:s} already exist, skip build".format(self.build_dir)) 144 | # return 145 | CommandRunner.run(cmd) 146 | 147 | def cmake_install(self, build_type = None): 148 | print("[install] {:s}".format(self.name)) 149 | cmd = "cmake --install {:s}".format(self.build_dir) 150 | if (build_type is not None): 151 | cmd += " --config {:s}".format(build_type) 152 | print(" cmake install cmd: ", cmd) 153 | # if (os.path.exists(self.install_dir)): 154 | # print(" install dir {:s} already exist, skip install".format(self.install_dir)) 155 | # return 156 | CommandRunner.run(cmd) 157 | -------------------------------------------------------------------------------- /sledpkg_run.py: -------------------------------------------------------------------------------- 1 | import sledpkg as sp 2 | import shutil 3 | import os 4 | 5 | 6 | def prepare_imgui(): 7 | pkg = sp.SledPackage('imgui') 8 | #pkg.clone_repo('https://github.com/ocornut/imgui', 'docking', mirror_url='https://gitee.com/mirrors/imgui') 9 | #pkg.clone_repo('https://github.com/zchrissirhcz/imgui', 'docking-for-image-compare') 10 | pkg.clone_repo('https://github.com/ocornut/imgui', tag='v1.89.5', mirror_url='https://gitee.com/mirrors/imgui') 11 | 12 | 13 | def preprare_portable_file_dialogs(): 14 | pkg = sp.SledPackage('portable-file-dialogs') 15 | pkg.clone_repo('https://github.com/samhocevar/portable-file-dialogs', mirror_url='https://gitee.com/mirrors_samhocevar/portable-file-dialogs') 16 | 17 | 18 | def prepare_glfw(): 19 | pkg = sp.SledPackage('glfw') 20 | pkg.clone_repo('https://github.com/glfw/glfw', tag='3.3.8', mirror_url='https://gitee.com/mirrors/glfw') 21 | 22 | 23 | def prepare_str(): 24 | pkg = sp.SledPackage('str') 25 | pkg.clone_repo('https://github.com/ocornut/str') 26 | 27 | 28 | def prepare_gtest(): 29 | pkg = sp.SledPackage('gtest') 30 | pkg.clone_repo('https://github.com/google/googletest', tag='release-1.11.0', mirror_url='https://gitee.com/mirrors/googletest') 31 | configure_args = [ 32 | "-DBUILD_GMOCK=OFF", 33 | ] 34 | if (sp.is_windows()): 35 | configure_args.append("-Dgtest_force_shared_crt=ON") 36 | else: 37 | configure_args.append("-DCMAKE_POSITION_INDEPENDENT_CODE=ON") 38 | pkg.cmake_configure( 39 | configure_args 40 | ) 41 | if sp.is_windows(): 42 | pkg.cmake_build('Debug') 43 | pkg.cmake_install('Debug') 44 | 45 | pkg.cmake_build('Release') 46 | pkg.cmake_install('Release') 47 | else: 48 | pkg.cmake_build() 49 | pkg.cmake_install() 50 | 51 | 52 | def prepare_opencv(): 53 | pkg = sp.SledPackage('opencv') 54 | pkg.clone_repo('https://github.com/opencv/opencv', tag='4.7.0', mirror_url='https://gitee.com/mirrors/opencv') 55 | gapi_dir = pkg.src_dir + "/modules/gapi" 56 | if (os.path.exists(gapi_dir)): 57 | shutil.rmtree(gapi_dir) 58 | pkg.cmake_configure( 59 | [ 60 | "-D BUILD_SHARED_LIBS=OFF", 61 | "-D BUILD_WITH_STATIC_CRT=OFF", 62 | "-D OPENCV_GENERATE_PKGCONFIG=ON", 63 | "-D BUILD_LIST=core,imgproc,imgcodecs,highgui", 64 | "-D BUILD_TESTS=OFF", 65 | "-D BUILD_PERF_TESTS=OFF", 66 | "-D WITH_CUDA=OFF", 67 | "-D WITH_VTK=OFF", 68 | "-D WITH_MATLAB=OFF", 69 | "-D BUILD_DOCS=OFF", 70 | "-D BUILD_opencv_python3=OFF", 71 | "-D BUILD_opencv_python2=OFF", 72 | "-D WITH_IPP=OFF", 73 | "-D WITH_PROTOBUF=OFF", 74 | "-D WITH_QUIRC=OFF", 75 | "-D WITH_EIGEN=OFF", 76 | "-D CV_DISABLE_OPTIMIZATION=OFF", 77 | "-D OPENCV_DOWNLOAD_MIRROR_ID=gitcode", 78 | "-D WITH_OPENCL=OFF", 79 | "-D WITH_OPENEXR=OFF" 80 | ] 81 | ) 82 | if sp.is_windows(): 83 | pkg.cmake_build('Debug') 84 | pkg.cmake_install('Debug') 85 | 86 | pkg.cmake_build('Release') 87 | pkg.cmake_install('Release') 88 | else: 89 | pkg.cmake_build() 90 | pkg.cmake_install() 91 | 92 | 93 | def prepare_mlcc(): 94 | pkg = sp.SledPackage('mlcc') 95 | pkg.clone_repo('https://github.com/scarsty/mlcc', commit_id='64c25fb') 96 | 97 | 98 | def prepare_fmtlib(): 99 | pkg = sp.SledPackage('fmtlib') 100 | pkg.clone_repo('https://github.com/fmtlib/fmt', tag='9.1.0', mirror_url='https://gitee.com/chooosky/fmt') 101 | 102 | 103 | if __name__ == '__main__': 104 | prepare_imgui() 105 | preprare_portable_file_dialogs() 106 | prepare_glfw() 107 | prepare_str() 108 | prepare_gtest() 109 | prepare_opencv() 110 | prepare_mlcc() 111 | prepare_fmtlib() -------------------------------------------------------------------------------- /src/kantu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # image file loading, format transform, string utilities 2 | add_library(kantu_basic STATIC 3 | transform_format.hpp 4 | transform_format.cpp 5 | image_io.hpp 6 | image_io.cpp 7 | string.hpp 8 | string.cpp 9 | ) 10 | target_include_directories(kantu_basic PUBLIC ${CMAKE_SOURCE_DIR}/src) 11 | target_link_libraries(kantu_basic PUBLIC ${OpenCV_LIBS} str fmt::fmt) 12 | 13 | # compare RGB/Gray images 14 | add_library(kantu_compare STATIC 15 | compare.hpp 16 | compare.cpp 17 | ) 18 | target_link_libraries(kantu_compare PUBLIC kantu_basic) 19 | 20 | # render result and show 21 | add_executable(KantuCompareApp 22 | app.cpp 23 | image_render.hpp 24 | image_render.cpp 25 | 26 | ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp 27 | ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp 28 | ${IMGUI_DIR}/imgui.cpp 29 | ${IMGUI_DIR}/imgui_draw.cpp 30 | ${IMGUI_DIR}/imgui_tables.cpp 31 | ${IMGUI_DIR}/imgui_widgets.cpp 32 | ${IMGUI_DIR}/imgui_demo.cpp 33 | ) 34 | target_link_libraries(KantuCompareApp 35 | glfw 36 | ${OPENGL_LIBRARIES} 37 | kantu_compare 38 | portable_file_dialogs 39 | ) 40 | 41 | if(CMAKE_SYSTEM_NAME MATCHES "Darwin") 42 | add_definitions(-DGL_SILENCE_DEPRECATION) 43 | endif() 44 | 45 | if(CMAKE_SYSTEM_NAME MATCHES "Windows") 46 | set_target_properties( 47 | KantuCompareApp PROPERTIES 48 | VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 49 | ) 50 | endif() 51 | 52 | add_custom_command(TARGET KantuCompareApp 53 | POST_BUILD 54 | COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/xkcd-script.ttf ${CMAKE_BINARY_DIR}/ 55 | ) 56 | 57 | if(KANTU_USE_PVS) 58 | include(cmake/PVS-Studio.cmake) 59 | pvs_studio_add_target(TARGET example2.analyze ALL 60 | OUTPUT FORMAT errorfile 61 | RECURSIVE ANALYZE KantuCompare 62 | MODE GA:1,2 63 | LOG target.err 64 | ) 65 | endif() 66 | 67 | # fast debugging with cv::imshow 68 | add_executable(kantu_image_viewer 69 | image_viewer.cpp 70 | ) 71 | target_link_libraries(kantu_image_viewer kantu_basic) 72 | 73 | 74 | install(TARGETS KantuCompareApp 75 | RUNTIME DESTINATION . 76 | ) 77 | install(FILES ${CMAKE_SOURCE_DIR}/xkcd-script.ttf 78 | DESTINATION . 79 | ) 80 | -------------------------------------------------------------------------------- /src/kantu/app.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | //#include 5 | #include 6 | 7 | #include "image_io.hpp" 8 | #define IMGUI_DEFINE_MATH_OPERATORS 9 | #include "imgui.h" 10 | #include "imgui_impl_glfw.h" 11 | #include "imgui_impl_opengl3.h" 12 | #include "imgui_internal.h" 13 | 14 | #include "app_design.hpp" 15 | #include "portable-file-dialogs.h" 16 | 17 | #include "kantu/compare.hpp" 18 | #include "kantu/image_render.hpp" 19 | #include "kantu/image_inspect.h" 20 | 21 | #define STR_IMPLEMENTATION 22 | #include "str/Str.h" 23 | using namespace kantu; 24 | 25 | #include "kantu/log.hpp" 26 | #include 27 | #include 28 | 29 | class MyApp : public App 30 | { 31 | public: 32 | MyApp() = default; 33 | ~MyApp() = default; 34 | 35 | void myUpdateMouseWheel(); 36 | 37 | void StartUp() 38 | { 39 | // Title 40 | glfwSetWindowTitle(window, "KantuCompare"); 41 | glfwSetWindowSize(window, 960, 640); 42 | 43 | // Style 44 | ImGui::StyleColorsDark(); 45 | ImGuiStyle& style = ImGui::GetStyle(); 46 | // style.WindowRounding = 8.f; 47 | // style.ChildRounding = 6.f; 48 | // style.FrameRounding = 6.f; 49 | // style.PopupRounding = 6.f; 50 | // style.GrabRounding = 6.f; 51 | // style.ChildRounding = 6.f; 52 | 53 | // Load Fonts only on specific OS for portability 54 | //std::string font_path = "/System/Library/Fonts/PingFang.ttc"; // system wide 55 | ImGuiIO& io = ImGui::GetIO(); 56 | std::filesystem::path font_path = "xkcd-script.ttf"; 57 | if (std::filesystem::exists(font_path)) 58 | { 59 | LOG(INFO) << fmt::format("Using font {:}", font_path.string()); 60 | io.Fonts->AddFontFromFileTTF(font_path.string().c_str(), 23); 61 | } 62 | //std::string font_path = "ark-pixel-16px-proportional-zh_cn.otf"; 63 | //io.Fonts->AddFontFromFileTTF(font_path.c_str(), 23); 64 | // #if __APPLE__ && __ARM_NEON 65 | // std::string font_path = "/Users/zz/Library/Fonts/SourceHanSansCN-Normal.otf"; // user installed 66 | // ImFont* font = io.Fonts->AddFontFromFileTTF(font_path.c_str(), 16.0f, NULL, io.Fonts->GetGlyphRangesChineseFull()); 67 | // io.Fonts->Build(); 68 | // IM_ASSERT(font != NULL); 69 | // #endif 70 | 71 | // ImFont* font = io.Fonts->AddFontFromFileTTF(font_path.c_str(), 16.0f, NULL, io.Fonts->GetGlyphRangesChineseFull()); 72 | // io.Fonts->Build(); 73 | // IM_ASSERT(font != NULL); 74 | 75 | //io.ConfigWindowsMoveFromTitleBarOnly = true; 76 | 77 | #if IMGUI_WITH_DOCKING 78 | ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; 79 | #endif 80 | 81 | //io.FontAllowUserScaling = true; 82 | 83 | InitFileFilters(); 84 | } 85 | 86 | void showText(const char* text, const char* inputId) 87 | { 88 | char input[256]; 89 | strcpy(input, text); 90 | ImGui::PushID(inputId); 91 | ImGui::PushItemWidth(ImGui::GetWindowSize().x); 92 | ImGui::InputText("", input, 256, ImGuiInputTextFlags_ReadOnly); 93 | ImGui::PopItemWidth(); 94 | ImGui::PopID(); 95 | } 96 | 97 | void Update() 98 | { 99 | //ImGui::ShowDemoWindow(); 100 | 101 | myUpdateMouseWheel(); // not working now. 102 | 103 | static bool use_work_area = true; 104 | static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoScrollWithMouse; 105 | 106 | // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) 107 | // Based on your use case you may want one of the other. 108 | const ImGuiViewport* viewport = ImGui::GetMainViewport(); 109 | ImGui::SetNextWindowPos(use_work_area ? viewport->WorkPos : viewport->Pos); 110 | ImGui::SetNextWindowSize(use_work_area ? viewport->WorkSize : viewport->Size); 111 | 112 | // ImGuiIO& io = ImGui::GetIO(); 113 | // ImVec2 display_size(io.DisplaySize.x, io.DisplaySize.y); 114 | // ImGui::SetNextWindowSize(display_size); 115 | 116 | ImGui::Begin("Testing menu", nullptr, flags); 117 | 118 | ImGui::BeginChild("##PathRegion", ImVec2(ImGui::GetWindowWidth() - 50, ImGui::GetWindowHeight() * 1 / 10), true, ImGuiWindowFlags_NoScrollbar); 119 | { 120 | ImGui::BeginChild("##leftpath", ImVec2(ImGui::GetWindowWidth() / 2 - 10, ImGui::GetWindowHeight()), false); 121 | if (ImGui::Button("Load##1")) 122 | { 123 | LoadImage(imageLeft); 124 | compare_condition_updated = true; 125 | } 126 | if (!imageLeft.mat.empty()) 127 | { 128 | ImGui::SameLine(); 129 | //ImGui::SetCursorPosX(x); // align back to the left 130 | 131 | //ImGui::Text("%s", text.c_str()); 132 | showText(imageLeft.get_name(), "1"); 133 | Str256 meta_info; 134 | meta_info.setf("W=%d,H=%d; %d bytes", imageLeft.mat.size().width, imageLeft.mat.size().height, imageLeft.filesize); 135 | showText(meta_info.c_str(), "2"); 136 | } 137 | ImGui::EndChild(); 138 | 139 | ImGui::SameLine(); 140 | ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); 141 | ImGui::SameLine(); 142 | 143 | ImGui::BeginChild("##rightpath", ImVec2(ImGui::GetWindowWidth() / 2 - 10, ImGui::GetWindowHeight()), false); 144 | if (ImGui::Button("Load##2")) 145 | { 146 | LoadImage(imageRight); 147 | compare_condition_updated = true; 148 | } 149 | if (!imageRight.mat.empty()) 150 | { 151 | Str256 text; 152 | text.setf("%s\nW=%d,H=%d; %d bytes", imageRight.get_name(), imageRight.mat.size().width, imageRight.mat.size().height, imageRight.filesize); 153 | ImGui::SameLine(); 154 | //ImGui::SetCursorPosX(x); // align back to the left 155 | 156 | //ImGui::Text("%s", text.c_str()); 157 | showText(imageRight.get_name(), "3"); 158 | Str256 meta_info; 159 | meta_info.setf("W=%d,H=%d; %d bytes", imageRight.mat.size().width, imageRight.mat.size().height, imageRight.filesize); 160 | showText(meta_info.c_str(), "4"); 161 | } 162 | ImGui::EndChild(); 163 | } 164 | ImGui::EndChild(); 165 | 166 | ImGui::BeginChild("##InputImagesRegion", ImVec2(ImGui::GetWindowWidth() - 50, ImGui::GetWindowHeight() * 4 / 10), true); 167 | { 168 | ImVec2 vMin = ImGui::GetWindowContentRegionMin(); 169 | ImVec2 vMax = ImGui::GetWindowContentRegionMax(); 170 | 171 | // vMin.x += ImGui::GetWindowPos().x; 172 | // vMin.y += ImGui::GetWindowPos().y; 173 | // vMax.x += ImGui::GetWindowPos().x; 174 | // vMax.y += ImGui::GetWindowPos().y; 175 | float window_content_height = vMax.y - vMin.y; 176 | ImGui::BeginChild("Image1", ImVec2(ImGui::GetWindowWidth() / 2 - 10, window_content_height), false); 177 | if (!imageLeft.mat.empty()) 178 | { 179 | //std::string winname = std::string("Image1 - ") + imageLeft.get_name(); 180 | Str256 winname; 181 | winname.setf("Image1 - %s", imageLeft.get_name()); 182 | ShowImage(winname.c_str(), imageLeft.get_open(), imageLeft, 1.0f); 183 | } 184 | ImGui::EndChild(); 185 | 186 | ImGui::SameLine(); 187 | ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); 188 | ImGui::SameLine(); 189 | 190 | ImGui::BeginChild("Image2", ImVec2(0, window_content_height), false); 191 | if (!imageRight.mat.empty()) 192 | { 193 | //std::string winname = std::string("Image2 - ") + imageRight.get_name(); 194 | Str256 winname; 195 | winname.setf("Image2 - %s", imageLeft.get_name()); 196 | ShowImage(winname.c_str(), imageRight.get_open(), imageRight, 0.0f); 197 | } 198 | ImGui::EndChild(); 199 | } 200 | ImGui::EndChild(); 201 | 202 | //ImGui::SameLine(); 203 | ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal); 204 | //ImGui::SameLine(); 205 | 206 | ImGui::BeginChild("##CompareResultRegion", ImVec2(ImGui::GetWindowWidth() - 50, ImGui::GetWindowHeight() * 5 / 10), false); 207 | { 208 | ImGui::BeginChild("###ConfigRegion", ImVec2(ImGui::GetWindowWidth() / 4, ImGui::GetWindowHeight() - 20), false); 209 | { 210 | // zoom 211 | { 212 | ImGui::PushItemWidth(200); 213 | char text[20] = {0}; 214 | sprintf(text, "Zoom: %d%%", zoom_percent); 215 | ImGui::Text("%s", text); 216 | ImGuiSliderFlags zoom_slider_flags = ImGuiSliderFlags_NoInput; 217 | ImGui::SliderInt("##Zoom", &zoom_percent, zoom_percent_min, zoom_percent_max, "", zoom_slider_flags); 218 | } 219 | // tolerance 220 | { 221 | ImGui::PushItemWidth(256); 222 | char text[20] = {0}; 223 | sprintf(text, "Tolerance: %d", diff_thresh); 224 | ImGui::Text("%s", text); 225 | ImGuiSliderFlags tolerance_slider_flags = ImGuiSliderFlags_NoInput; 226 | compare_condition_updated |= ImGui::SliderInt("##Tolerance", &diff_thresh, 0, 255, "", tolerance_slider_flags); 227 | } 228 | { 229 | if (is_exactly_same) 230 | ImGui::Text("Exactly Same: Yes"); 231 | else 232 | ImGui::Text("Exactly Same: No"); 233 | } 234 | { 235 | ImGui::Checkbox("Inspect Pixels", &inspect_pixels); 236 | } 237 | { 238 | if (ImGui::Button("Reload Input Images")) 239 | { 240 | if (!imageLeft.mat.empty()) 241 | { 242 | imageLeft.reload(); 243 | compare_condition_updated = true; 244 | } 245 | if (!imageRight.mat.empty()) 246 | { 247 | imageRight.reload(); 248 | compare_condition_updated = true; 249 | } 250 | } 251 | } 252 | } 253 | ImGui::EndChild(); 254 | 255 | ComputeDiffImage(); 256 | 257 | ImGui::SameLine(); 258 | ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); 259 | ImGui::SameLine(); 260 | 261 | ImGui::BeginChild("###RightImage", ImVec2(0, ImGui::GetWindowHeight() - 20), false); 262 | if (show_diff_image) 263 | { 264 | ShowImage("Diff Image", &show_diff_image, diff_image, 0.3f); 265 | ImGuiWindow* win = ImGui::GetCurrentWindow(); 266 | if (win->ScrollbarX || win->ScrollbarY) 267 | { 268 | ImGui::PushClipRect(win->OuterRectClipped.Min, win->OuterRectClipped.Max, false); 269 | if (win->ScrollbarX) 270 | { 271 | ImRect r = ImGui::GetWindowScrollbarRect(win, ImGuiAxis_X); 272 | win->DrawList->AddRect(r.Min, r.Max, IM_COL32(255, 0, 0, 255)); 273 | } 274 | if (win->ScrollbarY) 275 | { 276 | ImRect r = ImGui::GetWindowScrollbarRect(win, ImGuiAxis_Y); 277 | win->DrawList->AddRect(r.Min, r.Max, IM_COL32(255, 0, 0, 255)); 278 | } 279 | ImGui::PopClipRect(); 280 | } 281 | 282 | { 283 | ImGuiIO& io = ImGui::GetIO(); 284 | 285 | ImRect rc = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); 286 | ImVec2 mouseUVCoord = (io.MousePos - rc.Min) / rc.GetSize(); 287 | mouseUVCoord.y = 1.f - mouseUVCoord.y; 288 | if (inspect_pixels && mouseUVCoord.x >= 0.f && mouseUVCoord.y >= 0.f) 289 | { 290 | int width = diff_image.mat.size().width; 291 | int height = diff_image.mat.size().height; 292 | 293 | //imageInspect(width, height, pickerImage.GetBits(), mouseUVCoord, displayedTextureSize); 294 | ImVec2 displayedTextureSize(8, 8); 295 | ImageInspect::inspect(width, height, diff_image.mat.data, mouseUVCoord, displayedTextureSize); 296 | } 297 | } 298 | } 299 | ImGui::EndChild(); 300 | } 301 | ImGui::EndChild(); 302 | 303 | //ImGui::SameLine(); 304 | //ImGui::SeparatorEx(ImGuiSeparatorFlags_Horizontal); 305 | //ImGui::SameLine(); 306 | 307 | // ImGui::BeginChild("##StatusBarRegion", ImVec2(ImGui::GetWindowWidth(), ImGui::GetWindowHeight() / 10), false); 308 | // ImGui::Text("Important status(TODO)"); 309 | // ImGui::EndChild(); 310 | //StatusbarUI(); 311 | 312 | ImGui::End(); 313 | } 314 | 315 | private: 316 | int UI_ChooseImageFile(); 317 | void LoadImage(RichImage& image); 318 | void ComputeDiffImage(); 319 | void ShowImage(const char* windowName, bool* open, const RichImage& image, float align_to_right_ratio = 0.f); 320 | 321 | void StatusbarUI(); 322 | void InitFileFilters(); 323 | 324 | private: 325 | bool window_open = true; 326 | bool Check = true; 327 | Str256 filepath; 328 | RichImage imageLeft; 329 | RichImage imageRight; 330 | RichImage diff_image; 331 | bool compare_condition_updated = false; 332 | bool show_diff_image = false; 333 | int diff_thresh = 1; 334 | int zoom_percent = 46; 335 | int zoom_percent_min = 10; 336 | int zoom_percent_max = 1000; 337 | bool inspect_pixels = false; 338 | bool is_exactly_same = false; 339 | 340 | const float statusbarSize = 50; 341 | 342 | std::string filter_msg1 = "Image Files ("; 343 | std::string filter_msg2 = ""; 344 | }; 345 | 346 | static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved. 347 | 348 | static void StartLockWheelingWindow(ImGuiWindow* window) 349 | { 350 | ImGuiContext& g = *GImGui; 351 | if (g.WheelingWindow == window) 352 | return; 353 | g.WheelingWindow = window; 354 | g.WheelingWindowRefMousePos = g.IO.MousePos; 355 | g.WheelingWindowReleaseTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER; 356 | } 357 | 358 | // When with mouse wheel moving (vertically), and current window name contains 'Image', resize current window's size 359 | void MyApp::myUpdateMouseWheel() 360 | { 361 | ImGuiContext& g = *GImGui; 362 | 363 | ImGuiWindow* cur_window = g.WheelingWindow; 364 | 365 | // Reset the locked window if we move the mouse or after the timer elapses 366 | if (cur_window != nullptr) 367 | { 368 | // std::string window_name = cur_window->Name; 369 | // if (window_name.length() < 5 || window_name.find("Image") == std::string::npos) 370 | // { 371 | // return; 372 | // } 373 | // printf("!! cur_window->Name: %s\n", cur_window->Name); 374 | 375 | g.WheelingWindowReleaseTimer -= g.IO.DeltaTime; 376 | if (ImGui::IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold) 377 | g.WheelingWindowReleaseTimer = 0.0f; 378 | if (g.WheelingWindowReleaseTimer <= 0.0f) 379 | { 380 | g.WheelingWindow = NULL; 381 | g.WheelingWindowReleaseTimer = 0.0f; 382 | } 383 | } 384 | 385 | //float wheel_y = g.IO.MouseWheel; 386 | ImVec2 wheel; 387 | wheel.x = ImGui::TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f; 388 | wheel.y = ImGui::TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f; 389 | if (wheel.x == 0.0f && wheel.y == 0.0f) 390 | return; 391 | 392 | ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow; 393 | if (!window || window->Collapsed) 394 | return; 395 | 396 | if (wheel.y != 0.0f) 397 | { 398 | StartLockWheelingWindow(window); 399 | 400 | zoom_percent = zoom_percent + g.IO.MouseWheel * 5; 401 | if (zoom_percent > zoom_percent_max) 402 | { 403 | zoom_percent = zoom_percent_max; 404 | } 405 | else if (zoom_percent < zoom_percent_min) 406 | { 407 | zoom_percent = zoom_percent_min; 408 | } 409 | 410 | return; 411 | } 412 | } 413 | 414 | void MyApp::StatusbarUI() 415 | { 416 | ImGuiViewport* viewport = ImGui::GetMainViewport(); 417 | ImGui::SetNextWindowPos(ImVec2(viewport->Pos.x, viewport->Pos.y + viewport->Size.y - statusbarSize)); 418 | ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, statusbarSize)); 419 | //ImGui::SetNextWindowViewport(viewport->ID); 420 | ImGuiWindowFlags window_flags = 0 421 | //| ImGuiWindowFlags_NoDocking 422 | | ImGuiWindowFlags_NoTitleBar 423 | | ImGuiWindowFlags_NoResize 424 | | ImGuiWindowFlags_NoMove 425 | | ImGuiWindowFlags_NoScrollbar 426 | | ImGuiWindowFlags_NoSavedSettings; 427 | ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); 428 | ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.3f, 0.3f, 0.3f, 1.0f)); 429 | ImGui::Begin("STATUSBAR", NULL, window_flags); 430 | ImGui::PopStyleVar(); 431 | ImGui::Text("Status bar message."); 432 | ImGui::End(); 433 | ImGui::PopStyleColor(); 434 | } 435 | 436 | void MyApp::InitFileFilters() 437 | { 438 | filter_msg1 = "Image Files ("; 439 | filter_msg2 = ""; 440 | std::vector exts = kantu::get_supported_image_file_exts(); 441 | for (size_t i = 0; i < exts.size(); i++) 442 | { 443 | if (i > 0) 444 | { 445 | filter_msg1 += " ." + exts[i]; 446 | filter_msg2 += " *." + exts[i]; 447 | } 448 | else 449 | { 450 | filter_msg1 += "." + exts[i]; 451 | filter_msg2 += "*." + exts[i]; 452 | } 453 | } 454 | filter_msg1 += ")"; 455 | } 456 | 457 | void MyApp::ShowImage(const char* windowName, bool* open, const RichImage& image, float align_to_right_ratio) 458 | { 459 | if (*open) 460 | { 461 | GLuint texture = image.get_texture(); 462 | //ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(INFINITY, INFINITY)); 463 | 464 | // ImVec2 p_min = ImGui::GetCursorScreenPos(); // actual position 465 | // ImVec2 p_max = ImVec2(ImGui::GetContentRegionAvail().x + p_min.x, ImGui::GetContentRegionAvail().y + p_min.y); 466 | 467 | //ImGui::BeginChild("Image1Content", ImVec2(0, 0), true); 468 | //ImGui::Begin("Image1Content", NULL); 469 | //ImGui::GetWindowDrawList()->AddImage((void*)(uintptr_t)texture, p_min, p_max); 470 | //ImGui::EndChild(); 471 | //ImGui::End(); 472 | // 473 | ImVec2 actual_image_size(image.mat.size().width, image.mat.size().height); 474 | ImVec2 rendered_texture_size = actual_image_size * (zoom_percent * 1.0 / 100); 475 | 476 | bool clamped_x_by_window = false; 477 | bool clamped_y_by_window = false; 478 | 479 | ImVec2 window_size = ImGui::GetWindowSize(); 480 | ImVec2 image_window_size = rendered_texture_size; 481 | if (rendered_texture_size.x > window_size.x) 482 | { 483 | //rendered_texture_size.x = window_size.x; 484 | clamped_x_by_window = true; 485 | } 486 | if (rendered_texture_size.y > window_size.y) 487 | { 488 | //rendered_texture_size.y = window_size.y; 489 | clamped_y_by_window = true; 490 | } 491 | bool clamped_by_window = clamped_x_by_window | clamped_y_by_window; 492 | if (clamped_by_window) 493 | { 494 | image_window_size = window_size; 495 | } 496 | 497 | if (align_to_right_ratio >= 0 && align_to_right_ratio <= 1) 498 | { 499 | ImVec2 win_size = ImGui::GetCurrentWindow()->Size; 500 | ImVec2 offset; 501 | offset.y = 0; 502 | if (clamped_x_by_window) 503 | offset.x = 0; 504 | else 505 | offset.x = (win_size.x - rendered_texture_size.x) * align_to_right_ratio; 506 | ImVec2 p_min = ImGui::GetCursorScreenPos() + offset; 507 | //ImVec2 p_max = p_min + rendered_texture_size; 508 | //ImGui::GetWindowDrawList()->AddImage((void*)(uintptr_t)texture, p_min, p_max); 509 | ImGui::SetNextWindowPos(p_min); 510 | } 511 | else 512 | { 513 | //ImGui::Image((void*)(uintptr_t)texture, rendered_texture_size); 514 | ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); 515 | } 516 | 517 | //std::string label = cv::format("image##%d", texture); 518 | Str256 label; 519 | label.setf("image##%d", texture); 520 | ImGui::BeginChild(label.c_str(), image_window_size, clamped_by_window, ImGuiWindowFlags_HorizontalScrollbar); 521 | { 522 | ImGui::Image((void*)(uintptr_t)texture, rendered_texture_size); 523 | } 524 | ImGui::EndChild(); 525 | } 526 | } 527 | 528 | void MyApp::ComputeDiffImage() 529 | { 530 | if ((!imageLeft.mat.empty() && !imageRight.mat.empty() && compare_condition_updated)) 531 | { 532 | cv::Mat diff_mat; 533 | if (!imageLeft.mat.empty() && !imageRight.mat.empty()) 534 | { 535 | diff_mat = compare_two_mat(imageLeft.mat, imageRight.mat, diff_thresh, is_exactly_same); 536 | } 537 | else 538 | { 539 | diff_mat.create(255, 255, CV_8UC3); 540 | diff_mat = cv::Scalar(128, 128, 128); 541 | } 542 | 543 | if (diff_image.mat.empty()) 544 | { 545 | diff_image.clear(); // free texture memory 546 | } 547 | diff_image.load_mat(diff_mat); 548 | compare_condition_updated = false; 549 | show_diff_image = true; 550 | } 551 | } 552 | 553 | int MyApp::UI_ChooseImageFile() 554 | { 555 | // Check that a backend is available 556 | if (!pfd::settings::available()) 557 | { 558 | LOG(ERROR) << "Portable File Dialogs are not available on this platform.\n"; 559 | return 1; 560 | } 561 | 562 | // Set verbosity to true 563 | pfd::settings::verbose(true); 564 | 565 | #if _MSC_VER 566 | std::string default_image_directory = "C:/Users"; 567 | #else 568 | const char* home_dir = getenv("HOME"); 569 | std::string default_image_directory = home_dir; 570 | LOG(INFO) << fmt::format("[DEBUG] default_image_directory: {:s}\n", default_image_directory.c_str()); 571 | #endif 572 | 573 | // NOTE: file extension filter not working on macOSX 574 | auto f = pfd::open_file("Choose image file", 575 | //pfd::path::home(), 576 | default_image_directory, 577 | 578 | //{"Image Files (.jpg .png .jpeg .bmp .nv21 .nv12 .rgb24 .bgr24)", "*.jpg *.png *.jpeg *.bmp *.nv21 *.nv12 *.rgb24 *.bgr24", 579 | {filter_msg1, filter_msg2, "All Files", "*"} 580 | //pfd::opt::multiselect 581 | ); 582 | if (f.result().size() > 0) 583 | { 584 | LOG(INFO) << "Selected files:" << f.result()[0] << std::endl; 585 | 586 | filepath.setf("%s", f.result()[0].c_str()); 587 | } 588 | return 0; 589 | } 590 | 591 | void MyApp::LoadImage(RichImage& image) 592 | { 593 | UI_ChooseImageFile(); 594 | if (filepath.length() > 0) 595 | { 596 | image.load_from_file(filepath); 597 | } 598 | filepath = NULL; 599 | } 600 | 601 | int main(int argc, char** argv) 602 | { 603 | MyApp app; 604 | app.Run(); 605 | 606 | return 0; 607 | } 608 | -------------------------------------------------------------------------------- /src/kantu/app_design.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | #include "imgui_impl_glfw.h" 5 | #include "imgui_impl_opengl3.h" 6 | 7 | #if defined(IMGUI_IMPL_OPENGL_ES2) 8 | #include 9 | #endif 10 | #include // Will drag system OpenGL headers 11 | 12 | #include 13 | #include 14 | 15 | #include "kantu/log.hpp" 16 | #include 17 | 18 | using namespace Shadow; 19 | 20 | static void glfw_error_callback(int error, const char* description) 21 | { 22 | LOG(ERROR) << fmt::format("Glfw Error {:d}: {:s}\n", error, description); 23 | } 24 | 25 | static const char* decide_gl_glsl_versions() 26 | { 27 | // Decide GL+GLSL versions 28 | #if defined(IMGUI_IMPL_OPENGL_ES2) 29 | // GL ES 2.0 + GLSL 100 30 | const char* glsl_version = "#version 100"; 31 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); 32 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 33 | glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); 34 | #elif defined(__APPLE__) 35 | // GL 3.2 + GLSL 150 36 | const char* glsl_version = "#version 150"; 37 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 38 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); 39 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 40 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac 41 | #else 42 | // GL 3.0 + GLSL 130 43 | const char* glsl_version = "#version 130"; 44 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 45 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 46 | //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 47 | //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only 48 | #endif 49 | return glsl_version; 50 | } 51 | 52 | template 53 | class App 54 | { 55 | public: 56 | App() 57 | { 58 | // Setup 59 | // Setup window 60 | glfwSetErrorCallback(glfw_error_callback); 61 | if (!glfwInit()) 62 | exit(1); 63 | 64 | const char* glsl_version = decide_gl_glsl_versions(); 65 | 66 | // Create window with graphics context 67 | window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+OpenGL3 example", NULL, NULL); 68 | if (window == NULL) 69 | exit(1); 70 | glfwMakeContextCurrent(window); 71 | glfwSwapInterval(1); // Enable vsync 72 | 73 | // Setup Dear ImGui context 74 | IMGUI_CHECKVERSION(); 75 | ImGui::CreateContext(); 76 | ImGuiIO& io = ImGui::GetIO(); 77 | (void)io; 78 | 79 | // Setup Dear ImGui style 80 | ImGui::StyleColorsDark(); 81 | 82 | // Setup Platform/Renderer backends 83 | ImGui_ImplGlfw_InitForOpenGL(window, true); 84 | ImGui_ImplOpenGL3_Init(glsl_version); 85 | } 86 | 87 | ~App() 88 | { 89 | // Cleanup 90 | ImGui_ImplOpenGL3_Shutdown(); 91 | ImGui_ImplGlfw_Shutdown(); 92 | ImGui::DestroyContext(); 93 | 94 | glfwDestroyWindow(window); 95 | glfwTerminate(); 96 | } 97 | 98 | void Run() 99 | { 100 | // Our state 101 | StartUp(); 102 | // Main loop 103 | while (!glfwWindowShouldClose(window)) 104 | { 105 | // Poll and handle events (inputs, window resize, etc.) 106 | // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. 107 | // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. 108 | // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. 109 | // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. 110 | glfwPollEvents(); 111 | 112 | // Start the Dear ImGui frame 113 | ImGui_ImplOpenGL3_NewFrame(); 114 | ImGui_ImplGlfw_NewFrame(); 115 | ImGui::NewFrame(); 116 | 117 | Update(); 118 | 119 | // Rendering 120 | ImGui::Render(); 121 | int display_w, display_h; 122 | glfwGetFramebufferSize(window, &display_w, &display_h); 123 | glViewport(0, 0, display_w, display_h); 124 | glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); 125 | glClear(GL_COLOR_BUFFER_BIT); 126 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 127 | 128 | glfwSwapBuffers(window); 129 | } 130 | } 131 | 132 | void Update() 133 | { 134 | static_cast(this)->Update(); 135 | } 136 | void StartUp() 137 | { 138 | static_cast(this)->StartUp(); 139 | } 140 | 141 | protected: 142 | GLFWwindow* window; 143 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 144 | }; -------------------------------------------------------------------------------- /src/kantu/compare.cpp: -------------------------------------------------------------------------------- 1 | #include "kantu/compare.hpp" 2 | #include "kantu/log.hpp" 3 | #include 4 | 5 | using namespace Shadow; 6 | 7 | void kantu::get_diff_image(const cv::Mat& src1, const cv::Mat& src2, cv::Mat& diff, int thresh, cv::Scalar& below, cv::Scalar& above) 8 | { 9 | CV_Assert(src1.rows == src2.rows && src1.cols == src2.cols); 10 | CV_Assert(src1.channels() == src2.channels()); 11 | CV_Assert(thresh >= 0); 12 | 13 | const int channels = src1.channels(); 14 | 15 | cv::absdiff(src1, src2, diff); 16 | for (int i = 0; i < diff.rows; i++) 17 | { 18 | for (int j = 0; j < diff.cols; j++) 19 | { 20 | uchar* diff_pixel = diff.ptr(i, j); 21 | const uchar* src_pixel = src1.ptr(i, j); 22 | bool bigger = false; 23 | int diff_sum = 0; 24 | for (int k = 0; k < channels; k++) 25 | { 26 | diff_sum += diff_pixel[k]; 27 | if (diff_pixel[k] > thresh) 28 | { 29 | bigger = true; 30 | break; 31 | } 32 | } 33 | if (diff_sum == 0) 34 | { 35 | float R2Y = 0.299; 36 | float G2Y = 0.587; 37 | float B2Y = 0.114; 38 | 39 | int B = src_pixel[0]; 40 | int G = src_pixel[1]; 41 | int R = src_pixel[2]; 42 | 43 | int Gray = R2Y * R + G2Y * G + B2Y * B; 44 | 45 | diff_pixel[0] = Gray; 46 | diff_pixel[1] = Gray; 47 | diff_pixel[2] = Gray; 48 | diff_pixel[3] = 255; 49 | } 50 | else 51 | { 52 | if (bigger) 53 | { 54 | diff_pixel[0] = above.val[0]; 55 | diff_pixel[1] = above.val[1]; 56 | diff_pixel[2] = above.val[2]; 57 | diff_pixel[3] = 255; 58 | } 59 | else 60 | { 61 | diff_pixel[0] = below.val[0]; 62 | diff_pixel[1] = below.val[1]; 63 | diff_pixel[2] = below.val[2]; 64 | diff_pixel[3] = 255; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | cv::Mat kantu::compare_two_mat(const cv::Mat& image_left, const cv::Mat& image_right, int toleranceThresh, bool& is_exactly_same) 72 | { 73 | if (image_left.channels() != 4 || image_right.channels() != 4) 74 | { 75 | LOG(ERROR) << "only support BGRA image for comparision" << std::endl; 76 | return cv::Mat(); 77 | } 78 | const int channels = 4; 79 | 80 | // TODO: support 1, 2, 4 channel image comparison 81 | cv::Mat diff; 82 | if (image_left.empty() && image_right.empty()) 83 | { 84 | diff.create(256, 256, CV_8UC3); 85 | diff = cv::Scalar(128, 128, 128); 86 | is_exactly_same = true; 87 | } 88 | else if (image_right.empty()) 89 | { 90 | diff = image_right.clone(); 91 | is_exactly_same = false; 92 | } 93 | else if (image_left.empty()) 94 | { 95 | diff = image_left.clone(); 96 | is_exactly_same = false; 97 | } 98 | else 99 | { 100 | cv::Scalar pixel_diff; 101 | cv::Mat diff_image_left; 102 | cv::Mat diff_image_right; 103 | cv::Mat diff_image_compare; 104 | 105 | cv::Mat image_compare; 106 | if (image_left.size() != image_right.size()) 107 | { 108 | cv::Size big_size; 109 | big_size.height = std::max(image_left.size().height, image_right.size().height); 110 | big_size.width = std::max(image_left.size().width, image_right.size().width); 111 | 112 | cv::Mat image_left_big(big_size, image_left.type(), cv::Scalar(0)); 113 | cv::Mat image_right_big(big_size, image_right.type(), cv::Scalar(0)); 114 | for (int i = 0; i < image_left.rows; i++) 115 | { 116 | for (int j = 0; j < image_left.cols; j++) 117 | { 118 | for (int k = 0; k < channels; k++) 119 | { 120 | image_left_big.ptr(i, j)[k] = image_left.ptr(i, j)[k]; 121 | } 122 | } 123 | } 124 | 125 | for (int i = 0; i < image_right.rows; i++) 126 | { 127 | for (int j = 0; j < image_right.cols; j++) 128 | { 129 | for (int k = 0; k < channels; k++) 130 | { 131 | image_right_big.ptr(i, j)[k] = image_right.ptr(i, j)[k]; 132 | } 133 | } 134 | } 135 | 136 | // TODO: fix the transparency(alpha) for diff region in diff region(the intersection) and non-diff region(the union minus the intersection) 137 | cv::addWeighted(image_left_big, 1.0, image_right_big, 1.0, 0.0, image_compare); 138 | 139 | const int roi_width = std::min(image_left.size().width, image_right.size().width); 140 | const int roi_height = std::min(image_left.size().height, image_right.size().height); 141 | cv::Rect rect(0, 0, roi_width, roi_height); 142 | 143 | diff_image_left = image_left(rect); 144 | diff_image_right = image_right(rect); 145 | diff_image_compare = image_compare(rect); 146 | } 147 | else // size equal 148 | { 149 | diff_image_left = image_left; 150 | diff_image_right = image_right; 151 | image_compare.create(image_left.size(), image_left.type()); 152 | diff_image_compare = image_compare; 153 | } 154 | 155 | cv::absdiff(diff_image_left, diff_image_right, diff_image_compare); 156 | pixel_diff = cv::sum(diff_image_compare); 157 | int sum = pixel_diff.val[0] + pixel_diff.val[1] + pixel_diff.val[2] + pixel_diff.val[3]; 158 | //cv::setNumThreads(1); 159 | 160 | if (sum == 0) 161 | { 162 | // if the left and right image is different size, but same in the overlapped region, we compute the gray image, but assign to RGB pixels 163 | cv::Size diff_size = diff_image_compare.size(); 164 | for (int i = 0; i < diff_size.height; i++) 165 | { 166 | for (int j = 0; j < diff_size.width; j++) 167 | { 168 | float R2Y = 0.299; 169 | float G2Y = 0.587; 170 | float B2Y = 0.114; 171 | int B = diff_image_left.ptr(i, j)[0]; 172 | int G = diff_image_left.ptr(i, j)[1]; 173 | int R = diff_image_left.ptr(i, j)[2]; 174 | int gray = cv::saturate_cast(R2Y * R + G2Y * G + B2Y * B); 175 | 176 | diff_image_compare.ptr(i, j)[0] = gray; 177 | diff_image_compare.ptr(i, j)[1] = gray; 178 | diff_image_compare.ptr(i, j)[2] = gray; 179 | diff_image_compare.ptr(i, j)[3] = 255; 180 | } 181 | } 182 | is_exactly_same = true; 183 | } 184 | else 185 | { 186 | cv::Scalar above_color(0, 0, 255 - 50); 187 | cv::Scalar below_color(255 - 50, 0, 0); 188 | kantu::get_diff_image(diff_image_left, diff_image_right, diff_image_compare, toleranceThresh, below_color, above_color); 189 | is_exactly_same = false; 190 | } 191 | 192 | diff = image_compare.clone(); 193 | LOG(INFO) << fmt::format("Compare get pixel diff: ({:d}, {:d}, {:d}) with thresh {:d}\n", 194 | (int)pixel_diff.val[0], 195 | (int)pixel_diff.val[1], 196 | (int)pixel_diff.val[2], 197 | toleranceThresh); 198 | } 199 | 200 | return diff; 201 | } 202 | -------------------------------------------------------------------------------- /src/kantu/compare.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace kantu { 6 | 7 | void get_diff_image(const cv::Mat& src1, const cv::Mat& src2, cv::Mat& diff, int thresh, cv::Scalar& below, cv::Scalar& above); 8 | cv::Mat compare_two_mat(const cv::Mat& image_left, const cv::Mat& image_right, int toleranceThresh, bool& is_exactly_same); 9 | 10 | } // namespace kantu 11 | -------------------------------------------------------------------------------- /src/kantu/image.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace kantu { 7 | 8 | // https://github.com/FFmpeg/FFmpeg/blob/master/libavutil/pixfmt.h 9 | enum class PixelFormat 10 | { 11 | NONE = -1, 12 | //AV_PIX_FMT_YUV420P, // i420 13 | 14 | // 3/2 15 | NV12, 16 | NV21, 17 | I420, 18 | YV12, 19 | 20 | // 2 21 | UYVY, 22 | YUYV, 23 | YVYU, 24 | 25 | // 3 26 | I444, 27 | BGR24, 28 | RGB24, 29 | 30 | // 4 31 | BGRA32, 32 | RGBA32, 33 | 34 | // 1 35 | GRAY8, 36 | }; 37 | 38 | class FourccImage 39 | { 40 | public: 41 | PixelFormat format; 42 | std::vector planes; 43 | cv::Mat view1d; 44 | cv::Mat view2d; 45 | int height; 46 | int width; 47 | }; 48 | 49 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu/image_inspect.h: -------------------------------------------------------------------------------- 1 | // https://github.com/CedricGuillemet/imgInspect 2 | // 3 | // The MIT License(MIT) 4 | // 5 | // Copyright(c) 2019 Cedric Guillemet 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files(the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions : 13 | // 14 | // The above copyright notice and this permission notice shall be included in all 15 | // copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | // SOFTWARE. 24 | // 25 | /* 26 | example 27 | Image pickerImage; 28 | ImGui::ImageButton(pickerImage.textureID, ImVec2(pickerImage.mWidth, pickerImage.mHeight)); 29 | ImRect rc = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); 30 | ImVec2 mouseUVCoord = (io.MousePos - rc.Min) / rc.GetSize(); 31 | mouseUVCoord.y = 1.f - mouseUVCoord.y; 32 | 33 | 34 | 35 | 36 | 37 | 38 | if (io.KeyShift && io.MouseDown[0] && mouseUVCoord.x >= 0.f && mouseUVCoord.y >= 0.f) 39 | { 40 | int width = pickerImage.mWidth; 41 | int height = pickerImage.mHeight; 42 | 43 | imageInspect(width, height, pickerImage.GetBits(), mouseUVCoord, displayedTextureSize); 44 | } 45 | */ 46 | #pragma once 47 | 48 | namespace ImageInspect 49 | { 50 | inline void histogram(const int width, const int height, const unsigned char* const bits) 51 | { 52 | unsigned int count[4][256] = {{0}}; 53 | 54 | const unsigned char* ptrCols = bits; 55 | 56 | ImGui::InvisibleButton("histogram", ImVec2(512, 256)); 57 | for (int l = 0; l < height * width; l++) 58 | { 59 | count[0][*ptrCols++]++; 60 | count[1][*ptrCols++]++; 61 | count[2][*ptrCols++]++; 62 | count[3][*ptrCols++]++; 63 | } 64 | 65 | unsigned int maxv = count[0][0]; 66 | unsigned int* pCount = &count[0][0]; 67 | for (int i = 0; i < 3 * 256; i++, pCount++) 68 | { 69 | maxv = (maxv > *pCount) ? maxv : *pCount; 70 | } 71 | 72 | ImDrawList* drawList = ImGui::GetWindowDrawList(); 73 | const ImVec2 rmin = ImGui::GetItemRectMin(); 74 | const ImVec2 rmax = ImGui::GetItemRectMax(); 75 | const ImVec2 size = ImGui::GetItemRectSize(); 76 | const float hFactor = size.y / float(maxv); 77 | 78 | for (int i = 0; i <= 10; i++) 79 | { 80 | float ax = rmin.x + (size.x / 10.f) * float(i); 81 | float ay = rmin.y + (size.y / 10.f) * float(i); 82 | drawList->AddLine(ImVec2(rmin.x, ay), ImVec2(rmax.x, ay), 0x80808080); 83 | drawList->AddLine(ImVec2(ax, rmin.y), ImVec2(ax, rmax.y), 0x80808080); 84 | } 85 | 86 | const float barWidth = (size.x / 256.f); 87 | for (int j = 0; j < 256; j++) 88 | { 89 | // pixel count << 2 + color index(on 2 bits) 90 | uint32_t cols[3] = {(count[0][j] << 2), (count[1][j] << 2) + 1, (count[2][j] << 2) + 2}; 91 | if (cols[0] > cols[1]) 92 | ImSwap(cols[0], cols[1]); 93 | if (cols[1] > cols[2]) 94 | ImSwap(cols[1], cols[2]); 95 | if (cols[0] > cols[1]) 96 | ImSwap(cols[0], cols[1]); 97 | float heights[3]; 98 | uint32_t colors[3]; 99 | uint32_t currentColor = 0xFFFFFFFF; 100 | for (int i = 0; i < 3; i++) 101 | { 102 | heights[i] = rmax.y - (cols[i] >> 2) * hFactor; 103 | colors[i] = currentColor; 104 | currentColor -= 0xFF << ((cols[i] & 3) * 8); 105 | } 106 | 107 | float currentHeight = rmax.y; 108 | const float left = rmin.x + barWidth * float(j); 109 | const float right = left + barWidth; 110 | for (int i = 0; i < 3; i++) 111 | { 112 | if (heights[i] >= currentHeight) 113 | { 114 | continue; 115 | } 116 | drawList->AddRectFilled(ImVec2(left, currentHeight), ImVec2(right, heights[i]), colors[i]); 117 | currentHeight = heights[i]; 118 | } 119 | } 120 | } 121 | inline void drawNormal(ImDrawList* draw_list, const ImRect& rc, float x, float y) 122 | { 123 | draw_list->AddCircle(rc.GetCenter(), rc.GetWidth() / 2.f, 0x20AAAAAA, 24, 1.f); 124 | draw_list->AddCircle(rc.GetCenter(), rc.GetWidth() / 4.f, 0x20AAAAAA, 24, 1.f); 125 | draw_list->AddLine(rc.GetCenter(), rc.GetCenter() + ImVec2(x, y) * rc.GetWidth() / 2.f, 0xFF0000FF, 2.f); 126 | } 127 | 128 | inline void inspect(const int width, 129 | const int height, 130 | const unsigned char* const bits, 131 | ImVec2 mouseUVCoord, 132 | ImVec2 displayedTextureSize) 133 | { 134 | ImGui::BeginTooltip(); 135 | ImGui::BeginGroup(); 136 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 137 | static const float zoomRectangleWidth = 160.f; 138 | 139 | // bitmap zoom 140 | ImGui::InvisibleButton("AnotherInvisibleMan", ImVec2(zoomRectangleWidth, zoomRectangleWidth)); 141 | const ImRect pickRc(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); 142 | draw_list->AddRectFilled(pickRc.Min, pickRc.Max, 0xFF000000); 143 | static int zoomSize = 4; 144 | const float quadWidth = zoomRectangleWidth / float(zoomSize * 2 + 1); 145 | const ImVec2 quadSize(quadWidth, quadWidth); 146 | const int basex = ImClamp(int(mouseUVCoord.x * width), zoomSize, width - zoomSize); 147 | const int basey = ImClamp(int(mouseUVCoord.y * height), zoomSize, height - zoomSize); 148 | for (int y = -zoomSize; y <= zoomSize; y++) 149 | { 150 | for (int x = -zoomSize; x <= zoomSize; x++) 151 | { 152 | uint32_t texel = ((uint32_t*)bits)[(basey - y) * width + x + basex]; 153 | ImVec2 pos = pickRc.Min + ImVec2(float(x + zoomSize), float(y + zoomSize)) * quadSize; 154 | draw_list->AddRectFilled(pos, pos + quadSize, texel); 155 | } 156 | } 157 | ImGui::SameLine(); 158 | 159 | // center quad 160 | const ImVec2 pos = pickRc.Min + ImVec2(float(zoomSize), float(zoomSize)) * quadSize; 161 | draw_list->AddRect(pos, pos + quadSize, 0xFF0000FF, 0.f, 15, 2.f); 162 | 163 | // normal direction 164 | ImGui::InvisibleButton("AndOneMore", ImVec2(zoomRectangleWidth, zoomRectangleWidth)); 165 | ImRect normRc(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); 166 | for (int y = -zoomSize; y <= zoomSize; y++) 167 | { 168 | for (int x = -zoomSize; x <= zoomSize; x++) 169 | { 170 | uint32_t texel = ((uint32_t*)bits)[(basey - y) * width + x + basex]; 171 | const ImVec2 posQuad = normRc.Min + ImVec2(float(x + zoomSize), float(y + zoomSize)) * quadSize; 172 | //draw_list->AddRectFilled(pos, pos + quadSize, texel); 173 | const float nx = float(texel & 0xFF) / 128.f - 1.f; 174 | const float ny = float((texel & 0xFF00)>>8) / 128.f - 1.f; 175 | const ImRect rc(posQuad, posQuad + quadSize); 176 | drawNormal(draw_list, rc, nx, ny); 177 | } 178 | } 179 | 180 | 181 | 182 | ImGui::EndGroup(); 183 | ImGui::SameLine(); 184 | ImGui::BeginGroup(); 185 | uint32_t texel = ((uint32_t*)bits)[(basey - zoomSize * 2 - 1) * width + basex]; 186 | ImVec4 color = ImColor(texel); 187 | ImVec4 colHSV; 188 | ImGui::ColorConvertRGBtoHSV(color.x, color.y, color.z, colHSV.x, colHSV.y, colHSV.z); 189 | ImGui::Text("U %1.3f V %1.3f", mouseUVCoord.x, mouseUVCoord.y); 190 | ImGui::Text("Coord %d %d", int(mouseUVCoord.x * width), int(mouseUVCoord.y * height)); 191 | ImGui::Separator(); 192 | ImGui::Text("R 0x%02x G 0x%02x B 0x%02x", int(color.x * 255.f), int(color.y * 255.f), int(color.z * 255.f)); 193 | ImGui::Text("R %1.3f G %1.3f B %1.3f", color.x, color.y, color.z); 194 | ImGui::Separator(); 195 | ImGui::Text( 196 | "H 0x%02x S 0x%02x V 0x%02x", int(colHSV.x * 255.f), int(colHSV.y * 255.f), int(colHSV.z * 255.f)); 197 | ImGui::Text("H %1.3f S %1.3f V %1.3f", colHSV.x, colHSV.y, colHSV.z); 198 | ImGui::Separator(); 199 | ImGui::Text("Alpha 0x%02x", int(color.w * 255.f)); 200 | ImGui::Text("Alpha %1.3f", color.w); 201 | ImGui::Separator(); 202 | ImGui::Text("Size %d, %d", int(displayedTextureSize.x), int(displayedTextureSize.y)); 203 | ImGui::EndGroup(); 204 | histogram(width, height, bits); 205 | ImGui::EndTooltip(); 206 | } 207 | } // namespace ImageInspect 208 | -------------------------------------------------------------------------------- /src/kantu/image_io.cpp: -------------------------------------------------------------------------------- 1 | #include "kantu/image_io.hpp" 2 | #include "kantu/image.hpp" 3 | #include "kantu/transform_format.hpp" 4 | #include "kantu/string.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "mlcc/filefunc.h" 12 | #include "log.hpp" 13 | #include 14 | 15 | using namespace Shadow; 16 | 17 | /// @return BGR, BGRA or GRAY 18 | cv::Mat kantu::convert_fourcc_to_mat(const FourccImage& fourcc) 19 | { 20 | cv::Mat total = fourcc.view1d; 21 | 22 | int height = fourcc.height; 23 | int width = fourcc.width; 24 | 25 | cv::Mat mat; 26 | PixelFormat fmt = fourcc.format; 27 | 28 | switch (fmt) 29 | { 30 | case PixelFormat::NV21: 31 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_NV21); 32 | break; 33 | 34 | case PixelFormat::NV12: 35 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_NV12); 36 | break; 37 | 38 | case PixelFormat::I420: 39 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_I420); 40 | break; 41 | 42 | case PixelFormat::YV12: 43 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_YV12); 44 | break; 45 | 46 | case PixelFormat::UYVY: 47 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_UYVY); 48 | break; 49 | 50 | case PixelFormat::YUYV: 51 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_YUYV); 52 | break; 53 | 54 | case PixelFormat::YVYU: 55 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_YUV2BGR_YVYU); 56 | break; 57 | 58 | case PixelFormat::I444: 59 | { 60 | cv::Mat i444 = fourcc.view2d; 61 | #if 1 62 | mat.create(i444.size(), i444.type()); 63 | i444_to_rgb(i444.data, mat.data, width, height); 64 | cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); 65 | #else 66 | cv::cvtColor(yuv444_mat, image, cv::COLOR_YUV2BGR); 67 | // cv::Mat tmp; 68 | // chw_to_hwc(yuv444_mat, tmp); 69 | #endif 70 | break; 71 | } 72 | 73 | case PixelFormat::BGR24: 74 | mat = fourcc.view2d; 75 | break; 76 | 77 | case PixelFormat::RGB24: 78 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_RGB2BGR); 79 | break; 80 | 81 | case PixelFormat::BGRA32: 82 | mat = fourcc.view2d; 83 | break; 84 | 85 | case PixelFormat::RGBA32: 86 | cv::cvtColor(fourcc.view2d, mat, cv::COLOR_RGBA2BGRA); 87 | break; 88 | 89 | case PixelFormat::GRAY8: 90 | mat = fourcc.view2d; 91 | break; 92 | 93 | default: 94 | LOG(ERROR) << fmt::format("not supported format {:d}\n", (int)fmt); 95 | } 96 | 97 | return mat; 98 | } 99 | 100 | namespace { 101 | using namespace kantu; 102 | int get_expected_fourcc_file_size(std::string mapped_ext, int height, int width) 103 | { 104 | int expected_size = -1; 105 | PixelFormat fmt = get_pixel_format_from_file_ext(mapped_ext); 106 | switch (fmt) 107 | { 108 | case PixelFormat::NV21: 109 | case PixelFormat::NV12: 110 | case PixelFormat::I420: 111 | case PixelFormat::YV12: 112 | expected_size = height * width * 3 / 2; 113 | break; 114 | 115 | case PixelFormat::RGB24: 116 | case PixelFormat::BGR24: 117 | expected_size = height * width * 3; 118 | break; 119 | 120 | case PixelFormat::RGBA32: 121 | case PixelFormat::BGRA32: 122 | expected_size = height * width * 4; 123 | break; 124 | 125 | case PixelFormat::GRAY8: 126 | expected_size = height * width; 127 | break; 128 | 129 | case PixelFormat::UYVY: 130 | case PixelFormat::YUYV: 131 | case PixelFormat::YVYU: 132 | expected_size = height * width * 2; 133 | break; 134 | 135 | case PixelFormat::I444: 136 | expected_size = height * width * 3; 137 | break; 138 | 139 | default: 140 | LOG(INFO) << fmt::format("un handled lower_ext: {:s}\n", mapped_ext.c_str()); 141 | } 142 | 143 | return expected_size; 144 | } 145 | 146 | } // namespace 147 | 148 | namespace kantu { 149 | 150 | FourccFileInfo::FourccFileInfo(const FilePath& path) 151 | { 152 | ext = path.ext(); 153 | if (ext.length() < 3) 154 | { 155 | valid = false; 156 | err_msg = "no valid ext found in filepath"; 157 | return; 158 | } 159 | 160 | filepath = path.path(); 161 | head = path.basename(); 162 | 163 | mapped_ext = to_lower(ext); 164 | // convert raw extension (lowercase) to identical extension 165 | bool do_opencv_identical_ext_mapping = true; 166 | if (do_opencv_identical_ext_mapping) 167 | { 168 | if (mapped_ext == "yuv" || mapped_ext == "iyuv") 169 | { 170 | mapped_ext = "i420"; 171 | } 172 | else if (mapped_ext == "y422" || mapped_ext == "uynv") 173 | { 174 | mapped_ext = "uyvy"; 175 | } 176 | } 177 | 178 | bool do_yuvviewer_identical_ext_mapping = true; 179 | if (do_yuvviewer_identical_ext_mapping) 180 | { 181 | if (mapped_ext == "grey") 182 | { 183 | mapped_ext = "gray"; 184 | } 185 | else if (mapped_ext == "yv24") 186 | { 187 | mapped_ext = "i444"; 188 | } 189 | } 190 | 191 | /// validate filename's ext 192 | std::vector valid_ext = get_supported_image_file_exts(); 193 | int pos = kantu::find(valid_ext, mapped_ext); 194 | if (pos == -1) 195 | { 196 | err_msg = "invalid filename extension! Currently only supports these: (case insensitive) "; 197 | for (size_t i = 0; i < valid_ext.size(); i++) 198 | { 199 | err_msg = err_msg + " " + valid_ext[i]; 200 | } 201 | return; 202 | } 203 | 204 | // (ext=="nv21" || ext=="nv12" || ext=="bgr24" || ext=="rgb24") 205 | // valid format for head: 206 | // [prefix]_[width]x[height] 207 | // len(prefix)>0 208 | // len(width)>0 209 | // len(height)>0 210 | // find and validate `_` 211 | std::string::size_type underline_pos = head.find_last_of('_'); 212 | // LOG(INFO) << fmt::format("underline_pos = {:d}\n", underline_pos); 213 | if (underline_pos != std::string::npos && (int)underline_pos > static_cast(head.length()) - 4) 214 | { 215 | err_msg = "filename's head invalid. [prefix]_[width]x[height] required, `_` position invalid now"; 216 | return; 217 | } 218 | 219 | std::string dim_str = head.substr(underline_pos + 1); 220 | int non_digit_cnt = 0; 221 | int non_digit_pos = -1; 222 | for (size_t i = 0; i < dim_str.length(); i++) 223 | { 224 | if (!isdigit(dim_str[i])) 225 | { 226 | non_digit_cnt++; 227 | non_digit_pos = (int)i; 228 | } 229 | } 230 | // LOG(INFO) << (fmt::format("non_digit_cnt is {:d}\n", non_digit_cnt)); 231 | if ((non_digit_cnt != 1) || (dim_str[non_digit_pos] != 'x')) 232 | { 233 | err_msg = "filename's head invalid. [prefix]_[width]x[height] required, dim str wrong now"; 234 | return; 235 | } 236 | 237 | std::string width_str = dim_str.substr(0, non_digit_pos); 238 | std::string height_str = dim_str.substr(non_digit_pos + 1); 239 | width = std::stoi(width_str); 240 | height = std::stoi(height_str); 241 | 242 | int actual_size = kantu::get_file_size(filepath); 243 | int expected_size = get_expected_fourcc_file_size(mapped_ext, height, width); 244 | filesize = actual_size; 245 | 246 | if (expected_size != actual_size) 247 | { 248 | err_msg = "invalid file size, filename described different that actual"; 249 | LOG(ERROR) << fmt::format("expected_size: {:d}, actual_size: {:d}\n", expected_size, actual_size); 250 | return; 251 | } 252 | 253 | if (mapped_ext == "nv21" || mapped_ext == "nv12" || mapped_ext == "i420" || mapped_ext == "uyvy" || mapped_ext == "yuyv" || mapped_ext == "yv12" || mapped_ext == "yvyu") 254 | { 255 | if (height % 2 != 0 || width % 2 != 0) 256 | { 257 | err_msg = "file dimension invalid. both height and width should be even number"; 258 | return; 259 | } 260 | } 261 | valid = true; 262 | } 263 | 264 | } // namespace kantu 265 | 266 | int kantu::get_file_size(const Str256& filepath) 267 | { 268 | std::filesystem::path p{filepath.c_str()}; 269 | return (int)std::filesystem::file_size(p); 270 | } 271 | 272 | static cv::Mat convert_mat_to_bgra(const cv::Mat& src) 273 | { 274 | cv::Mat dst = src; 275 | switch (src.channels()) 276 | { 277 | case 1: 278 | cv::cvtColor(src, dst, cv::COLOR_GRAY2BGRA); 279 | break; 280 | case 3: 281 | cv::cvtColor(src, dst, cv::COLOR_BGR2BGRA); 282 | break; 283 | case 4: 284 | dst = src; 285 | default: 286 | LOG(ERROR) << "only 1, 3, 4 channel image supported\n"; 287 | } 288 | return dst; 289 | } 290 | 291 | /// displayable means `bgra` 292 | cv::Mat kantu::load_as_displayable_image(const std::string& image_path) 293 | { 294 | cv::Mat empty_image; 295 | 296 | std::filesystem::path image_path_ = image_path; 297 | if (!std::filesystem::exists(image_path_)) 298 | { 299 | LOG(ERROR) << fmt::format("file {:s} does not exist\n", image_path.c_str()); 300 | return empty_image; 301 | } 302 | 303 | FilePath path(image_path); 304 | std::string lower_ext = to_lower(path.ext()); 305 | int pos = kantu::find({ "jpg", "jpeg", "png", "bmp" }, lower_ext); 306 | if (pos != -1) 307 | { 308 | cv::Mat image = cv::imread(image_path, cv::IMREAD_UNCHANGED); 309 | return convert_mat_to_bgra(image); 310 | } 311 | 312 | FourccFileInfo file_info(path); 313 | bool valid = file_info.valid; 314 | if (!valid) 315 | { 316 | LOG(ERROR) << fmt::format("{:s}\n", file_info.err_msg.c_str()); 317 | return empty_image; 318 | } 319 | else 320 | { 321 | LOG(INFO) << fmt::format("reading file {:s}\n", image_path.c_str()); 322 | FourccImage fourcc = load_fourcc(file_info); 323 | cv::Mat image = convert_fourcc_to_mat(fourcc); 324 | return convert_mat_to_bgra(image); 325 | } 326 | } 327 | 328 | std::vector kantu::get_supported_image_file_exts() 329 | { 330 | static std::vector exts = { 331 | "jpg", 332 | "jpeg", 333 | "png", 334 | "bmp", 335 | 336 | "rgb24", 337 | "bgr24", 338 | "rgba32", 339 | "bgra32", 340 | "gray", "grey", 341 | 342 | "nv21", // yuv420sp 343 | "nv12", 344 | "i420", 345 | "iyuv", // opencv 346 | "yuv", // yuvviewer 347 | 348 | "uyvy", 349 | "y422", // opencv 350 | "uynv", // opencv 351 | 352 | "yuyv", 353 | "yunv", // opencv 354 | "yuy2", // opencv 355 | 356 | "yv12", // yuv420p 357 | "yvyu", 358 | 359 | // not supported yet 360 | "i444", 361 | "yv24", 362 | 363 | "uyvy2", 364 | "vyuy", 365 | "vyuy2", 366 | "yuyv2", 367 | "i422h", 368 | "yv16h", 369 | "i422v", 370 | "yv16v", 371 | "lpi422h", 372 | "yvu", 373 | "uvy", 374 | "vuy", 375 | }; 376 | 377 | // YUVviewer supported: 378 | // I444, YV24, NV12, NV21, I420, YV12, UYVY, UYVY2, VYUY, VYUY2, YUYV, YUYV2, YVYU, YVYU2, I422H, YV16H, I422V, YV16V, LPI422H, YUV, YVU, UVY, VUY, Gray, Grey 379 | 380 | return exts; 381 | } 382 | 383 | 384 | 385 | FourccImage kantu::load_fourcc(const FourccFileInfo& file_info) 386 | { 387 | FourccImage fourcc; 388 | 389 | std::ifstream fs(file_info.filepath, std::ios::in | std::ios::binary); 390 | cv::Mat view1d(1, file_info.filesize, CV_8UC1); 391 | fs.read(reinterpret_cast(view1d.data), file_info.filesize); 392 | 393 | int height = file_info.height; 394 | int width = file_info.width; 395 | fourcc.view1d = view1d; 396 | fourcc.height = height; 397 | fourcc.width = width; 398 | 399 | PixelFormat fmt = get_pixel_format_from_file_ext(file_info.mapped_ext); 400 | fourcc.format = fmt; 401 | switch (fmt) 402 | { 403 | case PixelFormat::NV21: 404 | case PixelFormat::NV12: 405 | { 406 | fourcc.view2d = view1d.reshape(1, height * 3 / 2); 407 | 408 | cv::Range y_range(0, height * width); 409 | cv::Range uv_range(y_range.end, (int)view1d.total()); 410 | cv::Mat Y = view1d.colRange(y_range).reshape(1, height); 411 | cv::Mat UV = view1d.colRange(uv_range).reshape(2, height / 2); 412 | fourcc.planes.emplace_back(Y); 413 | fourcc.planes.emplace_back(UV); 414 | break; 415 | } 416 | 417 | case PixelFormat::I420: 418 | { 419 | fourcc.view2d = view1d.reshape(1, height * 3 / 2); 420 | 421 | cv::Range y_range(0, height * width); 422 | cv::Range u_range(y_range.end, y_range.end + (height / 2) * (width / 2)); 423 | cv::Range v_range(u_range.end, (int)view1d.total()); 424 | cv::Mat Y = view1d.colRange(y_range).reshape(1, height); 425 | cv::Mat U = view1d.colRange(u_range).reshape(2, height / 2); 426 | cv::Mat V = view1d.colRange(v_range).reshape(2, height / 2); 427 | fourcc.planes.emplace_back(Y); 428 | fourcc.planes.emplace_back(U); 429 | fourcc.planes.emplace_back(V); 430 | break; 431 | } 432 | case PixelFormat::YV12: 433 | { 434 | fourcc.view2d = view1d.reshape(1, height * 3 / 2); 435 | 436 | cv::Range y_range(0, height * width); 437 | cv::Range v_range(y_range.end, y_range.end + (height / 2) * (width / 2)); 438 | cv::Range u_range(v_range.end, (int)view1d.total()); 439 | cv::Mat Y = view1d.colRange(y_range).reshape(1, height); 440 | cv::Mat V = view1d.colRange(v_range).reshape(2, height / 2); 441 | cv::Mat U = view1d.colRange(u_range).reshape(2, height / 2); 442 | fourcc.planes.emplace_back(Y); 443 | fourcc.planes.emplace_back(V); 444 | fourcc.planes.emplace_back(U); 445 | break; 446 | } 447 | 448 | case PixelFormat::UYVY: 449 | case PixelFormat::YUYV: 450 | case PixelFormat::YVYU: 451 | { 452 | fourcc.view2d = view1d.reshape(2, height); 453 | fourcc.planes.emplace_back(fourcc.view2d); 454 | break; 455 | } 456 | 457 | case PixelFormat::I444: 458 | { 459 | fourcc.view2d = view1d.reshape(3, height); 460 | fourcc.planes.emplace_back(fourcc.view2d); 461 | break; 462 | } 463 | 464 | case PixelFormat::BGR24: 465 | case PixelFormat::RGB24: 466 | { 467 | fourcc.view2d = view1d.reshape(3, height); 468 | fourcc.planes.emplace_back(fourcc.view2d); 469 | break; 470 | } 471 | 472 | case PixelFormat::BGRA32: 473 | case PixelFormat::RGBA32: 474 | { 475 | fourcc.view2d = view1d.reshape(4, height); 476 | fourcc.planes.emplace_back(fourcc.view2d); 477 | break; 478 | } 479 | 480 | case PixelFormat::GRAY8: 481 | { 482 | fourcc.view2d = view1d.reshape(1, height); 483 | fourcc.planes.emplace_back(fourcc.view2d); 484 | break; 485 | } 486 | 487 | default: 488 | LOG(ERROR) << "not supported or not handled format\n"; 489 | 490 | } 491 | 492 | return fourcc; 493 | } 494 | 495 | PixelFormat kantu::get_pixel_format_from_file_ext(const std::string& ext) 496 | { 497 | std::unordered_map mp = { 498 | { "nv21", PixelFormat::NV21 }, 499 | { "nv12", PixelFormat::NV12 }, 500 | { "i420", PixelFormat::I420 }, 501 | { "yv12", PixelFormat::YV12 }, 502 | 503 | { "uyvy", PixelFormat::UYVY }, 504 | { "yuyv", PixelFormat::YUYV }, 505 | { "yvyu", PixelFormat::YVYU }, 506 | 507 | { "i444", PixelFormat::I444 }, 508 | 509 | { "rgb24", PixelFormat::RGB24 }, 510 | { "bgr24", PixelFormat::BGR24 }, 511 | 512 | { "bgra32", PixelFormat::BGRA32 }, 513 | { "rgba32", PixelFormat::RGBA32 }, 514 | 515 | { "gray", PixelFormat::GRAY8 }, 516 | }; 517 | 518 | if (mp.count(ext)) 519 | { 520 | return mp[ext]; 521 | } 522 | LOG(ERROR) << fmt::format("Un-supported image file extension {:s} for mapping\n", ext.c_str()); 523 | return PixelFormat::NONE; 524 | } -------------------------------------------------------------------------------- /src/kantu/image_io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kantu/image.hpp" 4 | #include 5 | #include "str/Str.h" 6 | #include "kantu/string.hpp" 7 | 8 | namespace kantu { 9 | 10 | class FourccFileInfo 11 | { 12 | public: 13 | FourccFileInfo() = default; 14 | explicit FourccFileInfo(const FilePath& path); 15 | 16 | public: 17 | std::string filepath; 18 | std::string head; 19 | std::string ext; // same as file 20 | std::string mapped_ext; // converted to lowercase, then mapping to identical one 21 | int height = 0; 22 | int width = 0; 23 | bool valid = false; 24 | std::string err_msg; 25 | int filesize = 0; 26 | }; 27 | 28 | FourccImage load_fourcc(const FourccFileInfo& file_info); 29 | PixelFormat get_pixel_format_from_file_ext(const std::string& ext); 30 | cv::Mat convert_fourcc_to_mat(const FourccImage& tu); 31 | std::vector get_supported_image_file_exts(); 32 | cv::Mat load_as_displayable_image(const std::string& image_path); 33 | int get_file_size(const Str256& filepath); 34 | 35 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu/image_render.cpp: -------------------------------------------------------------------------------- 1 | #include "kantu/image_render.hpp" 2 | #include "kantu/image_io.hpp" 3 | #include "kantu/log.hpp" 4 | 5 | using namespace Shadow; 6 | 7 | GLuint kantu::get_texture_from_image(const cv::Mat& image) 8 | { 9 | cv::Mat im0 = image; 10 | #if _MSC_VER 11 | im0 = image.clone(); 12 | #endif 13 | 14 | // Create a OpenGL texture identifier 15 | GLuint image_texture; 16 | glGenTextures(1, &image_texture); 17 | glBindTexture(GL_TEXTURE_2D, image_texture); 18 | 19 | // Setup filtering parameters for display 20 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 21 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 22 | #if !_MSC_VER 23 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // This is required on WebGL for non power-of-two textures 24 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Same 25 | #endif 26 | // Upload pixels into texture 27 | #if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__) 28 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 29 | #endif 30 | 31 | const int channels = image.channels(); 32 | GLint internalformat = 0; 33 | GLenum format = 0; 34 | bool valid_format = true; 35 | if (channels == 4) 36 | { 37 | internalformat = GL_RGBA; 38 | #if _MSC_VER 39 | format = GL_RGBA; 40 | cvtColor(im0, im0, cv::COLOR_BGRA2RGBA); 41 | #else 42 | format = GL_BGRA; 43 | #endif 44 | } 45 | else if (channels == 3) 46 | { 47 | internalformat = GL_RGB; 48 | #if _MSC_VER 49 | format = GL_RGB; 50 | cvtColor(im0, im0, cv::COLOR_BGR2RGB); 51 | #else 52 | format = GL_BGR; 53 | #endif 54 | } 55 | else if (channels == 1) 56 | { 57 | internalformat = GL_LUMINANCE; 58 | format = GL_LUMINANCE; 59 | } 60 | else 61 | { 62 | valid_format = false; 63 | LOG(ERROR) << "only support 1, 3, 4 channels\n"; 64 | } 65 | 66 | if (valid_format) 67 | glTexImage2D(GL_TEXTURE_2D, 0, internalformat, im0.size().width, im0.size().height, 0, format, GL_UNSIGNED_BYTE, im0.data); 68 | 69 | return image_texture; 70 | } 71 | 72 | namespace kantu { 73 | 74 | void RichImage::load_from_file(const Str256& filepath) 75 | { 76 | //cv::Mat mat = cv::imread(filepath.c_str(), cv::IMREAD_UNCHANGED); 77 | std::string imagepath = filepath.c_str(); 78 | cv::Mat mat = load_as_displayable_image(imagepath); 79 | if (mat.empty()) return; 80 | switch (mat.channels()) 81 | { 82 | case 1: 83 | cv::cvtColor(mat, mat, cv::COLOR_GRAY2BGRA); 84 | break; 85 | case 3: 86 | cv::cvtColor(mat, mat, cv::COLOR_BGR2BGRA); 87 | break; 88 | case 4: 89 | // NOP 90 | break; 91 | default: 92 | LOG(ERROR) << "only support 1, 3, 4 channels\n"; // TODO: fix me. consider encapsulate cv::imread(), support 2 channels. 93 | } 94 | load_mat(mat); 95 | set_name(filepath); 96 | filesize = kantu::get_file_size(filepath); 97 | } 98 | 99 | void RichImage::reload() 100 | { 101 | if (name.length() > 0) 102 | { 103 | load_from_file(name.c_str()); 104 | } 105 | } 106 | 107 | void RichImage::load_mat(cv::Mat& frame) 108 | { 109 | if (frame.empty()) 110 | return; 111 | 112 | if (!texture) 113 | { 114 | open = true; 115 | mat = frame; // maybe i should copy that frame (clone it) 116 | texture = get_texture_from_image(mat); 117 | } 118 | else 119 | { 120 | //printf("We HAVE SOMETHING AT TEXTURE\n"); 121 | clear(); 122 | open = true; 123 | mat = frame; 124 | texture = get_texture_from_image(mat); 125 | } 126 | } 127 | 128 | void RichImage::update_mat(cv::Mat& frame, bool change_color_order) 129 | { 130 | // if does not have the same size then i need to recreate the texture from scratch 131 | //could be something like 132 | if (!(frame.size() == mat.size())) 133 | { 134 | clear(); 135 | mat.release(); // maybe this is redundant 136 | load_mat(frame); 137 | } 138 | 139 | if (change_color_order) 140 | cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); 141 | 142 | // image must have same size 143 | glBindTexture(GL_TEXTURE_2D, texture); 144 | glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame.cols, frame.rows, GL_RGB, 145 | GL_UNSIGNED_BYTE, frame.ptr()); 146 | } 147 | 148 | // clear texture and release all memory associated with it 149 | void RichImage::clear() 150 | { 151 | glDeleteTextures(1, &texture); 152 | texture = 0; 153 | } 154 | 155 | GLuint RichImage::get_texture() const 156 | { 157 | return texture; 158 | } 159 | 160 | bool* RichImage::get_open() 161 | { 162 | return &open; 163 | } 164 | 165 | void RichImage::set_name(const Str256& _name) 166 | { 167 | name = _name.c_str(); 168 | } 169 | 170 | //then GetName 171 | const char* RichImage::get_name() const 172 | { 173 | return name.c_str(); 174 | } 175 | 176 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu/image_render.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "str/Str.h" 6 | 7 | namespace kantu { 8 | 9 | GLuint get_texture_from_image(const cv::Mat& image); 10 | 11 | class RichImage 12 | { 13 | public: 14 | GLuint texture; 15 | cv::Mat mat; 16 | bool open; 17 | std::string name; 18 | int filesize; 19 | 20 | public: 21 | RichImage() 22 | : texture(0), open(false) 23 | { 24 | } 25 | 26 | void load_from_file(const Str256& filepath); 27 | void reload(); 28 | void load_mat(cv::Mat& frame); 29 | void update_mat(cv::Mat& frame, bool change_color_order = false); 30 | void clear(); // clear texture and release all memory associated with it 31 | GLuint get_texture() const; 32 | bool* get_open(); 33 | void set_name(const Str256& _name); 34 | const char* get_name() const; 35 | }; 36 | 37 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu/image_viewer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef STR_IMPLEMENTATION 5 | #define STR_IMPLEMENTATION 1 6 | #endif 7 | #include "kantu/image_io.hpp" 8 | #include "kantu/log.hpp" 9 | #include 10 | 11 | using namespace Shadow; 12 | 13 | static void help(const char* exe_name) 14 | { 15 | LOG(INFO) << fmt::format("Usage: {:s} image_path\n", exe_name); 16 | } 17 | 18 | int main(int argc, char** argv) 19 | { 20 | if (argc != 2) 21 | { 22 | help(argv[0]); 23 | return 1; 24 | } 25 | 26 | std::string filename = argv[1]; 27 | 28 | cv::Mat image = kantu::load_as_displayable_image(filename); 29 | const std::string& win_name = filename; 30 | cv::namedWindow(win_name, cv::WINDOW_NORMAL); 31 | cv::resizeWindow(win_name, cv::Size{600, 800}); 32 | cv::imshow(win_name, image); 33 | //cv::imwrite("result.png", image); 34 | cv::waitKey(0); 35 | 36 | return 0; 37 | } -------------------------------------------------------------------------------- /src/kantu/log.hpp: -------------------------------------------------------------------------------- 1 | // based on https://github.com/junluan/shadow/blob/master/shadow/util/log.hpp 2 | #ifndef SHADOW_UTIL_LOG_HPP_ 3 | #define SHADOW_UTIL_LOG_HPP_ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #if defined(__linux__) || defined(__APPLE__) 13 | #include 14 | #if defined(__ANDROID__) || defined(ANDROID) 15 | #include 16 | #else 17 | #include 18 | #endif 19 | #endif 20 | 21 | namespace Shadow { 22 | 23 | #define SLOG_INFO LogMessage("INFO", __FILE__, __LINE__) 24 | #define SLOG_WARNING LogMessage("WARNING", __FILE__, __LINE__) 25 | #define SLOG_ERROR LogMessage("ERROR", __FILE__, __LINE__) 26 | #define SLOG_FATAL LogMessage("FATAL", __FILE__, __LINE__) 27 | 28 | #define LOG(severity) SLOG_##severity.stream() 29 | #define LOG_IF(severity, condition) \ 30 | if ((condition)) LOG(severity) 31 | 32 | #define CHECK_NOTNULL(condition) \ 33 | if ((condition) == nullptr) \ 34 | LOG(FATAL) << "Check Failed: '" #condition << "' Must be non NULL " 35 | 36 | #define CHECK(condition) \ 37 | if (!(condition)) LOG(FATAL) << "Check Failed: " #condition << " " 38 | 39 | #define CHECK_OP(condition, val1, val2) \ 40 | if (!((val1)condition(val2))) \ 41 | LOG(FATAL) << "Check Failed: " #val1 " " #condition " " #val2 " (" << (val1) \ 42 | << " vs " << (val2) << ") " 43 | 44 | #define CHECK_EQ(val1, val2) CHECK_OP(==, val1, val2) 45 | #define CHECK_NE(val1, val2) CHECK_OP(!=, val1, val2) 46 | #define CHECK_LE(val1, val2) CHECK_OP(<=, val1, val2) 47 | #define CHECK_LT(val1, val2) CHECK_OP(<, val1, val2) 48 | #define CHECK_GE(val1, val2) CHECK_OP(>=, val1, val2) 49 | #define CHECK_GT(val1, val2) CHECK_OP(>, val1, val2) 50 | 51 | #if defined(NDEBUG) 52 | #define DLOG(severity) \ 53 | while (false) LOG(severity) 54 | #define DLOG_IF(severity, condition) \ 55 | while (false) LOG_IF(severity, condition) 56 | 57 | #define DCHECK(condition) \ 58 | while (false) CHECK(condition) 59 | #define DCHECK_EQ(val1, val2) \ 60 | while (false) CHECK_EQ(val1, val2) 61 | #define DCHECK_NE(val1, val2) \ 62 | while (false) CHECK_NE(val1, val2) 63 | #define DCHECK_LE(val1, val2) \ 64 | while (false) CHECK_LE(val1, val2) 65 | #define DCHECK_LT(val1, val2) \ 66 | while (false) CHECK_LT(val1, val2) 67 | #define DCHECK_GE(val1, val2) \ 68 | while (false) CHECK_GE(val1, val2) 69 | #define DCHECK_GT(val1, val2) \ 70 | while (false) CHECK_GT(val1, val2) 71 | 72 | #else 73 | #define DLOG(severity) LOG(severity) 74 | #define DLOG_IF(severity, condition) LOG_IF(severity, condition) 75 | 76 | #define DCHECK(condition) CHECK(condition) 77 | #define DCHECK_EQ(val1, val2) CHECK_EQ(val1, val2) 78 | #define DCHECK_NE(val1, val2) CHECK_NE(val1, val2) 79 | #define DCHECK_LE(val1, val2) CHECK_LE(val1, val2) 80 | #define DCHECK_LT(val1, val2) CHECK_LT(val1, val2) 81 | #define DCHECK_GE(val1, val2) CHECK_GE(val1, val2) 82 | #define DCHECK_GT(val1, val2) CHECK_GT(val1, val2) 83 | #endif 84 | 85 | class LogMessage { 86 | public: 87 | LogMessage(const std::string& severity, const char* file, int line) 88 | : severity_(severity) { 89 | std::string file_str(file); 90 | const auto& file_name = file_str.substr(file_str.find_last_of("/\\") + 1); 91 | stream_ << severity << ": " << file_name << ":" << line << "] "; 92 | } 93 | ~LogMessage() noexcept(false) { 94 | const auto& msg = stream_.str(); 95 | #if defined(__ANDROID__) || defined(ANDROID) 96 | if (severity_ == "INFO") { 97 | __android_log_print(ANDROID_LOG_INFO, "native", "%s\n", msg.c_str()); 98 | } else if (severity_ == "WARNING") { 99 | __android_log_print(ANDROID_LOG_WARN, "native", "%s\n", msg.c_str()); 100 | } else if (severity_ == "ERROR") { 101 | __android_log_print(ANDROID_LOG_ERROR, "native", "%s\n", msg.c_str()); 102 | } else if (severity_ == "FATAL") { 103 | __android_log_print(ANDROID_LOG_FATAL, "native", "%s\n", msg.c_str()); 104 | } 105 | #else 106 | if (severity_ == "INFO") { 107 | std::cout << msg << std::endl; 108 | } else { 109 | std::cerr << msg << std::endl; 110 | } 111 | #endif 112 | if (severity_ == "FATAL") { 113 | const auto& trace = stack_trace(0); 114 | if (!trace.empty()) { 115 | std::cerr << trace; 116 | } 117 | throw std::runtime_error(msg); 118 | } 119 | } 120 | 121 | std::stringstream& stream() { return stream_; } 122 | 123 | LogMessage(const LogMessage&) = delete; 124 | void operator=(const LogMessage&) = delete; 125 | 126 | private: 127 | static std::string demangle(const char* symbol) { 128 | std::string symbol_str(symbol); 129 | #if defined(__linux__) || defined(__APPLE__) 130 | auto func_start = symbol_str.find("_Z"); 131 | if (func_start != std::string::npos) { 132 | auto func_end = symbol_str.find_first_of(" +", func_start); 133 | const auto& func_symbol = 134 | symbol_str.substr(func_start, func_end - func_start); 135 | int status = 0; 136 | size_t length = 0; 137 | auto demangle_func_symbol = 138 | abi::__cxa_demangle(func_symbol.c_str(), nullptr, &length, &status); 139 | if (demangle_func_symbol != nullptr && status == 0 && length > 0) { 140 | symbol_str = symbol_str.substr(0, func_start) + demangle_func_symbol + 141 | symbol_str.substr(func_end); 142 | } 143 | free(demangle_func_symbol); 144 | } 145 | #endif 146 | return symbol_str; 147 | } 148 | 149 | static std::string stack_trace(int start_frame, int stack_size = 20) { 150 | std::stringstream ss; 151 | #if defined(__linux__) || defined(__APPLE__) 152 | #if !defined(__ANDROID__) && !defined(ANDROID) 153 | std::vector stack(stack_size, nullptr); 154 | stack_size = backtrace(stack.data(), stack_size); 155 | ss << "Stack trace:" << std::endl; 156 | auto symbols = backtrace_symbols(stack.data(), stack_size); 157 | if (symbols != nullptr) { 158 | for (int n = start_frame; n < stack_size; ++n) { 159 | const auto& demangle_symbol = demangle(symbols[n]); 160 | if (demangle_symbol.find("LogMessage") == std::string::npos) { 161 | ss << " [bt] " << demangle_symbol << std::endl; 162 | } 163 | } 164 | } 165 | free(symbols); 166 | #endif 167 | #endif 168 | return ss.str(); 169 | } 170 | 171 | std::string severity_; 172 | std::stringstream stream_; 173 | }; 174 | 175 | } // namespace Shadow 176 | 177 | #endif // SHADOW_UTIL_LOG_HPP_ 178 | -------------------------------------------------------------------------------- /src/kantu/string.cpp: -------------------------------------------------------------------------------- 1 | #include "kantu/string.hpp" 2 | 3 | std::string kantu::to_lower(const std::string& str) 4 | { 5 | std::string lower_str = str; 6 | for (size_t i = 0; i < str.size(); i++) 7 | { 8 | if (isalpha(str[i])) 9 | { 10 | lower_str[i] = (char)tolower(str[i]); 11 | } 12 | } 13 | return lower_str; 14 | } 15 | 16 | std::string& kantu::replace_all(std::string& src, const std::string& old_value, const std::string& new_value) 17 | { 18 | for (std::string::size_type pos(0); pos != std::string::npos; pos += new_value.length()) 19 | { 20 | if ((pos = src.find(old_value, pos)) != std::string::npos) 21 | { 22 | src.replace(pos, old_value.length(), new_value); 23 | } 24 | else 25 | { 26 | break; 27 | } 28 | } 29 | return src; 30 | } -------------------------------------------------------------------------------- /src/kantu/string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace kantu { 8 | 9 | std::string to_lower(const std::string& str); 10 | std::string& replace_all(std::string& src, const std::string& old_value, const std::string& new_value); 11 | 12 | 13 | class FilePath 14 | { 15 | public: 16 | FilePath(const std::string& filepath) : 17 | m_path(filepath), 18 | m_directory(""), 19 | m_filename(""), 20 | m_basename(""), 21 | m_ext("") 22 | { 23 | get_directory_and_filename(); 24 | get_basename_and_ext(); 25 | } 26 | 27 | private: 28 | void get_directory_and_filename() 29 | { 30 | int pos = (int)m_path.length() - 1; 31 | for (; pos >= 0; pos--) 32 | { 33 | if (m_path[pos] == '/' || m_path[pos] == '\\') 34 | { 35 | break; 36 | } 37 | } 38 | if (pos == -1) 39 | { 40 | m_filename = m_path; 41 | } 42 | else 43 | { 44 | m_directory = m_path.substr(0, pos); 45 | m_filename = m_path.substr(pos + 1); 46 | } 47 | } 48 | 49 | void get_basename_and_ext() 50 | { 51 | std::string::size_type pos = m_filename.find_last_of('.'); 52 | if (pos == std::string::npos) 53 | { 54 | m_basename = m_filename; 55 | } 56 | else 57 | { 58 | m_basename = m_filename.substr(0, pos); 59 | m_ext = m_filename.substr(pos + 1); 60 | } 61 | } 62 | 63 | public: 64 | [[nodiscard]] std::string path() const 65 | { 66 | return m_path; 67 | } 68 | 69 | [[nodiscard]] std::string directory() const 70 | { 71 | return m_directory; 72 | } 73 | 74 | [[nodiscard]] std::string filename() const 75 | { 76 | return m_filename; 77 | } 78 | 79 | [[nodiscard]] std::string basename() const 80 | { 81 | return m_basename; 82 | } 83 | 84 | [[nodiscard]] std::string ext() const 85 | { 86 | return m_ext; 87 | } 88 | 89 | private: 90 | // path = [directory/]filename 91 | std::string m_path; 92 | std::string m_directory; 93 | 94 | // filename = basename.ext 95 | std::string m_filename; 96 | 97 | std::string m_basename; 98 | std::string m_ext; 99 | }; 100 | 101 | template 102 | int find(const std::vector& vec, const T& target) 103 | { 104 | int pos = -1; 105 | typename std::vector::const_iterator iter = std::find(vec.begin(), vec.end(), target); 106 | if (iter != vec.end()) 107 | { 108 | pos = (int)std::distance(vec.begin(), iter); 109 | } 110 | return pos; 111 | } 112 | 113 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu/transform_format.cpp: -------------------------------------------------------------------------------- 1 | #include "kantu/transform_format.hpp" 2 | #include "kantu/log.hpp" 3 | #include 4 | 5 | using namespace Shadow; 6 | 7 | void kantu::chw_to_hwc(const cv::Mat& src, cv::Mat& dst) 8 | { 9 | if (src.depth() != CV_8U) 10 | { 11 | LOG(ERROR) << "error: src's depth() should be CV_8U\n"; 12 | return; 13 | } 14 | if (src.channels() != 3) 15 | { 16 | LOG(ERROR) << "error: currently only support 3 channel\n"; 17 | return; 18 | } 19 | dst.create(src.size(), src.type()); 20 | 21 | int width = src.cols; 22 | int height = src.rows; 23 | const int channels = src.channels(); 24 | uint8_t* rgb = dst.data; 25 | 26 | const int chw_pitch = width; 27 | 28 | const uint8_t* chw_plane0 = src.data; 29 | const uint8_t* chw_plane1 = src.data + width * height; 30 | const uint8_t* chw_plane2 = src.data + width * height * 2; 31 | 32 | const int dstPitch = width * channels; 33 | for (int i = 0; i < height; i++) 34 | { 35 | const uint8_t* pY = chw_plane0 + i * chw_pitch; 36 | const uint8_t* pU = chw_plane1 + i * chw_pitch; 37 | const uint8_t* pV = chw_plane2 + i * chw_pitch; 38 | 39 | uint8_t* prgb = rgb + i * dstPitch; 40 | for (int j = 0; j < width; j++) 41 | { 42 | uint8_t y, u, v; 43 | y = *pY++; 44 | u = *pU++; 45 | v = *pV++; 46 | *prgb++ = y; 47 | *prgb++ = u; 48 | *prgb++ = v; 49 | } 50 | } 51 | 52 | cv::cvtColor(dst, dst, cv::COLOR_BGR2RGB); 53 | } 54 | 55 | void kantu::my_chw_to_hwc(cv::InputArray src, cv::OutputArray dst) { 56 | const auto& src_size = src.getMat().size; 57 | const int src_c = src_size[0]; 58 | const int src_h = src_size[1]; 59 | const int src_w = src_size[2]; 60 | 61 | auto c_hw = src.getMat().reshape(0, {src_c, src_h * src_w}); 62 | 63 | dst.create(src_h, src_w, CV_MAKETYPE(src.depth(), src_c)); 64 | cv::Mat dst_1d = dst.getMat().reshape(src_c, {src_h, src_w}); 65 | 66 | cv::transpose(c_hw, dst_1d); 67 | } 68 | 69 | 70 | // behave same as opencv(exclude carotene) 71 | static void RGBfromYUV_BT601_u8(uint8_t& R, uint8_t& G, uint8_t& B, uint8_t Y, uint8_t U, uint8_t V) 72 | { 73 | // double dR, dG, dB; 74 | // double dY = Y; 75 | // double dU = U; 76 | // double dV = V; 77 | // RGBfromYUV_BT601(dR, dG, dB, dY, dU, dV); 78 | 79 | // R = dR; 80 | // G = dG; 81 | // B = dB; 82 | 83 | static const int ITUR_BT_601_CY = 1220542; 84 | static const int ITUR_BT_601_CUB = 2116026; 85 | static const int ITUR_BT_601_CUG = -409993; 86 | static const int ITUR_BT_601_CVG = -852492; 87 | static const int ITUR_BT_601_CVR = 1673527; 88 | static const int ITUR_BT_601_SHIFT = 20; 89 | 90 | 91 | #define CLIP_TO_UCHAR(x) (uint8_t)((x) < 0 ? 0 : ((x) > 255 ? 255 : (x))) 92 | 93 | int u = U - 128;/// 94 | int v = V - 128;/// 95 | 96 | int shift = 1 << (ITUR_BT_601_SHIFT - 1); 97 | int ruv = shift + ITUR_BT_601_CVR * v; 98 | int guv = shift + ITUR_BT_601_CVG * v + ITUR_BT_601_CUG * u; 99 | int buv = shift + ITUR_BT_601_CUB * u; 100 | 101 | 102 | int y00 = std::max(0, Y-16) * ITUR_BT_601_CY; 103 | R = CLIP_TO_UCHAR( (y00 + ruv) >> ITUR_BT_601_SHIFT ); 104 | G = CLIP_TO_UCHAR( (y00 + guv) >> ITUR_BT_601_SHIFT ); 105 | B = CLIP_TO_UCHAR( (y00 + buv) >> ITUR_BT_601_SHIFT ); 106 | 107 | #undef CLIP_TO_UCHAR 108 | } 109 | 110 | 111 | void kantu::i444_to_rgb(uint8_t* i444, uint8_t* rgb, int height, int width) 112 | { 113 | uint8_t* src = i444; 114 | uint8_t* dst = rgb; 115 | const int w = width; 116 | const int h = height; 117 | 118 | LOG(INFO) << fmt::format("!! calling {:s}\n", __FUNCTION__); 119 | 120 | for (int i = 0; i < height; i++) 121 | { 122 | for (int j = 0; j < width; j++) 123 | { 124 | uint8_t y = src[0 * w * h + i * w + j]; 125 | uint8_t u = src[1 * w * h + i * w + j]; 126 | uint8_t v = src[2 * w * h + i * w + j]; 127 | uint8_t r, g, b; 128 | RGBfromYUV_BT601_u8(r, g, b, y, u, v); 129 | dst[i * w * 3 + j * 3 + 0] = r; 130 | dst[i * w * 3 + j * 3 + 1] = g; 131 | dst[i * w * 3 + j * 3 + 2] = b; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/kantu/transform_format.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "kantu/image.hpp" 4 | #include 5 | 6 | namespace kantu { 7 | 8 | enum class Transformer { 9 | RGB_to_GRAY, 10 | RGB_to_BGR 11 | }; 12 | 13 | void transformFormat(const FourccImage& src, FourccImage& dst, const Transformer& transformer); 14 | 15 | void chw_to_hwc(const cv::Mat& src, cv::Mat& dst); 16 | void my_chw_to_hwc(cv::InputArray src, cv::OutputArray dst); 17 | void i444_to_rgb(uint8_t* i444, uint8_t* bgr, int width, int height); 18 | 19 | class PlaneInfo 20 | { 21 | public: 22 | PlaneInfo(int _height, int _width, int _channels): 23 | height(_height), width(_width), channels(_channels) 24 | {} 25 | 26 | public: 27 | int height; 28 | int width; 29 | int channels; 30 | }; 31 | 32 | } // namespace kantu -------------------------------------------------------------------------------- /src/kantu_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | macro(kantu_add_test name) 3 | add_executable(test_${name} ${CMAKE_CURRENT_SOURCE_DIR}/test_${name}.cpp) 4 | set(dep_libs GTest::gtest GTest::gtest_main) 5 | if(ANDROID) 6 | list(APPEND dep_libs log) 7 | endif() 8 | target_link_libraries(test_${name} PUBLIC 9 | ${dep_libs} 10 | ${ARGN} 11 | ) 12 | gtest_add_tests(TARGET test_${name}) 13 | endmacro() 14 | 15 | execute_process( 16 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/images ${CMAKE_BINARY_DIR} 17 | ) 18 | 19 | kantu_add_test(image_compare kantu_compare) 20 | -------------------------------------------------------------------------------- /src/kantu_tests/test_image_compare.cpp: -------------------------------------------------------------------------------- 1 | #include "gtest/gtest.h" 2 | 3 | #ifndef STR_IMPLEMENTATION 4 | #define STR_IMPLEMENTATION 1 5 | #endif 6 | #include "kantu/image_io.hpp" 7 | #include "kantu/compare.hpp" 8 | #include "kantu/string.hpp" 9 | 10 | TEST(FilePath, t1) 11 | { 12 | const std::string path = "C:\\Users\\zz\\data\\lena.png"; 13 | kantu::FilePath filepath(path); 14 | EXPECT_EQ(filepath.path(), path); 15 | EXPECT_EQ(filepath.directory(), "C:\\Users\\zz\\data"); 16 | EXPECT_EQ(filepath.filename(), "lena.png"); 17 | EXPECT_EQ(filepath.basename(), "lena"); 18 | EXPECT_EQ(filepath.ext(), "png"); 19 | } 20 | 21 | TEST(FilePath, t2) 22 | { 23 | const std::string path = "C:\\Users\\zz\\data\\lena"; 24 | kantu::FilePath filepath(path); 25 | EXPECT_EQ(filepath.path(), path); 26 | EXPECT_EQ(filepath.directory(), "C:\\Users\\zz\\data"); 27 | EXPECT_EQ(filepath.filename(), "lena"); 28 | EXPECT_EQ(filepath.basename(), "lena"); 29 | EXPECT_EQ(filepath.ext(), ""); 30 | } 31 | 32 | TEST(FilePath, t3) 33 | { 34 | const std::string path = "/home/zz/data/lena.png"; 35 | kantu::FilePath filepath(path); 36 | EXPECT_EQ(filepath.path(), path); 37 | EXPECT_EQ(filepath.directory(), "/home/zz/data"); 38 | EXPECT_EQ(filepath.filename(), "lena.png"); 39 | EXPECT_EQ(filepath.basename(), "lena"); 40 | EXPECT_EQ(filepath.ext(), "png"); 41 | } 42 | 43 | TEST(FilePath, t4) 44 | { 45 | const std::string path = "C:\\Users\\zz\\data/lena.png"; 46 | kantu::FilePath filepath(path); 47 | EXPECT_EQ(filepath.path(), path); 48 | EXPECT_EQ(filepath.directory(), "C:\\Users\\zz\\data"); 49 | EXPECT_EQ(filepath.filename(), "lena.png"); 50 | EXPECT_EQ(filepath.basename(), "lena"); 51 | EXPECT_EQ(filepath.ext(), "png"); 52 | } 53 | 54 | TEST(ImageFileName, non_raw) 55 | { 56 | std::string head = "temp"; 57 | std::vector image_exts = { 58 | "jpg", "png", "bmp", "jpeg", "jpg", 59 | "JPG", "PNG", "BMP", "JPEG", "JPG" 60 | }; 61 | for (const std::string& ext : image_exts) 62 | { 63 | std::string lower_ext = kantu::to_lower(ext); 64 | std::string image_path = head + "." + ext; 65 | kantu::FourccFileInfo fileinfo(image_path); 66 | 67 | EXPECT_EQ(fileinfo.valid, false); 68 | EXPECT_EQ(fileinfo.filepath, image_path); 69 | EXPECT_EQ(fileinfo.head, head); 70 | EXPECT_EQ(fileinfo.ext, ext); 71 | EXPECT_EQ(fileinfo.mapped_ext, lower_ext); 72 | EXPECT_EQ(fileinfo.height, 0); 73 | EXPECT_EQ(fileinfo.width, 0); 74 | } 75 | } 76 | 77 | TEST(find, t1) 78 | { 79 | std::vector exts = { "jpg", "png", "bmp" }; 80 | int pos = -1; 81 | 82 | pos = kantu::find(exts, std::string("jpg")); 83 | EXPECT_EQ(pos, 0); 84 | 85 | pos = kantu::find(exts, std::string("png")); 86 | EXPECT_EQ(pos, 1); 87 | 88 | pos = kantu::find(exts, std::string("bmp")); 89 | EXPECT_EQ(pos, 2); 90 | 91 | pos = kantu::find(exts, std::string("qaq")); 92 | EXPECT_EQ(pos, -1); 93 | } 94 | 95 | // TEST(ImageFileName, raw) 96 | // { 97 | // std::string head = "temp"; 98 | // const int height = 1280; 99 | // const int width = 720; 100 | // std::vector image_exts = { 101 | // "nv21", "nv12", "rgb", "bgr", "i420", "i444", 102 | // "NV21", "NV12", "RGB", "BGR", "I420", "I444", 103 | // }; 104 | // for (const std::string& ext : image_exts) 105 | // { 106 | // std::string lower_ext = kantu::to_lower(ext); 107 | // std::string image_path = head + "_" + std::to_string(width) + "x" + std::to_string(height) + "." + ext; 108 | // kantu::ImageFileInfo fileinfo(image_path); 109 | 110 | // EXPECT_EQ(fileinfo.valid, true); 111 | // EXPECT_EQ(fileinfo.filename, image_path); 112 | // EXPECT_EQ(fileinfo.head, head); 113 | // EXPECT_EQ(fileinfo.ext, ext); 114 | // EXPECT_EQ(fileinfo.lower_ext, lower_ext); 115 | // EXPECT_EQ(fileinfo.height, height); 116 | // EXPECT_EQ(fileinfo.width, width); 117 | // } 118 | // } 119 | 120 | // TEST(ImageFileName, head) 121 | // { 122 | // #if _MSC_VER 123 | // std::string image_path = "C:/Users/zz/data/1920x1080.NV21"; 124 | // kantu::FourccFileInfo fileinfo(image_path); 125 | // EXPECT_EQ(fileinfo.head, "1920X1080"); 126 | // #endif 127 | // } 128 | 129 | // 1. 文件名字 ImageFileName 130 | // 2. 文件内容 ImageFile 131 | // 3. 格式转换 TransformFormat 132 | // 4. 图像比对 CompareImage 133 | // 5. 渲染结果 RenderResult -------------------------------------------------------------------------------- /xkcd-script.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zchrissirhcz/KantuCompare/2b15c04a1787331282633aad9f48105719853779/xkcd-script.ttf --------------------------------------------------------------------------------