Codebase list frei0r / e890677
Update upstream source from tag 'upstream/3.1.2' Update to upstream version '3.1.2' with Debian dir a4f3d6e187eb83662cc02e281f49f78d2e4c846f IOhannes m zmรถlnig (Debian/GNU) 2 months ago
29 changed file(s) with 1677 addition(s) and 218 deletion(s). Raw diff Collapse all Expand all
0 #!/usr/bin/env bash
1
2 set -euo pipefail
3
4 base_sha=${1:?base sha required}
5 head_sha=${2:?head sha required}
6
7 mode=full
8 reason="changed files require the full suite"
9 declare -a plugin_dirs=()
10 declare -a targets=()
11 declare -a plugin_paths=()
12
13 fatal() {
14 printf 'error: %s\n' "$1" >&2
15 exit 1
16 }
17
18 is_plugin_tree() {
19 case "$1" in
20 src/filter/*|src/mixer2/*|src/mixer3/*) return 0 ;;
21 *) return 1 ;;
22 esac
23 }
24
25 plugin_dir_from_path() {
26 local path=$1
27 IFS=/ read -r top category name _ <<< "$path"
28 if [[ "$top" == "src" && -n "${category:-}" && -n "${name:-}" ]]; then
29 printf '%s/%s/%s\n' "$top" "$category" "$name"
30 return 0
31 fi
32 return 1
33 }
34
35 contains_dir() {
36 local needle=$1
37 shift || true
38 local item
39 for item in "$@"; do
40 if [[ "$item" == "$needle" ]]; then
41 return 0
42 fi
43 done
44 return 1
45 }
46
47 dir_exists_in_commit() {
48 local commit=$1
49 local path=$2
50 git cat-file -e "${commit}:${path}" 2>/dev/null
51 }
52
53 parent_cmake_registers_plugin() {
54 local commit=$1
55 local plugin_dir=$2
56 local category=${plugin_dir#src/}
57 category=${category%%/*}
58 local plugin_name=${plugin_dir##*/}
59 local parent_cmake="src/${category}/CMakeLists.txt"
60
61 local parent_text
62 parent_text=$(git show "${commit}:${parent_cmake}" 2>/dev/null) || {
63 return 1
64 }
65
66 printf '%s\n' "$parent_text" | grep -Eq "^[[:space:]]*add_subdirectory[[:space:]]*\\([[:space:]]*${plugin_name}[[:space:]]*\\)"
67 }
68
69 extract_target_name() {
70 local commit=$1
71 local plugin_dir=$2
72 local plugin_name=${plugin_dir##*/}
73 local cmake_file="${plugin_dir}/CMakeLists.txt"
74
75 local cmake_text
76 cmake_text=$(git show "${commit}:${cmake_file}" 2>/dev/null) || {
77 return 1
78 }
79
80 local target
81 target=$(printf '%s\n' "$cmake_text" | sed -nE 's/^[[:space:]]*set[[:space:]]*\([[:space:]]*TARGET[[:space:]]+([^[:space:])]+).*/\1/p' | head -n1)
82 if [[ -n "$target" ]]; then
83 printf '%s\n' "$target"
84 return 0
85 fi
86
87 target=$(printf '%s\n' "$cmake_text" | sed -nE 's/^[[:space:]]*add_library[[:space:]]*\(([[:alnum:]_.+-]+)[[:space:]]+MODULE.*/\1/p' | head -n1)
88 if [[ -n "$target" ]]; then
89 printf '%s\n' "$target"
90 return 0
91 fi
92
93 printf '%s\n' "$plugin_name"
94 }
95
96 while IFS=$'\t' read -r status path new_path; do
97 [[ -n "${status:-}" ]] || continue
98
99 if [[ "$status" == A* ]] && is_plugin_tree "$path"; then
100 plugin_dir=$(plugin_dir_from_path "$path") || true
101 if [[ -n "${plugin_dir:-}" ]] && dir_exists_in_commit "$base_sha" "$plugin_dir"; then
102 reason="PR adds files inside existing plugin directory: $plugin_dir"
103 plugin_dirs=()
104 break
105 fi
106
107 if [[ -n "${plugin_dir:-}" ]] && ! contains_dir "$plugin_dir" "${plugin_dirs[@]}"; then
108 plugin_dirs+=("$plugin_dir")
109 fi
110 continue
111 fi
112
113 case "$path" in
114 src/filter/CMakeLists.txt|src/mixer2/CMakeLists.txt|src/mixer3/CMakeLists.txt)
115 continue
116 ;;
117 *)
118 reason="PR touches non-new-plugin files: $path"
119 plugin_dirs=()
120 break
121 ;;
122 esac
123 done < <(git diff --name-status --find-renames "$base_sha" "$head_sha")
124
125 if [[ ${#plugin_dirs[@]} -gt 0 ]]; then
126 mode=targeted
127 reason="PR only adds new plugin directories"
128
129 for plugin_dir in "${plugin_dirs[@]}"; do
130 if ! parent_cmake_registers_plugin "$head_sha" "$plugin_dir"; then
131 fatal "parent CMakeLists.txt does not register ${plugin_dir}. Add add_subdirectory(${plugin_dir##*/}) to the category CMakeLists.txt."
132 fi
133
134 target=$(extract_target_name "$head_sha" "$plugin_dir") || {
135 mode=full
136 reason="could not resolve target for $plugin_dir"
137 targets=()
138 plugin_paths=()
139 break
140 }
141
142 plugin_paths+=("build/${plugin_dir}/${target}.so")
143 targets+=("$target")
144 done
145 fi
146
147 printf 'mode=%s\n' "$mode"
148 printf 'reason=%s\n' "$reason"
149
150 if [[ "$mode" == "targeted" ]]; then
151 {
152 printf 'targets<<EOF\n'
153 printf '%s\n' "${targets[*]}"
154 printf 'EOF\n'
155 printf 'plugin_paths<<EOF\n'
156 printf '%s\n' "${plugin_paths[*]}"
157 printf 'EOF\n'
158 printf 'plugin_dirs<<EOF\n'
159 printf '%s\n' "${plugin_dirs[*]}"
160 printf 'EOF\n'
161 }
162 else
163 {
164 printf 'targets=\n'
165 printf 'plugin_paths=\n'
166 printf 'plugin_dirs=\n'
167 }
168 fi
1414
1515 jobs:
1616 conventional-commits:
17 name: ๐Ÿ“œ Parse PR commits
17 name: ๐Ÿ“œ Conventional Commits
1818 runs-on: ubuntu-latest
1919 steps:
2020 - name: ๐Ÿ”Ž Check PR commit messages follow Conventional Commits
1313 cancel-in-progress: true
1414
1515 jobs:
16 semantic-release:
17 name: ๐Ÿค– Semantic release
18 runs-on: ubuntu-latest
19 if: "!contains(github.event.pull_request.labels.*.name, 'skip-release')"
16 release-plan:
17 name: ๐Ÿค– Plan release
18 runs-on: ubuntu-latest
19 if: github.event.workflow_run.conclusion == 'success'
2020 outputs:
21 release: ${{ steps.tag_release.outputs.release }}
22 version: ${{ steps.tag_release.outputs.version }}
21 release: ${{ steps.plan_release.outputs.release }}
22 version: ${{ steps.plan_release.outputs.version }}
23 tag: ${{ steps.plan_release.outputs.tag }}
2324 steps:
2425 - uses: actions/checkout@v6
2526 - name: Setup Node.js
2930 - name: Install semantic-release
3031 run: |
3132 npm i semantic-release/changelog
32 - name: Tag release
33 id: tag_release
33 - name: Plan release
34 id: plan_release
3435 env:
3536 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3637 run: |
37 npx semantic-release | tee semantic-release.log
38 if [[ `git tag --points-at HEAD` == "" ]]; then
39 echo "release=False" >> $GITHUB_OUTPUT
38 npx semantic-release --dry-run | tee semantic-release.log
39 version=$(awk '/The next release version is/ { print $NF }' semantic-release.log | tail -n1)
40 if [[ -z "$version" ]]; then
41 echo "release=False" >> "$GITHUB_OUTPUT"
4042 else
41 echo "release=True" >> $GITHUB_OUTPUT
42 awk '/Published release/ { printf("version=v%s\n",$8) }' semantic-release.log >> $GITHUB_OUTPUT
43 echo "release=True" >> "$GITHUB_OUTPUT"
44 printf 'version=%s\ntag=v%s\n' "$version" "$version" >> "$GITHUB_OUTPUT"
4345 fi
4446
4547 linux-build:
4648 name: ๐Ÿง linux build
4749 runs-on: ubuntu-latest
48 needs: [semantic-release]
49 if: ${{ needs.semantic-release.outputs.release == 'True' }}
50 needs: [release-plan]
51 if: ${{ needs.release-plan.outputs.release == 'True' }}
5052 steps:
5153 - uses: actions/checkout@v6
5254 - name: apt install deps
5355 run: |
5456 sudo apt-get update -y -q
55 sudo apt-get install -y -q --no-install-recommends cmake ninja-build libopencv-dev libgavl-dev libfreetype-dev libcairo-dev
57 sudo apt-get install -y -q --no-install-recommends cmake ninja-build libopencv-dev libgavl-dev libfreetype-dev libcairo2-dev
5658 - name: Build using cmake+ninja
5759 run: |
5860 mkdir build && cd build
59 cmake -G "Ninja" ../
61 cmake -G "Ninja" -D FREI0R_VERSION=${{ needs.release-plan.outputs.version }} ../
6062 ninja
6163 - name: Upload linux filter
6264 uses: actions/upload-artifact@v6
7880 with:
7981 name: release-linux-generator
8082 path: build/src/generator/**/*.so
83 - name: Upload linux pkg-config
84 uses: actions/upload-artifact@v6
85 with:
86 name: release-linux-pkgconfig
87 path: build/frei0r.pc
8188
8289 win-build:
8390 name: ๐ŸชŸ win64 build
8491 runs-on: windows-latest
85 needs: [semantic-release]
86 if: ${{ needs.semantic-release.outputs.release == 'True' }}
92 needs: [release-plan]
93 if: ${{ needs.release-plan.outputs.release == 'True' }}
8794 steps:
8895 - uses: actions/checkout@v6
8996 - uses: ilammy/msvc-dev-cmd@v1
94101 - name: Build using nmake
95102 run: |
96103 mkdir build && cd build
97 cmake -G "NMake Makefiles" -D WITHOUT_OPENCV=1 -D WITHOUT_CAIRO=1 -D WITHOUT_GAVL=1 ../
104 cmake -G "NMake Makefiles" -D WITHOUT_OPENCV=1 -D WITHOUT_CAIRO=1 -D WITHOUT_GAVL=1 -D FREI0R_VERSION=${{ needs.release-plan.outputs.version }} ../
98105 nmake
99106 - name: Upload win64 filter
100107 uses: actions/upload-artifact@v6
116123 with:
117124 name: release-win64-generator
118125 path: build/src/generator/**/*.dll
126 - name: Upload win64 pkg-config
127 uses: actions/upload-artifact@v6
128 with:
129 name: release-win64-pkgconfig
130 path: build/frei0r.pc
119131
120132 osx-build:
121133 name: ๐Ÿ osx build
122134 runs-on: macos-latest
123 needs: [semantic-release]
124 if: ${{ needs.semantic-release.outputs.release == 'True' }}
135 needs: [release-plan]
136 if: ${{ needs.release-plan.outputs.release == 'True' }}
125137 steps:
126138 - uses: actions/checkout@v6
127139 - name: Update Homebrew
134146 - name: Build using ninja
135147 run: |
136148 mkdir build && cd build
137 cmake -G "Ninja" -D WITHOUT_OPENCV=1 -D WITHOUT_GAVL=1 ../
149 cmake -G "Ninja" -D WITHOUT_OPENCV=1 -D WITHOUT_GAVL=1 -D FREI0R_VERSION=${{ needs.release-plan.outputs.version }} ../
138150 ninja
139151 - name: Upload osx filter
140152 uses: actions/upload-artifact@v6
156168 with:
157169 name: release-osx-generator
158170 path: build/src/generator/**/*.so
171 - name: Upload osx pkg-config
172 uses: actions/upload-artifact@v6
173 with:
174 name: release-osx-pkgconfig
175 path: build/frei0r.pc
176
177 publish-release:
178 name: ๐Ÿš€ Publish release
179 runs-on: ubuntu-latest
180 needs: [release-plan, win-build, osx-build, linux-build]
181 if: ${{ needs.release-plan.outputs.release == 'True' }}
182 outputs:
183 version: ${{ steps.publish_release.outputs.version }}
184 tag: ${{ steps.publish_release.outputs.tag }}
185 steps:
186 - uses: actions/checkout@v6
187 - name: Setup Node.js
188 uses: actions/setup-node@v6
189 with:
190 node-version: latest
191 - name: Install semantic-release
192 run: |
193 npm i semantic-release/changelog
194 - name: Publish release
195 id: publish_release
196 env:
197 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
198 run: |
199 npx semantic-release
200 printf 'version=%s\ntag=%s\n' \
201 "${{ needs.release-plan.outputs.version }}" \
202 "${{ needs.release-plan.outputs.tag }}" >> "$GITHUB_OUTPUT"
159203
160204 draft-binary-release:
161205 name: ๐Ÿ“ฆ Pack release
162 needs: [semantic-release, win-build, osx-build, linux-build]
163 if: ${{ needs.semantic-release.outputs.release == 'True' }}
206 needs: [release-plan, publish-release, win-build, osx-build, linux-build]
207 if: ${{ needs.release-plan.outputs.release == 'True' }}
164208 runs-on: ubuntu-latest
165209 steps:
166210 - uses: actions/checkout@v6
171215 frei0r-bin
172216 - name: create compressed archives
173217 run: |
174 dst=frei0r-${{ needs.semantic-release.outputs.version }}_win64
218 dst=frei0r-${{ needs.publish-release.outputs.version }}_win64
175219 mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3
176220 find frei0r-bin/release-win64-filter -type f -name '*.dll' -exec cp {} $dst/filter \;
177221 find frei0r-bin/release-win64-generator -type f -name '*.dll' -exec cp {} $dst/generator \;
182226 cp ChangeLog $dst/ChangeLog.txt
183227 cp AUTHORS.md $dst/AUTHORS.txt
184228 cp include/frei0r.h include/frei0r.hpp $dst/
185 echo "${{ needs.semantic-release.outputs.version }}" > $dst/VERSION.txt
229 cp frei0r-bin/release-win64-pkgconfig/frei0r.pc $dst/
230 echo "${{ needs.publish-release.outputs.version }}" > $dst/VERSION.txt
186231 zip -r -9 $dst.zip $dst
187232
188 dst=frei0r-${{ needs.semantic-release.outputs.version }}_osx
233 dst=frei0r-${{ needs.publish-release.outputs.version }}_osx
189234 mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3
190235 find frei0r-bin/release-osx-filter -type f -name '*.so' -exec cp {} $dst/filter \;
191236 find frei0r-bin/release-osx-generator -type f -name '*.so' -exec cp {} $dst/generator \;
196241 cp ChangeLog $dst/ChangeLog.txt
197242 cp AUTHORS.md $dst/AUTHORS.txt
198243 cp include/frei0r.h include/frei0r.hpp $dst/
199 echo "${{ needs.semantic-release.outputs.version }}" > $dst/VERSION.txt
244 cp frei0r-bin/release-osx-pkgconfig/frei0r.pc $dst/
245 echo "${{ needs.publish-release.outputs.version }}" > $dst/VERSION.txt
200246 zip -r -9 $dst.zip $dst
201247
202 dst=frei0r-${{ needs.semantic-release.outputs.version }}_linux
248 dst=frei0r-${{ needs.publish-release.outputs.version }}_linux
203249 mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3
204250 find frei0r-bin/release-linux-filter -type f -name '*.so' -exec cp {} $dst/filter \;
205251 find frei0r-bin/release-linux-generator -type f -name '*.so' -exec cp {} $dst/generator \;
210256 cp ChangeLog $dst/ChangeLog.txt
211257 cp AUTHORS.md $dst/AUTHORS.txt
212258 cp include/frei0r.h include/frei0r.hpp $dst/
213 echo "${{ needs.semantic-release.outputs.version }}" > $dst/VERSION.txt
259 cp frei0r-bin/release-linux-pkgconfig/frei0r.pc $dst/
260 echo "${{ needs.publish-release.outputs.version }}" > $dst/VERSION.txt
214261 tar cvfz $dst.tar.gz $dst
215262
216263 sha256sum *.zip *.tar.gz > SHA256SUMS.txt
222269 *.zip
223270 *.tar.gz
224271 SHA256SUMS.txt
225 tag_name: ${{ needs.semantic-release.outputs.version }}
272 tag_name: ${{ needs.publish-release.outputs.tag }}
226273 draft: true
227274 prerelease: false
228275 fail_on_unmatched_files: true
2121 cancel-in-progress: true
2222
2323 jobs:
24
25 # reuse:
26 # name: ๐Ÿšจ REUSE Compliance
27 # runs-on: ubuntu-latest
28 # steps:
29 # - uses: actions/checkout@v6
30 # - uses: fsfe/reuse-action@v1
31
2432 c-lint:
2533 name: ๐Ÿšจ C lint
2634 runs-on: ubuntu-latest
6775 runs-on: ubuntu-latest
6876 steps:
6977 - uses: actions/checkout@v6
70 - name: install dependencies
78 with:
79 fetch-depth: 0
80 - uses: hendrikmuhs/ccache-action@v1.2
81 - name: Cache APT packages
82 uses: awalsh128/cache-apt-pkgs-action@latest
83 with:
84 packages: "${{ matrix.compiler }} cmake ninja-build libfreetype-dev libopencv-dev libcairo2-dev libgavl-dev"
85 - name: Detect new plugins
86 id: detect
87 if: github.event_name == 'pull_request'
7188 run: |
72 sudo apt-get update -qy
73 sudo apt-get install --no-install-recommends -y ${{ matrix.compiler }} cmake ninja-build libfreetype-dev libopencv-dev libcairo2-dev libgavl-dev
89 .github/scripts/detect-new-plugins.sh \
90 "${{ github.event.pull_request.base.sha }}" \
91 "${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
92 - name: Print CI mode
93 run: |
94 echo "mode=${{ steps.detect.outputs.mode || 'full' }}"
95 echo "reason=${{ steps.detect.outputs.reason || 'push event uses the full suite' }}"
7496 - name: ${{ matrix.compiler }} initialize cmake build
7597 run: |
7698 mkdir -p build && cd build
77 cmake -G "Ninja" ../
99 cmake -G "Ninja" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache ..
78100 - name: ${{ matrix.compiler }} run ninja build
101 if: github.event_name != 'pull_request' || steps.detect.outputs.mode != 'targeted'
79102 run: |
80103 cd build && ninja
104 - name: ${{ matrix.compiler }} build new plugins
105 if: github.event_name == 'pull_request' && steps.detect.outputs.mode == 'targeted'
106 run: |
107 cd build
108 cmake --build . --target ${{ steps.detect.outputs.targets }}
81109 - name: ${{ matrix.compiler }} analyze plugins
110 if: github.event_name != 'pull_request' || steps.detect.outputs.mode != 'targeted'
82111 run: |
83 cd test && make
84 - name: ${{ matrix.compiler }} upload plugin analysis
85 uses: actions/upload-artifact@v6
86 with:
87 name: release-plugin-analysis
88 path: test/*.json
112 cd test && make frei0r-asan && make check
113 - name: ${{ matrix.compiler }} analyze new plugins
114 if: github.event_name == 'pull_request' && steps.detect.outputs.mode == 'targeted'
115 run: |
116 cd test
117 make frei0r-asan
118 for plugin in ${{ steps.detect.outputs.plugin_paths }}; do
119 ./frei0r-run -d -p "../$plugin"
120 done
00 # Build instructions
11
22 Frei0r can be built using CMake.
3
4 Minimum toolchain expectations:
5
6 + C compiler
7 + C++ compiler with C++11 support (required)
8 + CMake
9 + Ninja or Make
310
411 The presence of optional libraries on the system will trigger compilation of extra plugins. These libraries are:
512
1623 It is recommended to use a separate `build` sub-folder.
1724
1825 ```
19 mkdir -p build
20 cd build && cmake ../
21 make
26 cmake -S . -B build
27 cmake --build build
2228 ```
2329
2430 To disable face recognition plugins (recommended when using with MLT):
2531 ```
26 mkdir -p build
27 cd build && cmake -DWITHOUT_FACERECOGNITION=ON ../
28 make
32 cmake -S . -B build -DWITHOUT_FACERECOGNITION=ON
33 cmake --build build
2934 ```
3035
31 Also ninja and nmake are supported through cmake:
36 Ninja and nmake are also supported through CMake:
3237 ```
33 cmake -G 'Ninja' ../
34 cmake -G 'NMake Makefiles' ../
38 cmake -S . -B build -G 'Ninja'
39 cmake -S . -B build -G 'NMake Makefiles'
3540 ```
3641
42 Top-level shorthand targets are available through `GNUmakefile`:
43 ```
44 make release-gcc-ninja
45 make debug-gcc
46 ```
47
48 Runtime test utilities:
49 ```
50 cd test
51 make frei0r-asan # builds ./frei0r-run with ASAN
52 make check # loads and runs all built plugins under ../build/src
53 make frei0r-meta
54 make scan-meta
55 ```
11
22 list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
33
4 project (frei0r)
5 set (VERSION 2.5.1)
4 set (FREI0R_VERSION "3.0.0" CACHE STRING "Project version in SemVer format")
5 if (DEFINED ENV{FREI0R_VERSION} AND NOT "$ENV{FREI0R_VERSION}" STREQUAL "")
6 set (FREI0R_VERSION "$ENV{FREI0R_VERSION}")
7 endif ()
8 string (REGEX REPLACE "^v" "" FREI0R_VERSION "${FREI0R_VERSION}")
9 if (NOT FREI0R_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+$")
10 message (FATAL_ERROR "FREI0R_VERSION must be a SemVer string like 3.0.0")
11 endif ()
12
13 project (frei0r VERSION ${FREI0R_VERSION})
14 set (CMAKE_CXX_STANDARD 11)
15 set (CMAKE_CXX_STANDARD_REQUIRED True)
616
717 include(GNUInstallDirs)
818
0 all: release-gcc-ninja
1
2 debug: debug-gcc
3
4 release-gcc:
5 mkdir -p build
6 cd build && cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release ..
7 cd build && make
8
9 release-gcc-ninja:
10 mkdir -p build
11 cd build && cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release -G 'Ninja' ..
12 cd build && ninja
13
14 release-clang:
15 mkdir -p build
16 cd build && cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release ..
17 cd build && make
18
19 release-clang-ninja:
20 mkdir -p build
21 cd build && cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -G 'Ninja' ..
22 cd build && ninja
23
24 debug-gcc:
25 mkdir -p build
26 cd build && cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS_DEBUG='-ggdb -fno-omit-frame-pointer -fsanitize=address' -DCMAKE_C_FLAGS_DEBUG='-ggdb -fno-omit-frame-pointer -fsanitize=address' ..
27 cd build && make
28
29
30 debug-clang-ninja:
31 mkdir -p build
32 cd build && cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS_DEBUG='-ggdb -fno-omit-frame-pointer -fsanitize=address' -DCMAKE_C_FLAGS_DEBUG='-ggdb -fno-omit-frame-pointer -fsanitize=address' -G 'Ninja' ..
33 cd build && ninja
34
35 clean:
36 rm -rf build
6262
6363 For details see the [BUILD](/BUILD.md) file.
6464
65 ### Quick build and test
66
67 ```sh
68 cmake -S . -B build -G Ninja
69 cmake --build build
70 cd test && make frei0r-asan && make check
71 ```
72
73 ### Metadata scan utility
74
75 The metadata scanner binary is `test/frei0r-meta` (previously `frei0r-info`):
76
77 ```sh
78 cd test && make frei0r-meta && make scan-meta
79 ```
80
6581 ### MS / Windows
6682
6783 We distribute official builds of frei0r plugins as .dll for the Win64 platform from the releases page.
85101
86102 A [frei0r Brew formula](https://formulae.brew.sh/formula/frei0r) is available.
87103
88 Official builds of frei0r plugins as .dlsym for the Apple/OSX platform will be soon included in the releases page.
104 Official macOS release artifacts are distributed from the releases page.
89105
90106 # Documentation
91107
159175
160176 For a full list of contributors and the project history, see the file [AUTHORS](/AUTHORS), the [ChangeLog](/ChangeLog) and the project web page: https://frei0r.dyne.org
161177
162
44
55 Name: frei0r
66 Description: minimalistic plugin API for video effects
7 Version: @VERSION@
7 Version: @PROJECT_VERSION@
88 Libs:
99 Cflags: -I${includedir}
10
9090 param_ptrs.push_back(&p_loc);
9191 s_params.push_back(param_info(name,desc,F0R_PARAM_STRING));
9292 }
93
94
93
94
9595 void get_param_value(f0r_param_t param, int param_index)
9696 {
97 if (!param || param_index < 0 || param_index >= (int)param_ptrs.size())
98 return;
99
97100 void* ptr = param_ptrs[param_index];
98
101
99102 switch (s_params[param_index].m_type)
100103 {
101104 case F0R_PARAM_BOOL :
124127
125128 void set_param_value(f0r_param_t param, int param_index)
126129 {
130 if (!param || param_index < 0 || param_index >= (int)param_ptrs.size())
131 return;
132
127133 void* ptr = param_ptrs[param_index];
128
134
129135 switch (s_params[param_index].m_type)
130136 {
131137 case F0R_PARAM_BOOL :
145151 = *static_cast<f0r_param_position*>(param);
146152 break;
147153 case F0R_PARAM_STRING:
148 *static_cast<std::string*>(ptr)
149 = *static_cast<f0r_param_string*>(param);
154 {
155 f0r_param_string str = *static_cast<f0r_param_string*>(param);
156 if (str)
157 *static_cast<std::string*>(ptr) = str;
150158 break;
151159 }
152
153 }
154
160 }
161
162 }
163
155164 virtual void update(double time,
156165 uint32_t* out,
157166 const uint32_t* in1,
296305
297306 void f0r_get_param_info(f0r_param_info_t* info, int param_index)
298307 {
308 if (!info || param_index < 0 || param_index >= (int)frei0r::s_params.size())
309 return;
310
299311 info->name=frei0r::s_params[param_index].m_name.c_str();
300312 info->type=frei0r::s_params[param_index].m_type;
301313 info->explanation=frei0r::s_params[param_index].m_desc.c_str();
315327 delete static_cast<frei0r::fx*>(instance);
316328 }
317329
318 void f0r_set_param_value(f0r_instance_t instance,
330 void f0r_set_param_value(f0r_instance_t instance,
319331 f0r_param_t param, int param_index)
320332 {
321 static_cast<frei0r::fx*>(instance)->set_param_value(param, param_index);
333 if (instance)
334 static_cast<frei0r::fx*>(instance)->set_param_value(param, param_index);
322335 }
323336
324337 void f0r_get_param_value(f0r_instance_t instance,
325338 f0r_param_t param, int param_index)
326339 {
327 static_cast<frei0r::fx*>(instance)->get_param_value(param, param_index);
340 if (instance)
341 static_cast<frei0r::fx*>(instance)->get_param_value(param, param_index);
328342 }
329343
330344 void f0r_update2(f0r_instance_t instance, double time,
346360 {
347361 f0r_update2(instance, time, inframe, 0, 0, outframe);
348362 }
349
2626 add_subdirectory (brightness)
2727 add_subdirectory (bw0r)
2828 add_subdirectory (cartoon)
29 add_subdirectory (camerashake)
2930 add_subdirectory (cluster)
3031 add_subdirectory (colgate)
3132 add_subdirectory (coloradj)
5758 add_subdirectory (heatmap0r)
5859 add_subdirectory (hueshift0r)
5960 add_subdirectory (invert0r)
60 add_subdirectory (kaleid0sc0pe)
61 add_subdirectory (kaleid0sc0pe)
6162 add_subdirectory (keyspillm0pup)
6263 add_subdirectory (lenscorrection)
6364 add_subdirectory (letterb0xed)
102103 add_subdirectory (twolay0r)
103104 add_subdirectory (vertigo)
104105 add_subdirectory (vignette)
106 add_subdirectory (water)
185185 }
186186 }
187187
188 void f0r_destruct(f0r_instance_t s)
188 void f0r_destruct(f0r_instance_t inst)
189189 {
190 s0ft0tsu_t* s = inst;
191 free(s->lumaframe);
190192 free(s);
191193 }
0 cmake_minimum_required(VERSION 3.10)
1 project(CameraShakePlugin)
2
3 set(CMAKE_CXX_STANDARD 14)
4 set(CMAKE_POSITION_INDEPENDENT_CODE ON)
5
6 # Compile as shared library
7 add_library(camerashake MODULE camerashake.cpp)
8
9 # Remove the "lib" prefix so that Frei0r can read it correctly
10 set_target_properties(camerashake PROPERTIES PREFIX "")
0 /* camerashake.cpp
1 * Copyright (C) 2026 PakeMPC
2 * This file is a Frei0r plugin.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19
20 #include <stdlib.h>
21 #include <string.h>
22 #if defined(_MSC_VER)
23 #define _USE_MATH_DEFINES
24 #endif
25 #include <math.h>
26
27 extern "C" {
28 #include <frei0r.h>
29
30 struct CameraShakeInstance {
31 double amp_x, amp_y, rotation, zoom, speed, opacity, blur;
32 float bg_r, bg_g, bg_b;
33 unsigned int width, height;
34 };
35
36 int f0r_init(void) { return 1; }
37 void f0r_deinit(void) {}
38
39 void f0r_get_plugin_info(f0r_plugin_info_t* info) {
40 info->name = "Camera Shake Ultimate";
41 info->author = "PakeMPC";
42 info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
43 info->color_model = F0R_COLOR_MODEL_RGBA8888;
44 info->frei0r_version = FREI0R_MAJOR_VERSION;
45 info->major_version = 2; info->minor_version = 0;
46 info->num_params = 8;
47 info->explanation = "Camera movement effect with rotation, opacity, and blur.";
48 }
49
50 void f0r_get_param_info(f0r_param_info_t* info, int param_index) {
51 if (param_index == 0) { info->name = "amplitude_x"; info->type = F0R_PARAM_DOUBLE; }
52 else if (param_index == 1) { info->name = "amplitude_y"; info->type = F0R_PARAM_DOUBLE; }
53 else if (param_index == 2) { info->name = "rotation"; info->type = F0R_PARAM_DOUBLE; }
54 else if (param_index == 3) { info->name = "zoom"; info->type = F0R_PARAM_DOUBLE; }
55 else if (param_index == 4) { info->name = "speed"; info->type = F0R_PARAM_DOUBLE; }
56 else if (param_index == 5) { info->name = "opacity"; info->type = F0R_PARAM_DOUBLE; }
57 else if (param_index == 6) { info->name = "blur"; info->type = F0R_PARAM_DOUBLE; }
58 else if (param_index == 7) { info->name = "bg_color"; info->type = F0R_PARAM_COLOR; }
59 }
60
61 f0r_instance_t f0r_construct(unsigned int width, unsigned int height) {
62 CameraShakeInstance* inst = new CameraShakeInstance();
63 inst->amp_x = 50.0; inst->amp_y = 50.0; inst->rotation = 10.0;
64 inst->zoom = 100.0; inst->speed = 50.0; inst->opacity = 100.0; inst->blur = 0.0;
65 inst->bg_r = 0.0f; inst->bg_g = 0.0f; inst->bg_b = 0.0f;
66 inst->width = width; inst->height = height;
67 return (f0r_instance_t)inst;
68 }
69
70 void f0r_destruct(f0r_instance_t instance) { delete (CameraShakeInstance*)instance; }
71
72 void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index) {
73 CameraShakeInstance* inst = (CameraShakeInstance*)instance;
74 double val = *((double*)param);
75 if (param_index == 0) inst->amp_x = val;
76 else if (param_index == 1) inst->amp_y = val;
77 else if (param_index == 2) inst->rotation = val;
78 else if (param_index == 3) inst->zoom = val;
79 else if (param_index == 4) inst->speed = val;
80 else if (param_index == 5) inst->opacity = val;
81 else if (param_index == 6) inst->blur = val;
82 else if (param_index == 7) {
83 f0r_param_color_t* color = (f0r_param_color_t*)param;
84 inst->bg_r = color->r; inst->bg_g = color->g; inst->bg_b = color->b;
85 }
86 }
87
88 void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index) {
89 CameraShakeInstance* inst = (CameraShakeInstance*)instance;
90 if (param_index == 0) *((double*)param) = inst->amp_x;
91 else if (param_index == 1) *((double*)param) = inst->amp_y;
92 else if (param_index == 2) *((double*)param) = inst->rotation;
93 else if (param_index == 3) *((double*)param) = inst->zoom;
94 else if (param_index == 4) *((double*)param) = inst->speed;
95 else if (param_index == 5) *((double*)param) = inst->opacity;
96 else if (param_index == 6) *((double*)param) = inst->blur;
97 else if (param_index == 7) {
98 f0r_param_color_t* color = (f0r_param_color_t*)param;
99 color->r = inst->bg_r; color->g = inst->bg_g; color->b = inst->bg_b;
100 }
101 }
102
103 void f0r_update(f0r_instance_t instance, double time, const uint32_t* inframe, uint32_t* outframe) {
104 CameraShakeInstance* inst = (CameraShakeInstance*)instance;
105 int w = inst->width;
106 int h = inst->height;
107
108 double speed_factor = inst->speed;
109
110 // 1. Zoom (0 to 500) mapped to scale (0.001 to 5.0)
111 double scale = inst->zoom / 100.0;
112 if (scale < 0.001) scale = 0.001; // Avoid division by zero
113
114 // 2. Shake X and Y (using the direct values โ€‹โ€‹up to 500px)
115 double shake_x = sin(time * speed_factor) * cos(time * speed_factor * 0.73) * inst->amp_x;
116 double shake_y = sin(time * speed_factor * 0.89) * cos(time * speed_factor * 1.1) * inst->amp_y;
117
118 // 3. Rotation
119 double max_angle = (inst->rotation / 100.0) * (M_PI / 4.0); // Maximum 45 degree
120 double theta = sin(time * speed_factor * 1.15) * max_angle;
121 double cos_t = cos(-theta);
122 double sin_t = sin(-theta);
123
124 // 4. Blur and Opacity Parameters
125 int blur_radius = (int)(inst->blur / 5.0); // Range of 0 to 20 pixels radius
126 double alpha_mult = inst->opacity / 100.0;
127
128 double cx = w / 2.0;
129 double cy = h / 2.0;
130
131 for (int y = 0; y < h; ++y) {
132 for (int x = 0; x < w; ++x) {
133
134 // Inverse mapping of rotation and scale matrix
135 double dx = (x - cx) / scale;
136 double dy = (y - cy) / scale;
137
138 double rx = dx * cos_t - dy * sin_t;
139 double ry = dx * sin_t + dy * cos_t;
140
141 int base_src_x = (int)(cx + rx + shake_x);
142 int base_src_y = (int)(cy + ry + shake_y);
143
144 uint8_t* out_ptr = (uint8_t*)&outframe[y * w + x];
145
146 // Check limits
147 if (base_src_x < 0 || base_src_x >= w || base_src_y < 0 || base_src_y >= h) {
148 out_ptr[0] = (uint8_t)(inst->bg_r * 255.0f);
149 out_ptr[1] = (uint8_t)(inst->bg_g * 255.0f);
150 out_ptr[2] = (uint8_t)(inst->bg_b * 255.0f);
151 out_ptr[3] = (uint8_t)(255 * alpha_mult);
152 continue;
153 }
154
155 // If there's NO blur, paint directly
156 if (blur_radius == 0) {
157 uint8_t* in_ptr = (uint8_t*)&inframe[base_src_y * w + base_src_x];
158 out_ptr[0] = in_ptr[0]; out_ptr[1] = in_ptr[1]; out_ptr[2] = in_ptr[2];
159 out_ptr[3] = (uint8_t)(in_ptr[3] * alpha_mult);
160 }
161 // If there is blur, do a "Fast Cross-Blur"
162 else {
163 int sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
164 int samples = 0;
165
166 // Horizontal and vertical sampling (cross shape for yield)
167 for (int i = -blur_radius; i <= blur_radius; i += 2) {
168 // Horizontal
169 int sx = base_src_x + i;
170 if (sx >= 0 && sx < w) {
171 uint8_t* p = (uint8_t*)&inframe[base_src_y * w + sx];
172 sum_r += p[0]; sum_g += p[1]; sum_b += p[2]; sum_a += p[3];
173 samples++;
174 }
175 // Vertical
176 int sy = base_src_y + i;
177 if (sy >= 0 && sy < h && i != 0) { // i!=0 to avoid adding the center twice
178 uint8_t* p = (uint8_t*)&inframe[sy * w + base_src_x];
179 sum_r += p[0]; sum_g += p[1]; sum_b += p[2]; sum_a += p[3];
180 samples++;
181 }
182 }
183
184 out_ptr[0] = sum_r / samples;
185 out_ptr[1] = sum_g / samples;
186 out_ptr[2] = sum_b / samples;
187 out_ptr[3] = (uint8_t)((sum_a / samples) * alpha_mult);
188 }
189 }
190 }
191 }
192 }
330330 break;
331331 case 3:
332332 inst->pointNumber = floor(*((f0r_param_double *)param) * 10);
333 inst->pointNumber = MIN(inst->pointNumber, 5);
333334 break;
334335 case 4:
335336 inst->formula = *((f0r_param_double *)param);
737738 double *points = (double*)calloc(inst->pointNumber * 2, sizeof(double));
738739 int i = inst->pointNumber * 2;
739740 //copy point values
740 while(--i > 0)
741 while(--i >= 0)
741742 points[i] = inst->points[i];
742743 //sort point values by X component
743744 for(i = 1; i < inst->pointNumber; i++)
798799 points = (double*)calloc(inst->pointNumber * 2, sizeof(double));
799800 i = inst->pointNumber * 2;
800801 //copy point values
801 while(--i > 0)
802 while(--i >= 0)
802803 points[i] = inst->points[i];
803804 //sort point values by X component
804805 for(i = 1; i < inst->pointNumber; i++)
1515
1616 ~delay0r()
1717 {
18 for (std::list< std::pair< double, unsigned int* > >::iterator i=buffer.begin(); i != buffer.end(); ++i)
18 for (std::list< std::pair< double, unsigned int* > >::iterator i=buffer.begin(); i != buffer.end(); )
1919 {
2020 delete[] i->second;
2121 i=buffer.erase(i);
2828 {
2929 unsigned int* reusable = 0;
3030 // remove old frames
31 for (std::list< std::pair< double, unsigned int* > >::iterator i=buffer.begin(); i != buffer.end(); ++i)
31 for (std::list< std::pair< double, unsigned int* > >::iterator i=buffer.begin(); i != buffer.end(); )
3232 {
3333 if (i->first < (time - delay) || i->first >= time)
3434 {
3939 reusable = i->second;
4040
4141 i=buffer.erase(i);
42 }
43 else
44 {
45 ++i;
4246 }
4347 }
4448
148148 uint32_t v = 0;
149149 for(unsigned int i = 0; i < w * h; i++)
150150 {
151 if(!(i + shift_component_x < 0 || i + shift_component_x >= w * h || i + shift_component_x + shift_component_y < 0 || i + shift_component_x + shift_component_y >= w * h))
151 if(i + shift_component_x >= 0 && i + shift_component_x < w * h &&
152 i + shift_component_y >= 0 && i + shift_component_y < w * h &&
153 i + shift_component_x + shift_component_y >= 0 && i + shift_component_x + shift_component_y < w * h)
152154 {
153155 if(larger_x_comp)
154156 {
2222 *
2323 */
2424 #include "kaleid0sc0pe.h"
25 #include "frei0r/math.h"
2526 #include <memory>
2627 #include <cstring>
2728 #ifndef NO_FUTURE
438439 __m128 ge_height = _mm_cmpge_ps(source_y, m_sse_height);
439440 source_y = _mm_or_ps(_mm_and_ps(_mm_sub_ps(m_sse_height, _mm_sub_ps(source_y, m_sse_height)), ge_height), _mm_andnot_ps(ge_height,source_y));
440441
441 __m128i source_xi = _mm_cvttps_epi32(_mm_min_ps(source_x, _mm_sub_ps(m_sse_width, m_sse_ps_1)));
442 __m128i source_yi = _mm_cvttps_epi32(_mm_min_ps(source_y, _mm_sub_ps(m_sse_height, m_sse_ps_1)));
442 __m128i source_xi = _mm_cvttps_epi32(_mm_min_ps(_mm_max_ps(source_x, _mm_setzero_ps()), _mm_sub_ps(m_sse_width, m_sse_ps_1)));
443 __m128i source_yi = _mm_cvttps_epi32(_mm_min_ps(_mm_max_ps(source_y, _mm_setzero_ps()), _mm_sub_ps(m_sse_height, m_sse_ps_1)));
443444
444445 std::int32_t* sx = reinterpret_cast<std::int32_t*>(&source_xi);
445446 std::int32_t* sy = reinterpret_cast<std::int32_t*>(&source_yi);
447
448 for (int i = 0; i < 4; ++i) {
449 sx[i] = CLAMP(sx[i], 0, static_cast<std::int32_t>(m_width) - 1);
450 sy[i] = CLAMP(sy[i], 0, static_cast<std::int32_t>(m_height) - 1);
451 }
452
446453 std::memcpy(out, lookup(block->in_frame, sx[0], sy[0]), m_pixel_size);
447454 out += m_pixel_size;
448455 std::memcpy(out, lookup(block->in_frame, sx[1], sy[1]), m_pixel_size);
510517 } else if (source_y > m_height - 10e-4f) {
511518 source_y = m_height - (source_y - m_height + 10e-4f);
512519 }
520 source_x = CLAMP(source_x, 0.0f, static_cast<float>(m_width - 1));
521 source_y = CLAMP(source_y, 0.0f, static_cast<float>(m_height - 1));
513522 std::memcpy(out, lookup(block->in_frame, static_cast<std::uint32_t>(source_x), static_cast<std::uint32_t>(source_y)), m_pixel_size);
514523 } else {
515524 process_bg(source_x, source_y, block->in_frame, out);
877877 break;
878878 case 2: //Mask type (list)
879879 tmpch = (*(f0r_param_string*)parm);
880 if (strcmp(p->liststr, tmpch)) {
880 if (tmpch && strcmp(p->liststr, tmpch)) {
881881 p->liststr = realloc( p->liststr, strlen(tmpch) + 1 );
882882 strcpy( p->liststr, tmpch );
883883 }
909909 break;
910910 case 7: //Operation 1 (list)
911911 tmpch = (*(f0r_param_string*)parm);
912 if (strcmp(p->liststr, tmpch)) {
912 if (tmpch && strcmp(p->liststr, tmpch)) {
913913 p->liststr = realloc( p->liststr, strlen(tmpch) + 1 );
914914 strcpy( p->liststr, tmpch );
915915 }
926926 break;
927927 case 9: //Operation 2 (list)
928928 tmpch = (*(f0r_param_string*)parm);
929 if (strcmp(p->liststr, tmpch)) {
929 if (tmpch && strcmp(p->liststr, tmpch)) {
930930 p->liststr = realloc( p->liststr, strlen(tmpch) + 1 );
931931 strcpy( p->liststr, tmpch );
932932 }
00 #include <random>
1 #include <cstdint>
12
23 struct pixshift0r
34 {
67
78 unsigned int m_shift_intensity;
89 unsigned int m_block_height;
9
10
1011 // If m_block_height == 0, then these bound block heights
1112 unsigned int m_block_height_min;
1213 unsigned int m_block_height_max;
1314
14 // FIXME: It might be better to make it global variable.
15 std::random_device m_rng_dev;
16
17 std::uniform_int_distribution<long long int> m_shift_rng;
15 std::mt19937 m_rng;
16
17 std::uniform_int_distribution<int64_t> m_shift_rng;
1818 std::uniform_int_distribution<unsigned int> m_block_height_rng;
1919
2020 pixshift0r(unsigned int width, unsigned int height)
21 : m_width(width), m_height(height), m_block_height(0) {}
21 : m_width(width), m_height(height), m_block_height(0),
22 m_shift_intensity(0), m_block_height_min(1), m_block_height_max(1),
23 m_rng(std::random_device{}()),
24 m_shift_rng(0, 0), m_block_height_rng(1, 1) {}
2225
2326 void process(const uint32_t *inframe, uint32_t *outframe)
2427 {
2528 for (unsigned int b = 0; b < m_height;)
2629 {
27 unsigned int block_height = m_block_height ? m_block_height : m_block_height_rng(m_rng_dev);
30 unsigned int block_height = m_block_height ? m_block_height : m_block_height_rng(m_rng);
2831
2932 // Number of rows to shift is either block size or
3033 // what we can *safely* operate on (avoids corruption).
3134 block_height = std::min(block_height, m_height - b);
3235
33 long long shift = m_shift_rng(m_rng_dev);
36 // Ensure we always make progress
37 if (block_height == 0)
38 block_height = 1;
39
40 int64_t shift = m_shift_intensity ? m_shift_rng(m_rng) : 0;
3441
3542 for (unsigned int j = 0; j < block_height; ++j)
3643 {
37 // NOTE: Faulty implementations, such as FFmpeg don't
38 // check for (width % 8 == 0 && height % 8 == 0).
39 //
40 // A possible workaround for all filters, is a scale filter:
41 // (e.g "scale=round(in_w/8)*8:round(in_h/8)*8:flags=neighbor").
44 // NOTE: Faulty implementations, such as FFmpeg don't
45 // check for (width % 8 == 0 && height % 8 == 0).
46 //
47 // A possible workaround for all filters, is a scale filter:
48 // (e.g "scale=round(in_w/8)*8:round(in_h/8)*8:flags=neighbor").
4249 //
4350 // https://ffmpeg.org/doxygen/trunk/vf__frei0r_8c_source.html#l00352
4451 size_t pitch = static_cast<size_t>(b + j) * m_width;
7279 this->m_block_height_max = max_bh;
7380
7481 auto rng_params = decltype(this->m_block_height_rng)::param_type(min_bh, max_bh);
75
82
7683 this->m_block_height_rng.param(rng_params);
7784 }
7885
8087 {
8188 this->m_shift_intensity = shift_intensity;
8289
83 long long intensity = static_cast<long long>(shift_intensity);
90 int64_t intensity = static_cast<int64_t>(shift_intensity);
8491
8592 auto rng_params = decltype(this->m_shift_rng)::param_type(-intensity, intensity);
86
93
8794 this->m_shift_rng.param(rng_params);
8895 }
8996 };
101108 pixshift0r *instance = new pixshift0r(width, height);
102109
103110 instance->set_shift_intensity(width / 100);
104 instance->set_block_height_range(height / 100, height / 10);
105
111 instance->set_block_height_range(std::max(1u, height / 100), std::max(1u, height / 10));
112
106113 return instance;
107114 }
108115
129136 info->type = F0R_PARAM_DOUBLE;
130137 info->explanation = "Aggressiveness of row/column-shifting";
131138 break;
132
139
133140 case 1:
134141 info->name = "block_height";
135142 info->type = F0R_PARAM_DOUBLE;
153160 void f0r_get_param_value(
154161 f0r_instance_t instance,
155162 f0r_param_t param,
156 int param_index
163 int param_index
157164 )
158165 {
159166 pixshift0r* context = (pixshift0r*)instance;
177184 void f0r_set_param_value(
178185 f0r_instance_t instance,
179186 f0r_param_t param,
180 int param_index
187 int param_index
181188 )
182189 {
183190 pixshift0r* context = (pixshift0r*)instance;
202209 f0r_instance_t instance,
203210 double,
204211 const uint32_t *inframe,
205 uint32_t *outframe
212 uint32_t *outframe
206213 )
207214 {
208215 static_cast<pixshift0r*>(instance)->process(inframe, outframe);
210217
211218 void f0r_deinit() {}
212219
213 void f0r_destruct (f0r_instance_t instance)
220 void f0r_destruct (f0r_instance_t instance)
214221 {
215222 delete static_cast<pixshift0r*>(instance);
216223 }
242242 {
243243 i = (oy>>16)*w + (ox>>16);
244244 if(i<0) i = 0;
245 if(i>=inst->pixels) i = inst->pixels;
245 if(i>=inst->pixels) i = inst->pixels - 1;
246246 v = inst->current_buffer[i] & 0xfcfcff;
247247 alpha = *src & 0xff000000;
248248 v = (v * 3) + ((*src++) & 0xfcfcff);
4545 #include <math.h>
4646
4747 #include "frei0r.h"
48 #include "frei0r/math.h"
4849
4950
5051 //----------------------------------------------------------
157158 if (size<1) size=1;
158159 kx=size; ky=size;
159160 kx=kx/ar; //kao aspect!=1 (anamorph)
161 kx = MAX(kx, 1);
162 ky = MAX(ky, 1);
160163
161164 black=0;
162165 white=255;
11
22 PLUGINDIR ?= ../build/src
33
4 all: build scan-plugins
4 CXX ?= g++
55
6 scan-plugins:
7 @$(if $(wildcard ${PLUGINDIR}),,>&2 echo "Scan dir not found: ${PLUGINDIR}" && exit 1)
8 @find ${PLUGINDIR} -type f -name '*.so' -exec ./frei0r-info {} \; > tmp.json
6 DEBUG_FLAGS := -O0 -g -ggdb -Wl,-undefined -Wl,dynamic_lookup -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer -fsanitize-address-use-after-scope
7
8 CI_OPT_FLAGS := -O3 -ffast-math -flto -march=native -pipe
9
10 # CI runs: make && make check (see .github/workflow)
11 # so the 'all' target is built in CI
12 all: frei0r-meta frei0r-asan
13 @echo "Test targets available:"
14 @echo "frei0r-run :: build headless test utility (default)"
15 @echo "frei0r-asan :: build ASAN test utility"
16 @echo "frei0r-gui :: build graphical test utility"
17 @echo "check :: run the built test on all plugins"
18 @echo "frei0r-meta :: build metadata plugin scanner"
19 @echo "scan-meta :: scan all plugins and produce metadata"
20
21 frei0r-run: frei0r-run.c
22 @printf 'Build frei0r plugin test run utility\n'
23 $(CXX) $(CI_OPT_FLAGS) -I../include -o frei0r-run frei0r-run.c -ldl
24
25 frei0r-asan: frei0r-run.c
26 @printf 'Build frei0r plugin test run utility\n'
27 $(CXX) $(DEBUG_FLAGS) -I../include -o frei0r-run frei0r-run.c -ldl
28
29 frei0r-gui: frei0r-run.c test-pattern.c test-pattern.h
30 @printf 'Build frei0r plugin test run utility\n'
31 $(CXX) $(CI_OPT_FLAGS) -I../include -o frei0r-run frei0r-run.c test-pattern.c -ldl -lm -lX11 -DGUI
32
33 check: frei0r-run
34 @if [ ! -d "${PLUGINDIR}" ]; then printf 'Scan dir not found: %s\n' "${PLUGINDIR}" >&2; exit 1; fi
35 @find "${PLUGINDIR}" -type f -name '*.so' | \
36 while IFS= read -r f; do \
37 ./frei0r-run -d -p "$$f" || break; done
38
39 frei0r-meta: frei0r-meta.c
40 @printf 'Build frei0r meta-data parsing utility\n'
41 ${CC} -o frei0r-meta -ggdb frei0r-meta.c ${INCLUDES}
42
43 scan-meta: frei0r-meta
44 @if [ ! -d "${PLUGINDIR}" ]; then printf 'Scan dir not found: %s\n' "${PLUGINDIR}" >&2; exit 1; fi
45 @find ${PLUGINDIR} -type f -name '*.so' -exec ./frei0r-meta {} \; > tmp.json
946 @echo "[" > frei0r-plugin-list.json
1047 @head -n -1 tmp.json >> frei0r-plugin-list.json
1148 @echo "}\n]" >> frei0r-plugin-list.json
1249 @rm tmp.json
13 $(info frei0r-plugin-list.json)
50 @printf 'frei0r-plugin-list.json\n'
1451
15 build:
16 @${CC} -o frei0r-info -ggdb frei0r-info.c ${INCLUDES}
52
53
54 generate-metadata: EXTENSION ?= so
55 generate-metadata:
56 @if [ ! -d "${PLUGINDIR}" ]; then printf 'Scan dir not found: %s\n' "${PLUGINDIR}" >&2; exit 1; fi
57 sh extract-plugin-info.sh ${EXTENSION} ${PLUGINDIR}
1758
1859 clean:
1960 rm -f *.o
20 rm -f frei0r-info
61 rm -f frei0r-run frei0r-meta
2162 rm -f *.json
0 #!/bin/sh
1
2 [ -r frei0r-meta ] || gmake
3 tmp=`mktemp`
4 find "${2}" -name "*.$1" |
5 while read -r line; do
6 file=`basename $line`
7 dir=`dirname $line`
8 name=`echo $file | cut -d. -f1`
9 ./frei0r-meta "$line" > $tmp
10 mv $tmp "$dir/$name.json"
11 ls "$dir/$name.json"
12 done
13 rm -f $tmp
+0
-103
test/frei0r-info.c less more
0
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <dlfcn.h>
6 #include <libgen.h>
7
8 #include <frei0r.h>
9
10 // frei0r function prototypes
11 typedef int (*f0r_init_f)(void);
12 typedef void (*f0r_deinit_f)(void);
13 typedef void (*f0r_get_plugin_info_f)(f0r_plugin_info_t *info);
14 typedef void (*f0r_get_param_info_f)(f0r_param_info_t *info, int param_index);
15
16 int main(int argc, char **argv) {
17
18 // instance frei0r pointers
19 static void *dl_handle;
20 static f0r_init_f f0r_init;
21 static f0r_init_f f0r_deinit;
22 static f0r_plugin_info_t pi;
23 static f0r_get_plugin_info_f f0r_get_plugin_info;
24 static f0r_get_param_info_f f0r_get_param_info;
25 static f0r_param_info_t param;
26
27 int c;
28
29 if(argc<2) exit(1);
30 const char *file = basename(argv[1]);
31 const char *dir = dirname(argv[1]);
32 char path[256];;
33 snprintf(path, 255,"%s/%s",dir,file);
34 // fprintf(stderr,"%s %s\n",argv[0], file);
35 // load shared library
36 dl_handle = dlopen(path, RTLD_NOW|RTLD_LOCAL);
37 if(!dl_handle) {
38 fprintf(stderr,"error: %s\n",dlerror());
39 exit(1);
40 }
41 // get plugin function calls
42 f0r_init = dlsym(dl_handle,"f0r_init");
43 f0r_deinit = dlsym(dl_handle,"f0r_deinit");
44 f0r_get_plugin_info = dlsym(dl_handle,"f0r_get_plugin_info");
45 f0r_get_param_info = dlsym(dl_handle,"f0r_get_param_info");
46 // always initialize plugin first
47 f0r_init();
48 // get info about plugin
49 f0r_get_plugin_info(&pi);
50 fprintf(stdout,
51 "{\n \"name\":\"%s\",\n \"type\":\"%s\",\n \"author\":\"%s\",\n"
52 " \"explanation\":\"%s\",\n \"color_model\":\"%s\",\n"
53 " \"frei0r_version\":\"%d\",\n \"version\":\"%d.%d\",\n \"num_params\":\"%d\"",
54 pi.name,
55 pi.plugin_type == F0R_PLUGIN_TYPE_FILTER ? "filter" :
56 pi.plugin_type == F0R_PLUGIN_TYPE_SOURCE ? "source" :
57 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER2 ? "mixer2" :
58 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER3 ? "mixer3" : "unknown",
59 pi.author, pi.explanation,
60 pi.color_model == F0R_COLOR_MODEL_BGRA8888 ? "bgra8888" :
61 pi.color_model == F0R_COLOR_MODEL_RGBA8888 ? "rgba8888" :
62 pi.color_model == F0R_COLOR_MODEL_PACKED32 ? "packed32" : "unknown",
63 pi.frei0r_version, pi.major_version, pi.minor_version, pi.num_params);
64
65 /* // check icon */
66 /* char icon[256]; */
67 /* char *dot = rindex(file, '.'); */
68 /* *dot = 0x0; */
69 /* snprintf(icon,255,"%s/%s.png",dir,file); */
70 /* FILE *icon_fd = fopen(icon,"r"); */
71 /* if(icon_fd) { */
72 /* fprintf(stderr," icon found: %s\n",icon); */
73 /* } */
74
75 // get info about params
76 if(pi.num_params>0) {
77 fprintf(stdout,",\n \"params\":[\n");
78 for(c=0; c<pi.num_params; c++) {
79 f0r_get_param_info(&param, c);
80 fprintf(stdout,
81 " {\n \"name\":\"%s\",\n \"type\":\"%s\",\n \"explanation\":\"%s\"\n }",
82 param.name,
83 param.type == F0R_PARAM_BOOL ? "bool" :
84 param.type == F0R_PARAM_COLOR ? "color" :
85 param.type == F0R_PARAM_DOUBLE ? "number" :
86 param.type == F0R_PARAM_POSITION ? "position" :
87 param.type == F0R_PARAM_STRING ? "string" : "unknown",
88 param.explanation);
89 if(pi.num_params>c+1) {
90 fprintf(stdout,",\n");
91 } else {
92 fprintf(stdout,"\n");
93 }
94 }
95 fprintf(stdout," ]\n");
96 }
97 fprintf(stdout,"\n},\n");
98 fflush(stdout);
99 f0r_deinit();
100 dlclose(dl_handle);
101 exit(0);
102 }
0 /* This file is part of frei0r (https://frei0r.dyne.org)
1 *
2 * Copyright (C) 2024-2025 Dyne.org foundation
3 * designed, written and maintained by Denis Roio <jaromil@dyne.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <errno.h>
23 #include <string.h>
24 #include <dlfcn.h>
25 #include <libgen.h>
26
27 #include <frei0r.h>
28
29 // frei0r function prototypes
30 typedef int (*f0r_init_f)(void);
31 typedef void (*f0r_deinit_f)(void);
32 typedef void (*f0r_get_plugin_info_f)(f0r_plugin_info_t *info);
33 typedef void (*f0r_get_param_info_f)(f0r_param_info_t *info, int param_index);
34
35 int main(int argc, char **argv) {
36
37 // instance frei0r pointers
38 static void *dl_handle;
39 static f0r_init_f f0r_init;
40 static f0r_init_f f0r_deinit;
41 static f0r_plugin_info_t pi;
42 static f0r_get_plugin_info_f f0r_get_plugin_info;
43 static f0r_get_param_info_f f0r_get_param_info;
44 static f0r_param_info_t param;
45
46 int c;
47
48 if(argc<2) exit(1);
49 const char *file = basename(argv[1]);
50 const char *dir = dirname(argv[1]);
51 char path[256];;
52 snprintf(path, 255,"%s/%s",dir,file);
53 // fprintf(stderr,"%s %s\n",argv[0], file);
54 // load shared library
55 dl_handle = dlopen(path, RTLD_NOW|RTLD_LOCAL);
56 if(!dl_handle) {
57 fprintf(stderr,"error: %s\n",dlerror());
58 exit(1);
59 }
60 // get plugin function calls
61 f0r_init = dlsym(dl_handle,"f0r_init");
62 f0r_deinit = dlsym(dl_handle,"f0r_deinit");
63 f0r_get_plugin_info = dlsym(dl_handle,"f0r_get_plugin_info");
64 f0r_get_param_info = dlsym(dl_handle,"f0r_get_param_info");
65 // always initialize plugin first
66 f0r_init();
67 // get info about plugin
68 f0r_get_plugin_info(&pi);
69 fprintf(stdout,
70 "{\n \"name\":\"%s\",\n \"type\":\"%s\",\n \"author\":\"%s\",\n"
71 " \"explanation\":\"%s\",\n \"color_model\":\"%s\",\n"
72 " \"frei0r_version\":\"%d\",\n \"version\":\"%d.%d\",\n \"num_params\":\"%d\"",
73 pi.name,
74 pi.plugin_type == F0R_PLUGIN_TYPE_FILTER ? "filter" :
75 pi.plugin_type == F0R_PLUGIN_TYPE_SOURCE ? "source" :
76 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER2 ? "mixer2" :
77 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER3 ? "mixer3" : "unknown",
78 pi.author, pi.explanation,
79 pi.color_model == F0R_COLOR_MODEL_BGRA8888 ? "bgra8888" :
80 pi.color_model == F0R_COLOR_MODEL_RGBA8888 ? "rgba8888" :
81 pi.color_model == F0R_COLOR_MODEL_PACKED32 ? "packed32" : "unknown",
82 pi.frei0r_version, pi.major_version, pi.minor_version, pi.num_params);
83
84 /* // check icon */
85 /* char icon[256]; */
86 /* char *dot = rindex(file, '.'); */
87 /* *dot = 0x0; */
88 /* snprintf(icon,255,"%s/%s.png",dir,file); */
89 /* FILE *icon_fd = fopen(icon,"r"); */
90 /* if(icon_fd) { */
91 /* fprintf(stderr," icon found: %s\n",icon); */
92 /* } */
93
94 // get info about params
95 if(pi.num_params>0) {
96 fprintf(stdout,",\n \"params\":[\n");
97 for(c=0; c<pi.num_params; c++) {
98 f0r_get_param_info(&param, c);
99 fprintf(stdout,
100 " {\n \"name\":\"%s\",\n \"type\":\"%s\",\n \"explanation\":\"%s\"\n }",
101 param.name,
102 param.type == F0R_PARAM_BOOL ? "bool" :
103 param.type == F0R_PARAM_COLOR ? "color" :
104 param.type == F0R_PARAM_DOUBLE ? "number" :
105 param.type == F0R_PARAM_POSITION ? "position" :
106 param.type == F0R_PARAM_STRING ? "string" : "unknown",
107 param.explanation);
108 if(pi.num_params>c+1) {
109 fprintf(stdout,",\n");
110 } else {
111 fprintf(stdout,"\n");
112 }
113 }
114 fprintf(stdout," ]\n");
115 }
116 fprintf(stdout,"\n}\n");
117 fflush(stdout);
118 f0r_deinit();
119 dlclose(dl_handle);
120 exit(0);
121 }
0 /* This file is part of frei0r (https://frei0r.dyne.org)
1 *
2 * Copyright (C) 2024-2025 Dyne.org foundation
3 * designed, written and maintained by Denis Roio <jaromil@dyne.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 */
19
20 #include <unistd.h>
21 #include <libgen.h>
22 #include <dlfcn.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <math.h>
27
28 #include <frei0r.h>
29 #include "test-pattern.h"
30
31 #if defined(__linux__) && defined(GUI)
32 #pragma message "Compiling GUI test"
33 #include <X11/Xlib.h>
34 #include <X11/Xutil.h>
35 #endif
36
37 // frei0r function prototypes
38 typedef int (*f0r_init_f)(void);
39 typedef void (*f0r_deinit_f)(void);
40 typedef void (*f0r_get_plugin_info_f)(f0r_plugin_info_t *info);
41 typedef void (*f0r_get_param_info_f)(f0r_param_info_t *info, int param_index);
42 typedef f0r_instance_t (*f0r_construct_f)(unsigned int width, unsigned int height);
43 typedef void (*f0r_update_f)(f0r_instance_t instance,
44 double time, const uint32_t* inframe, uint32_t* outframe);
45 typedef void (*f0r_update2_f)(f0r_instance_t instance, double time,
46 const uint32_t* inframe1, const uint32_t* inframe2,
47 const uint32_t* inframe3, uint32_t* outframe);
48 typedef void (*f0r_destruct_f)(f0r_instance_t instance);
49 typedef void (*f0r_set_param_value_f)(f0r_instance_t instance, f0r_param_t param, int param_index);
50 typedef void (*f0r_get_param_value_f)(f0r_instance_t instance, f0r_param_t param, int param_index);
51
52
53 // Generate a simple color bar test pattern
54 void generate_test_pattern(uint32_t* frame, int width, int height, int color_model, int pattern_variant) {
55 // Create color bars: red, green, blue, white, black, cyan, magenta, yellow
56 int bar_width = width / 8;
57
58 for (int y = 0; y < height; y++) {
59 for (int x = 0; x < width; x++) {
60 int bar_index = x / bar_width;
61 if (bar_index >= 8) bar_index = 7;
62
63 // Rotate pattern based on variant
64 if (pattern_variant == 1) {
65 // Vertical bars instead
66 bar_index = y / (height / 8);
67 if (bar_index >= 8) bar_index = 7;
68 } else if (pattern_variant == 2) {
69 // Checkerboard
70 bar_index = ((x / bar_width) + (y / (height / 8))) % 8;
71 }
72
73 uint8_t r, g, b, a = 255;
74
75 switch (bar_index) {
76 case 0: // Red
77 r = 255; g = 0; b = 0;
78 break;
79 case 1: // Green
80 r = 0; g = 255; b = 0;
81 break;
82 case 2: // Blue
83 r = 0; g = 0; b = 255;
84 break;
85 case 3: // White
86 r = 255; g = 255; b = 255;
87 break;
88 case 4: // Black
89 r = 0; g = 0; b = 0;
90 break;
91 case 5: // Cyan
92 r = 0; g = 255; b = 255;
93 break;
94 case 6: // Magenta
95 r = 255; g = 0; b = 255;
96 break;
97 case 7: // Yellow
98 r = 255; g = 255; b = 0;
99 break;
100 default:
101 r = 128; g = 128; b = 128;
102 break;
103 }
104
105 // Add some vertical variation for visual interest
106 if (y < height / 4) {
107 // Top quarter: keep original colors
108 } else if (y < height / 2) {
109 // Second quarter: darker
110 r = r * 0.7;
111 g = g * 0.7;
112 b = b * 0.7;
113 } else if (y < 3 * height / 4) {
114 // Third quarter: even darker
115 r = r * 0.4;
116 g = g * 0.4;
117 b = b * 0.4;
118 } else {
119 // Bottom quarter: much darker
120 r = r * 0.2;
121 g = g * 0.2;
122 b = b * 0.2;
123 }
124
125 if (color_model == F0R_COLOR_MODEL_BGRA8888) {
126 frame[y * width + x] = (a << 24) | (r << 16) | (g << 8) | b;
127 } else {
128 frame[y * width + x] = (a << 24) | (b << 16) | (g << 8) | r;
129 }
130 }
131 }
132 }
133
134 // Test parameters by cycling through different values
135 void test_parameters(f0r_instance_t instance, f0r_set_param_value_f f0r_set_param_value,
136 f0r_get_param_info_f f0r_get_param_info, int num_params, int frame_count) {
137 f0r_param_info_t param_info;
138 double double_val;
139 f0r_param_color_t color_val;
140 f0r_param_position_t position_val;
141
142 for (int i = 0; i < num_params; i++) {
143 f0r_get_param_info(&param_info, i);
144
145 switch (param_info.type) {
146 case F0R_PARAM_BOOL:
147 // Alternate between 0.0 and 1.0 every 30 frames
148 double_val = (frame_count / 30) % 2;
149 f0r_set_param_value(instance, (f0r_param_t)&double_val, i);
150 break;
151
152 case F0R_PARAM_DOUBLE:
153 // Cycle through values 0.0 to 1.0 over 60 frames
154 double_val = (frame_count % 60) / 60.0;
155 f0r_set_param_value(instance, (f0r_param_t)&double_val, i);
156 break;
157
158 case F0R_PARAM_COLOR:
159 // Cycle through different colors
160 switch ((frame_count / 20) % 4) {
161 case 0: // Red
162 color_val.r = 1.0; color_val.g = 0.0; color_val.b = 0.0;
163 break;
164 case 1: // Green
165 color_val.r = 0.0; color_val.g = 1.0; color_val.b = 0.0;
166 break;
167 case 2: // Blue
168 color_val.r = 0.0; color_val.g = 0.0; color_val.b = 1.0;
169 break;
170 case 3: // White
171 color_val.r = 1.0; color_val.g = 1.0; color_val.b = 1.0;
172 break;
173 }
174 f0r_set_param_value(instance, (f0r_param_t)&color_val, i);
175 break;
176
177 case F0R_PARAM_POSITION:
178 // Move position in a circular pattern
179 position_val.x = 0.5 + 0.4 * sin(frame_count * 0.1);
180 position_val.y = 0.5 + 0.4 * cos(frame_count * 0.1);
181 f0r_set_param_value(instance, (f0r_param_t)&position_val, i);
182 break;
183
184 case F0R_PARAM_STRING:
185 {
186 // For string parameters, use "0" as default
187 char *string_val = "0";
188 f0r_set_param_value(instance, (f0r_param_t)&string_val, i);
189 break;
190 }
191
192 default:
193 // For unknown parameter types, set to middle value
194 double_val = 0.5;
195 f0r_set_param_value(instance, (f0r_param_t)&double_val, i);
196 break;
197 }
198 }
199 }
200
201 int main(int argc, char* argv[]) {
202 // instance frei0r pointers
203 static void *dl_handle;
204 static f0r_init_f f0r_init;
205 static f0r_deinit_f f0r_deinit;
206 static f0r_plugin_info_t pi;
207 static f0r_get_plugin_info_f f0r_get_plugin_info;
208 static f0r_get_param_info_f f0r_get_param_info;
209 static f0r_param_info_t param;
210 static f0r_instance_t instance;
211 static f0r_construct_f f0r_construct;
212 static f0r_update_f f0r_update;
213 static f0r_destruct_f f0r_destruct;
214 static f0r_set_param_value_f f0r_set_param_value;
215 static f0r_get_param_value_f f0r_get_param_value;
216
217 const char *usage = "Usage: frei0r-run [-tdg] [-f frames] -p <frei0r_plugin_file>\n"
218 " -d debug mode\n"
219 " -g graphical display mode (Linux/WSL)\n"
220 " -f frames number of frames to process (default: 100)\n"
221 " -p plugin path to frei0r plugin file";
222 if (argc < 2) {
223 fprintf(stderr,"%s\n",usage);
224 return -1;
225 }
226
227 int opt;
228 #if defined(GUI)
229 int graphical = 1;
230 #else
231 int graphical = 0;
232 #endif
233 int debug = 0;
234 int frames = 100; // Number of frames to test
235 char plugin_file[512];
236 plugin_file[0] = '\0';
237 while((opt = getopt(argc, argv, "tdgf:p:")) != -1) {
238 switch(opt) {
239 case 'd':
240 debug = 1;
241 break;
242 case 'g':
243 graphical = 1;
244 break;
245 case 'f':
246 frames = atoi(optarg);
247 break;
248 case 'p':
249 snprintf(plugin_file, 511, "%s", optarg);
250 break;
251 }
252 }
253
254 if (plugin_file[0] == '\0') {
255 fprintf(stderr, "Error: plugin file required (-p option)\n%s\n", usage);
256 return -1;
257 }
258
259 // Set fixed video properties for test pattern
260 int frame_width = 640;
261 int frame_height = 480;
262 int fps = 30;
263
264 const char *file = basename(plugin_file);
265 const char *dir = dirname(plugin_file);
266 char path[256];;
267 snprintf(path, 255,"%s/%s",dir,file);
268 // fprintf(stderr,"%s %s\n",argv[0], file);
269 // load shared library
270 dl_handle = dlopen(path, RTLD_NOW|RTLD_LOCAL|RTLD_NODELETE);
271 if(!dl_handle) {
272 fprintf(stderr,"error: %s\n",dlerror());
273 exit(1);
274 }
275 // get plugin function calls
276 f0r_init = (f0r_init_f) dlsym(dl_handle,"f0r_init");
277 f0r_deinit = (f0r_deinit_f) dlsym(dl_handle,"f0r_deinit");
278 f0r_get_plugin_info = (f0r_get_plugin_info_f) dlsym(dl_handle,"f0r_get_plugin_info");
279 f0r_get_param_info = (f0r_get_param_info_f) dlsym(dl_handle,"f0r_get_param_info");
280 f0r_construct = (f0r_construct_f) dlsym(dl_handle,"f0r_construct");
281 f0r_update = (f0r_update_f) dlsym(dl_handle,"f0r_update");
282 f0r_destruct = (f0r_destruct_f) dlsym(dl_handle,"f0r_destruct");
283 f0r_set_param_value = (f0r_set_param_value_f) dlsym(dl_handle,"f0r_set_param_value");
284 f0r_get_param_value = (f0r_get_param_value_f) dlsym(dl_handle,"f0r_get_param_value");
285
286 // always initialize plugin first
287 f0r_init();
288 // get info about plugin
289 f0r_get_plugin_info(&pi);
290 const char *frei0r_color_model = (pi.color_model == F0R_COLOR_MODEL_BGRA8888 ? "bgra8888" :
291 pi.color_model == F0R_COLOR_MODEL_RGBA8888 ? "rgba8888" :
292 pi.color_model == F0R_COLOR_MODEL_PACKED32 ? "packed32" : "unknown");
293
294 if(debug) {
295 fprintf(stderr,"{\n \"name\":\"%s\",\n \"type\":\"%s\",\n \"color_model\":\"%s\",\n \"num_params\":\"%d\"\n}",
296 pi.name,
297 pi.plugin_type == F0R_PLUGIN_TYPE_FILTER ? "filter" :
298 pi.plugin_type == F0R_PLUGIN_TYPE_SOURCE ? "source" :
299 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER2 ? "mixer2" :
300 pi.plugin_type == F0R_PLUGIN_TYPE_MIXER3 ? "mixer3" : "unknown",
301 frei0r_color_model,
302 pi.num_params);
303 // Print parameter information
304 if (pi.num_params > 0) {
305 fprintf(stderr,",\n \"parameters\":[\n");
306 for (int i = 0; i < pi.num_params; i++) {
307 f0r_get_param_info(&param, i);
308 const char* param_type =
309 param.type == F0R_PARAM_BOOL ? "bool" :
310 param.type == F0R_PARAM_DOUBLE ? "double" :
311 param.type == F0R_PARAM_COLOR ? "color" :
312 param.type == F0R_PARAM_POSITION ? "position" :
313 param.type == F0R_PARAM_STRING ? "string" : "unknown";
314 fprintf(stderr," {\"name\":\"%s\",\"type\":\"%s\",\"explanation\":\"%s\"}",
315 param.name, param_type, param.explanation);
316 if (i < pi.num_params - 1) fprintf(stderr,",\n");
317 }
318 fprintf(stderr,"\n ]\n");
319 }
320 fprintf(stderr,"}\n");
321 }
322
323 instance = f0r_construct(frame_width, frame_height);
324
325 uint32_t *input_buffer = NULL;
326 uint32_t *input_buffer2 = NULL;
327 uint32_t *input_buffer3 = NULL;
328 uint32_t *output_buffer;
329
330 // Allocate buffers based on plugin type
331 if (pi.plugin_type == F0R_PLUGIN_TYPE_FILTER) {
332 input_buffer = (uint32_t*)calloc(4, frame_width * frame_height);
333 } else if (pi.plugin_type == F0R_PLUGIN_TYPE_MIXER2) {
334 input_buffer = (uint32_t*)calloc(4, frame_width * frame_height);
335 input_buffer2 = (uint32_t*)calloc(4, frame_width * frame_height);
336 } else if (pi.plugin_type == F0R_PLUGIN_TYPE_MIXER3) {
337 input_buffer = (uint32_t*)calloc(4, frame_width * frame_height);
338 input_buffer2 = (uint32_t*)calloc(4, frame_width * frame_height);
339 input_buffer3 = (uint32_t*)calloc(4, frame_width * frame_height);
340 }
341 // SOURCE type needs no input buffer
342
343 output_buffer = (uint32_t*)calloc(4, frame_width * frame_height);
344
345 #if defined(GUI)
346 // Generate initial test patterns
347 if (input_buffer)
348 generate_animated_test_pattern(input_buffer, frame_width, frame_height, 0, pi.color_model);
349 if (input_buffer2)
350 generate_animated_test_pattern(input_buffer2, frame_width, frame_height, 0, pi.color_model);
351 if (input_buffer3)
352 generate_animated_test_pattern(input_buffer3, frame_width, frame_height, 0, pi.color_model);
353 #else
354 if (input_buffer)
355 generate_test_pattern(input_buffer, frame_width, frame_height, pi.color_model, 0);
356 if (input_buffer2)
357 generate_test_pattern(input_buffer2, frame_width, frame_height, pi.color_model, 1);
358 if (input_buffer3)
359 generate_test_pattern(input_buffer3, frame_width, frame_height, pi.color_model, 2);
360 #endif
361
362 #if defined(__linux__) && defined(GUI)
363 Display *display = NULL;
364 Window window;
365 GC gc;
366 XImage *ximage = NULL;
367
368 if (graphical) {
369 display = XOpenDisplay(NULL);
370 if (!display) {
371 fprintf(stderr, "Warning: Cannot open X display, falling back to headless mode\n");
372 graphical = 0;
373 } else {
374 int screen = DefaultScreen(display);
375 window = XCreateSimpleWindow(display, RootWindow(display, screen),
376 0, 0, frame_width, frame_height, 1,
377 BlackPixel(display, screen),
378 WhitePixel(display, screen));
379
380 XStoreName(display, window, pi.name);
381 XSelectInput(display, window, ExposureMask | KeyPressMask);
382 XMapWindow(display, window);
383 gc = XCreateGC(display, window, 0, NULL);
384
385 Visual *visual = DefaultVisual(display, screen);
386 ximage = XCreateImage(display, visual, 24, ZPixmap, 0,
387 (char*)output_buffer, frame_width, frame_height, 32, 0);
388
389 XFlush(display);
390 }
391 }
392 #endif
393
394 // Load f0r_update2 for mixers
395 f0r_update2_f f0r_update2 = NULL;
396 if (pi.plugin_type == F0R_PLUGIN_TYPE_MIXER2 || pi.plugin_type == F0R_PLUGIN_TYPE_MIXER3) {
397 f0r_update2 = (f0r_update2_f)dlsym(dl_handle, "f0r_update2");
398 if (!f0r_update2) {
399 fprintf(stderr, "Error: Cannot load f0r_update2 for mixer plugin\n");
400 if (input_buffer) free(input_buffer);
401 if (input_buffer2) free(input_buffer2);
402 if (input_buffer3) free(input_buffer3);
403 free(output_buffer);
404 f0r_destruct(instance);
405 f0r_deinit();
406 dlclose(dl_handle);
407 return 1;
408 }
409 }
410
411 // Test the plugin with different parameter values
412 for (int frame = 0; frame < frames; frame++) {
413 #if defined(GUI)
414 // Generate animated test patterns for this frame
415 if (input_buffer)
416 generate_animated_test_pattern(input_buffer, frame_width, frame_height, frame, pi.color_model);
417 if (input_buffer2)
418 generate_animated_test_pattern(input_buffer2, frame_width, frame_height, frame + 10, pi.color_model);
419 if (input_buffer3)
420 generate_animated_test_pattern(input_buffer3, frame_width, frame_height, frame + 20, pi.color_model);
421 #else
422 if (input_buffer)
423 generate_test_pattern(input_buffer, frame_width, frame_height, pi.color_model, 0);
424 if (input_buffer2)
425 generate_test_pattern(input_buffer2, frame_width, frame_height, pi.color_model, 1);
426 if (input_buffer3)
427 generate_test_pattern(input_buffer3, frame_width, frame_height, pi.color_model, 2);
428 #endif
429
430 // Update parameters if the plugin has any
431 if (pi.num_params > 0 && f0r_set_param_value) {
432 test_parameters(instance, f0r_set_param_value, f0r_get_param_info, pi.num_params, frame);
433 }
434
435 // Apply plugin based on type
436 double time = (double)frame / (double)fps;
437
438 switch (pi.plugin_type) {
439 case F0R_PLUGIN_TYPE_SOURCE:
440 f0r_update(instance, time, NULL, output_buffer);
441 break;
442 case F0R_PLUGIN_TYPE_FILTER:
443 f0r_update(instance, time, (const uint32_t*)input_buffer, output_buffer);
444 break;
445 case F0R_PLUGIN_TYPE_MIXER2:
446 f0r_update2(instance, time, (const uint32_t*)input_buffer,
447 (const uint32_t*)input_buffer2, NULL, output_buffer);
448 break;
449 case F0R_PLUGIN_TYPE_MIXER3:
450 f0r_update2(instance, time, (const uint32_t*)input_buffer,
451 (const uint32_t*)input_buffer2, (const uint32_t*)input_buffer3,
452 output_buffer);
453 break;
454 default:
455 fprintf(stderr, "Unknown plugin type: %d\n", pi.plugin_type);
456 break;
457 }
458
459 #if defined(__linux__) && defined(GUI)
460 if (graphical && display) {
461 ximage->data = (char*)output_buffer;
462 XPutImage(display, window, gc, ximage, 0, 0, 0, 0, frame_width, frame_height);
463 XFlush(display);
464
465 // Check for key press to exit early
466 while (XPending(display)) {
467 XEvent event;
468 XNextEvent(display, &event);
469 if (event.type == KeyPress) {
470 fprintf(stderr, "\nInterrupted by user at frame %d\n", frame);
471 frame = frames; // Exit loop
472 break;
473 }
474 }
475
476 usleep(1000000 / fps); // Frame delay
477 }
478 #endif
479
480 if (!graphical && frame % 10 == 0 && debug) {
481 printf("Frame %d processed\n", frame);
482 }
483 }
484
485 #if defined(__linux__) && defined(GUI)
486 if (graphical && display) {
487 ximage->data = NULL; // Prevent XDestroyImage from freeing our buffer
488 XDestroyImage(ximage);
489 XFreeGC(display, gc);
490 XDestroyWindow(display, window);
491 XCloseDisplay(display);
492 }
493 #endif
494
495 if(debug) {
496 const char *plugin_type_name = "unknown";
497 switch (pi.plugin_type) {
498 case F0R_PLUGIN_TYPE_SOURCE: plugin_type_name = "source"; break;
499 case F0R_PLUGIN_TYPE_FILTER: plugin_type_name = "filter"; break;
500 case F0R_PLUGIN_TYPE_MIXER2: plugin_type_name = "mixer2"; break;
501 case F0R_PLUGIN_TYPE_MIXER3: plugin_type_name = "mixer3"; break;
502 }
503 printf("Test completed successfully. Plugin: %s (type: %s)\n", pi.name, plugin_type_name);
504 printf("Tested %d frames with %d parameters\n", frames, pi.num_params);
505 if (pi.plugin_type != F0R_PLUGIN_TYPE_SOURCE) {
506 printf("Input: %dx%d test pattern(s)\n", frame_width, frame_height);
507 }
508 printf("Output: %dx%d processed frames\n", frame_width, frame_height);
509 }
510
511 if (input_buffer) free(input_buffer);
512 if (input_buffer2) free(input_buffer2);
513 if (input_buffer3) free(input_buffer3);
514 free(output_buffer);
515
516 f0r_destruct(instance);
517 f0r_deinit();
518
519 dlclose(dl_handle);
520
521 return 0;
522 }
0 /* This file is part of frei0r (https://frei0r.dyne.org)
1 *
2 * Copyright (C) 2024-2025 Dyne.org foundation
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation, either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 */
18
19 #include "test-pattern.h"
20 #include <math.h>
21 #include <stdio.h>
22
23 #ifndef M_PI
24 #define M_PI 3.14159265358979323846
25 #endif
26
27 static void set_pixel(uint32_t* frame, int width, int height, int x, int y,
28 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
29 if (x >= 0 && x < width && y >= 0 && y < height) {
30 if (color_model == 0) {
31 frame[y * width + x] = (a << 24) | (r << 16) | (g << 8) | b;
32 } else {
33 frame[y * width + x] = (a << 24) | (b << 16) | (g << 8) | r;
34 }
35 }
36 }
37
38 static void draw_circle(uint32_t* frame, int width, int height,
39 int cx, int cy, int radius,
40 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
41 int x_start = cx - radius > 0 ? cx - radius : 0;
42 int x_end = cx + radius < width ? cx + radius : width - 1;
43 int y_start = cy - radius > 0 ? cy - radius : 0;
44 int y_end = cy + radius < height ? cy + radius : height - 1;
45
46 for (int y = y_start; y <= y_end; y++) {
47 for (int x = x_start; x <= x_end; x++) {
48 int dx = x - cx;
49 int dy = y - cy;
50 if (dx * dx + dy * dy <= radius * radius) {
51 set_pixel(frame, width, height, x, y, r, g, b, a, color_model);
52 }
53 }
54 }
55 }
56
57 static void draw_filled_rect(uint32_t* frame, int width, int height,
58 int x1, int y1, int x2, int y2,
59 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
60 if (x1 > x2) { int tmp = x1; x1 = x2; x2 = tmp; }
61 if (y1 > y2) { int tmp = y1; y1 = y2; y2 = tmp; }
62
63 if (x1 < 0) x1 = 0;
64 if (y1 < 0) y1 = 0;
65 if (x2 >= width) x2 = width - 1;
66 if (y2 >= height) y2 = height - 1;
67
68 for (int y = y1; y <= y2; y++) {
69 for (int x = x1; x <= x2; x++) {
70 set_pixel(frame, width, height, x, y, r, g, b, a, color_model);
71 }
72 }
73 }
74
75 static void draw_rotated_square(uint32_t* frame, int width, int height,
76 int cx, int cy, int size, double angle,
77 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
78 double cos_a = cos(angle);
79 double sin_a = sin(angle);
80 int half = size / 2;
81
82 for (int dy = -half; dy <= half; dy++) {
83 for (int dx = -half; dx <= half; dx++) {
84 int rx = (int)(dx * cos_a - dy * sin_a);
85 int ry = (int)(dx * sin_a + dy * cos_a);
86 set_pixel(frame, width, height, cx + rx, cy + ry, r, g, b, a, color_model);
87 }
88 }
89 }
90
91 static void draw_digit(uint32_t* frame, int width, int height,
92 int x, int y, int digit, int scale,
93 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
94 // Simple 5x7 bitmap font for digits 0-9
95 static const uint8_t font[10][7] = {
96 {0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F}, // 0
97 {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, // 1
98 {0x1F, 0x01, 0x01, 0x1F, 0x10, 0x10, 0x1F}, // 2
99 {0x1F, 0x01, 0x01, 0x1F, 0x01, 0x01, 0x1F}, // 3
100 {0x11, 0x11, 0x11, 0x1F, 0x01, 0x01, 0x01}, // 4
101 {0x1F, 0x10, 0x10, 0x1F, 0x01, 0x01, 0x1F}, // 5
102 {0x1F, 0x10, 0x10, 0x1F, 0x11, 0x11, 0x1F}, // 6
103 {0x1F, 0x01, 0x01, 0x02, 0x04, 0x08, 0x10}, // 7
104 {0x1F, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x1F}, // 8
105 {0x1F, 0x11, 0x11, 0x1F, 0x01, 0x01, 0x1F} // 9
106 };
107
108 if (digit < 0 || digit > 9) return;
109
110 for (int row = 0; row < 7; row++) {
111 uint8_t line = font[digit][row];
112 for (int col = 0; col < 5; col++) {
113 if (line & (1 << (4 - col))) {
114 for (int sy = 0; sy < scale; sy++) {
115 for (int sx = 0; sx < scale; sx++) {
116 set_pixel(frame, width, height,
117 x + col * scale + sx,
118 y + row * scale + sy,
119 r, g, b, a, color_model);
120 }
121 }
122 }
123 }
124 }
125 }
126
127 static void draw_number(uint32_t* frame, int width, int height,
128 int x, int y, int number, int scale,
129 uint8_t r, uint8_t g, uint8_t b, uint8_t a, int color_model) {
130 if (number == 0) {
131 draw_digit(frame, width, height, x, y, 0, scale, r, g, b, a, color_model);
132 return;
133 }
134
135 // Count digits
136 int temp = number;
137 int digits = 0;
138 while (temp > 0) {
139 digits++;
140 temp /= 10;
141 }
142
143 // Draw each digit
144 temp = number;
145 for (int i = digits - 1; i >= 0; i--) {
146 int digit = temp % 10;
147 draw_digit(frame, width, height, x + i * 6 * scale, y, digit, scale, r, g, b, a, color_model);
148 temp /= 10;
149 }
150 }
151
152 static void draw_zone_plate_patch(uint32_t* frame, int width, int height,
153 int cx, int cy, int size, int frame_num, int color_model) {
154 double phase = frame_num * 0.05;
155 int half = size / 2;
156
157 for (int dy = -half; dy < half; dy++) {
158 for (int dx = -half; dx < half; dx++) {
159 double dist = sqrt(dx * dx + dy * dy);
160 double freq = dist * 0.3;
161 double value = (sin(freq + phase) + 1.0) * 0.5;
162
163 uint8_t intensity = (uint8_t)(value * 255);
164 set_pixel(frame, width, height, cx + dx, cy + dy,
165 intensity, intensity, intensity, 255, color_model);
166 }
167 }
168 }
169
170 void generate_animated_test_pattern(uint32_t* frame, int width, int height, int frame_num, int color_model) {
171 // 1. Animated radial gradient background
172 int center_x = width / 2;
173 int center_y = height / 2;
174 double max_dist = sqrt(center_x * center_x + center_y * center_y);
175
176 // Color cycling through the animation
177 double color_phase = frame_num * 0.02;
178
179 for (int y = 0; y < height; y++) {
180 for (int x = 0; x < width; x++) {
181 int dx = x - center_x;
182 int dy = y - center_y;
183 double dist = sqrt(dx * dx + dy * dy);
184 double norm_dist = dist / max_dist;
185
186 // Create a pulsing radial gradient with color shift
187 double angle = atan2(dy, dx);
188 double r_val = (sin(norm_dist * M_PI + color_phase) + 1.0) * 0.5;
189 double g_val = (sin(norm_dist * M_PI + color_phase + M_PI * 2.0 / 3.0) + 1.0) * 0.5;
190 double b_val = (sin(norm_dist * M_PI + color_phase + M_PI * 4.0 / 3.0) + 1.0) * 0.5;
191
192 // Add some angular variation
193 double angular_mod = (sin(angle * 3.0 + color_phase * 0.5) + 1.0) * 0.2;
194
195 uint8_t r = (uint8_t)((r_val + angular_mod) * 127 + 32);
196 uint8_t g = (uint8_t)((g_val + angular_mod) * 127 + 32);
197 uint8_t b = (uint8_t)((b_val + angular_mod) * 127 + 32);
198
199 if (color_model == 0) {
200 frame[y * width + x] = (255 << 24) | (r << 16) | (g << 8) | b;
201 } else {
202 frame[y * width + x] = (255 << 24) | (b << 16) | (g << 8) | r;
203 }
204 }
205 }
206
207 // 2. Bouncing circle
208 int circle_radius = 40;
209 int circle_x = (int)(center_x + sin(frame_num * 0.05) * (center_x - circle_radius - 20));
210 int circle_y = (int)(center_y + cos(frame_num * 0.037) * (center_y - circle_radius - 20));
211
212 draw_circle(frame, width, height, circle_x, circle_y, circle_radius,
213 255, 200, 0, 255, color_model);
214
215 // 3. Rotating square
216 int square_size = 60;
217 int square_x = width / 4;
218 int square_y = height / 4;
219 double rotation = frame_num * 0.03;
220
221 draw_rotated_square(frame, width, height, square_x, square_y, square_size, rotation,
222 0, 255, 200, 255, color_model);
223
224 // 4. Horizontal moving bar
225 int bar_height = 20;
226 int bar_y = (int)(height * 0.75 + sin(frame_num * 0.04) * (height * 0.15));
227
228 draw_filled_rect(frame, width, height, 0, bar_y - bar_height/2,
229 width - 1, bar_y + bar_height/2,
230 255, 100, 200, 255, color_model);
231
232 // 5. Zone plate patch in corner
233 int zone_size = 80;
234 draw_zone_plate_patch(frame, width, height,
235 width - zone_size/2 - 10,
236 zone_size/2 + 10,
237 zone_size, frame_num, color_model);
238
239 // 6. Frame counter
240 draw_number(frame, width, height, 10, 10, frame_num, 2,
241 255, 255, 255, 255, color_model);
242 }
0 /* This file is part of frei0r (https://frei0r.dyne.org)
1 *
2 * Copyright (C) 2024-2025 Dyne.org foundation
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation, either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 */
18
19 #ifndef TEST_PATTERN_H
20 #define TEST_PATTERN_H
21
22 #include <stdint.h>
23
24 /**
25 * Generate an animated test pattern for video filter testing
26 *
27 * This creates a comprehensive test pattern that includes:
28 * - Animated radial gradient background (tests color interpolation and smooth transitions)
29 * - Bouncing circle (tests circular features and motion)
30 * - Rotating square (tests corners, rotation, and sharp edges)
31 * - Moving horizontal bar (tests horizontal motion and rectangular shapes)
32 * - Zone plate patch (tests spatial frequency response and aliasing)
33 * - Frame counter (tests text/detail preservation)
34 *
35 * @param frame Output buffer (32-bit per pixel)
36 * @param width Frame width in pixels
37 * @param height Frame height in pixels
38 * @param frame_num Current frame number (for animation)
39 * @param color_model Color model (F0R_COLOR_MODEL_BGRA8888, F0R_COLOR_MODEL_RGBA8888, or F0R_COLOR_MODEL_PACKED32)
40 */
41 void generate_animated_test_pattern(uint32_t* frame, int width, int height, int frame_num, int color_model);
42
43 #endif // TEST_PATTERN_H