├── .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 |
[](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/linux-x64.yml) [](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/windows-x64.yml) [](https://github.com/zchrissirhcz/KantuCompare/actions/workflows/mac-x64.yml)
4 |
5 | A GUI for image difference visualization.
6 |
7 | 
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
--------------------------------------------------------------------------------