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
| 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 |
| 14 | 14 | |
| 15 | 15 | jobs: |
| 16 | 16 | conventional-commits: |
| 17 | name: ๐ Parse PR commits | |
| 17 | name: ๐ Conventional Commits | |
| 18 | 18 | runs-on: ubuntu-latest |
| 19 | 19 | steps: |
| 20 | 20 | - name: ๐ Check PR commit messages follow Conventional Commits |
| 13 | 13 | cancel-in-progress: true |
| 14 | 14 | |
| 15 | 15 | 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' | |
| 20 | 20 | 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 }} | |
| 23 | 24 | steps: |
| 24 | 25 | - uses: actions/checkout@v6 |
| 25 | 26 | - name: Setup Node.js |
| 29 | 30 | - name: Install semantic-release |
| 30 | 31 | run: | |
| 31 | 32 | npm i semantic-release/changelog |
| 32 | - name: Tag release | |
| 33 | id: tag_release | |
| 33 | - name: Plan release | |
| 34 | id: plan_release | |
| 34 | 35 | env: |
| 35 | 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 36 | 37 | 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" | |
| 40 | 42 | 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" | |
| 43 | 45 | fi |
| 44 | 46 | |
| 45 | 47 | linux-build: |
| 46 | 48 | name: ๐ง linux build |
| 47 | 49 | 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' }} | |
| 50 | 52 | steps: |
| 51 | 53 | - uses: actions/checkout@v6 |
| 52 | 54 | - name: apt install deps |
| 53 | 55 | run: | |
| 54 | 56 | 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 | |
| 56 | 58 | - name: Build using cmake+ninja |
| 57 | 59 | run: | |
| 58 | 60 | mkdir build && cd build |
| 59 | cmake -G "Ninja" ../ | |
| 61 | cmake -G "Ninja" -D FREI0R_VERSION=${{ needs.release-plan.outputs.version }} ../ | |
| 60 | 62 | ninja |
| 61 | 63 | - name: Upload linux filter |
| 62 | 64 | uses: actions/upload-artifact@v6 |
| 78 | 80 | with: |
| 79 | 81 | name: release-linux-generator |
| 80 | 82 | 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 | |
| 81 | 88 | |
| 82 | 89 | win-build: |
| 83 | 90 | name: ๐ช win64 build |
| 84 | 91 | 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' }} | |
| 87 | 94 | steps: |
| 88 | 95 | - uses: actions/checkout@v6 |
| 89 | 96 | - uses: ilammy/msvc-dev-cmd@v1 |
| 94 | 101 | - name: Build using nmake |
| 95 | 102 | run: | |
| 96 | 103 | 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 }} ../ | |
| 98 | 105 | nmake |
| 99 | 106 | - name: Upload win64 filter |
| 100 | 107 | uses: actions/upload-artifact@v6 |
| 116 | 123 | with: |
| 117 | 124 | name: release-win64-generator |
| 118 | 125 | 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 | |
| 119 | 131 | |
| 120 | 132 | osx-build: |
| 121 | 133 | name: ๐ osx build |
| 122 | 134 | 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' }} | |
| 125 | 137 | steps: |
| 126 | 138 | - uses: actions/checkout@v6 |
| 127 | 139 | - name: Update Homebrew |
| 134 | 146 | - name: Build using ninja |
| 135 | 147 | run: | |
| 136 | 148 | 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 }} ../ | |
| 138 | 150 | ninja |
| 139 | 151 | - name: Upload osx filter |
| 140 | 152 | uses: actions/upload-artifact@v6 |
| 156 | 168 | with: |
| 157 | 169 | name: release-osx-generator |
| 158 | 170 | 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" | |
| 159 | 203 | |
| 160 | 204 | draft-binary-release: |
| 161 | 205 | 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' }} | |
| 164 | 208 | runs-on: ubuntu-latest |
| 165 | 209 | steps: |
| 166 | 210 | - uses: actions/checkout@v6 |
| 171 | 215 | frei0r-bin |
| 172 | 216 | - name: create compressed archives |
| 173 | 217 | run: | |
| 174 | dst=frei0r-${{ needs.semantic-release.outputs.version }}_win64 | |
| 218 | dst=frei0r-${{ needs.publish-release.outputs.version }}_win64 | |
| 175 | 219 | mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3 |
| 176 | 220 | find frei0r-bin/release-win64-filter -type f -name '*.dll' -exec cp {} $dst/filter \; |
| 177 | 221 | find frei0r-bin/release-win64-generator -type f -name '*.dll' -exec cp {} $dst/generator \; |
| 182 | 226 | cp ChangeLog $dst/ChangeLog.txt |
| 183 | 227 | cp AUTHORS.md $dst/AUTHORS.txt |
| 184 | 228 | 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 | |
| 186 | 231 | zip -r -9 $dst.zip $dst |
| 187 | 232 | |
| 188 | dst=frei0r-${{ needs.semantic-release.outputs.version }}_osx | |
| 233 | dst=frei0r-${{ needs.publish-release.outputs.version }}_osx | |
| 189 | 234 | mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3 |
| 190 | 235 | find frei0r-bin/release-osx-filter -type f -name '*.so' -exec cp {} $dst/filter \; |
| 191 | 236 | find frei0r-bin/release-osx-generator -type f -name '*.so' -exec cp {} $dst/generator \; |
| 196 | 241 | cp ChangeLog $dst/ChangeLog.txt |
| 197 | 242 | cp AUTHORS.md $dst/AUTHORS.txt |
| 198 | 243 | 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 | |
| 200 | 246 | zip -r -9 $dst.zip $dst |
| 201 | 247 | |
| 202 | dst=frei0r-${{ needs.semantic-release.outputs.version }}_linux | |
| 248 | dst=frei0r-${{ needs.publish-release.outputs.version }}_linux | |
| 203 | 249 | mkdir -p $dst/filter $dst/generator $dst/mixer2 $dst/mixer3 |
| 204 | 250 | find frei0r-bin/release-linux-filter -type f -name '*.so' -exec cp {} $dst/filter \; |
| 205 | 251 | find frei0r-bin/release-linux-generator -type f -name '*.so' -exec cp {} $dst/generator \; |
| 210 | 256 | cp ChangeLog $dst/ChangeLog.txt |
| 211 | 257 | cp AUTHORS.md $dst/AUTHORS.txt |
| 212 | 258 | 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 | |
| 214 | 261 | tar cvfz $dst.tar.gz $dst |
| 215 | 262 | |
| 216 | 263 | sha256sum *.zip *.tar.gz > SHA256SUMS.txt |
| 222 | 269 | *.zip |
| 223 | 270 | *.tar.gz |
| 224 | 271 | SHA256SUMS.txt |
| 225 | tag_name: ${{ needs.semantic-release.outputs.version }} | |
| 272 | tag_name: ${{ needs.publish-release.outputs.tag }} | |
| 226 | 273 | draft: true |
| 227 | 274 | prerelease: false |
| 228 | 275 | fail_on_unmatched_files: true |
| 21 | 21 | cancel-in-progress: true |
| 22 | 22 | |
| 23 | 23 | 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 | ||
| 24 | 32 | c-lint: |
| 25 | 33 | name: ๐จ C lint |
| 26 | 34 | runs-on: ubuntu-latest |
| 67 | 75 | runs-on: ubuntu-latest |
| 68 | 76 | steps: |
| 69 | 77 | - 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' | |
| 71 | 88 | 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' }}" | |
| 74 | 96 | - name: ${{ matrix.compiler }} initialize cmake build |
| 75 | 97 | run: | |
| 76 | 98 | mkdir -p build && cd build |
| 77 | cmake -G "Ninja" ../ | |
| 99 | cmake -G "Ninja" -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache .. | |
| 78 | 100 | - name: ${{ matrix.compiler }} run ninja build |
| 101 | if: github.event_name != 'pull_request' || steps.detect.outputs.mode != 'targeted' | |
| 79 | 102 | run: | |
| 80 | 103 | 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 }} | |
| 81 | 109 | - name: ${{ matrix.compiler }} analyze plugins |
| 110 | if: github.event_name != 'pull_request' || steps.detect.outputs.mode != 'targeted' | |
| 82 | 111 | 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 | |
| 0 | 0 | # Build instructions |
| 1 | 1 | |
| 2 | 2 | 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 | |
| 3 | 10 | |
| 4 | 11 | The presence of optional libraries on the system will trigger compilation of extra plugins. These libraries are: |
| 5 | 12 | |
| 16 | 23 | It is recommended to use a separate `build` sub-folder. |
| 17 | 24 | |
| 18 | 25 | ``` |
| 19 | mkdir -p build | |
| 20 | cd build && cmake ../ | |
| 21 | make | |
| 26 | cmake -S . -B build | |
| 27 | cmake --build build | |
| 22 | 28 | ``` |
| 23 | 29 | |
| 24 | 30 | To disable face recognition plugins (recommended when using with MLT): |
| 25 | 31 | ``` |
| 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 | |
| 29 | 34 | ``` |
| 30 | 35 | |
| 31 | Also ninja and nmake are supported through cmake: | |
| 36 | Ninja and nmake are also supported through CMake: | |
| 32 | 37 | ``` |
| 33 | cmake -G 'Ninja' ../ | |
| 34 | cmake -G 'NMake Makefiles' ../ | |
| 38 | cmake -S . -B build -G 'Ninja' | |
| 39 | cmake -S . -B build -G 'NMake Makefiles' | |
| 35 | 40 | ``` |
| 36 | 41 | |
| 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 | ``` | |
| 1 | 1 | |
| 2 | 2 | list (APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) |
| 3 | 3 | |
| 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) | |
| 6 | 16 | |
| 7 | 17 | include(GNUInstallDirs) |
| 8 | 18 |
| 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 |
| 62 | 62 | |
| 63 | 63 | For details see the [BUILD](/BUILD.md) file. |
| 64 | 64 | |
| 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 | ||
| 65 | 81 | ### MS / Windows |
| 66 | 82 | |
| 67 | 83 | We distribute official builds of frei0r plugins as .dll for the Win64 platform from the releases page. |
| 85 | 101 | |
| 86 | 102 | A [frei0r Brew formula](https://formulae.brew.sh/formula/frei0r) is available. |
| 87 | 103 | |
| 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. | |
| 89 | 105 | |
| 90 | 106 | # Documentation |
| 91 | 107 | |
| 159 | 175 | |
| 160 | 176 | 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 |
| 161 | 177 | |
| 162 | ||
| 4 | 4 | |
| 5 | 5 | Name: frei0r |
| 6 | 6 | Description: minimalistic plugin API for video effects |
| 7 | Version: @VERSION@ | |
| 7 | Version: @PROJECT_VERSION@ | |
| 8 | 8 | Libs: |
| 9 | 9 | Cflags: -I${includedir} |
| 10 |
| 90 | 90 | param_ptrs.push_back(&p_loc); |
| 91 | 91 | s_params.push_back(param_info(name,desc,F0R_PARAM_STRING)); |
| 92 | 92 | } |
| 93 | ||
| 94 | ||
| 93 | ||
| 94 | ||
| 95 | 95 | void get_param_value(f0r_param_t param, int param_index) |
| 96 | 96 | { |
| 97 | if (!param || param_index < 0 || param_index >= (int)param_ptrs.size()) | |
| 98 | return; | |
| 99 | ||
| 97 | 100 | void* ptr = param_ptrs[param_index]; |
| 98 | ||
| 101 | ||
| 99 | 102 | switch (s_params[param_index].m_type) |
| 100 | 103 | { |
| 101 | 104 | case F0R_PARAM_BOOL : |
| 124 | 127 | |
| 125 | 128 | void set_param_value(f0r_param_t param, int param_index) |
| 126 | 129 | { |
| 130 | if (!param || param_index < 0 || param_index >= (int)param_ptrs.size()) | |
| 131 | return; | |
| 132 | ||
| 127 | 133 | void* ptr = param_ptrs[param_index]; |
| 128 | ||
| 134 | ||
| 129 | 135 | switch (s_params[param_index].m_type) |
| 130 | 136 | { |
| 131 | 137 | case F0R_PARAM_BOOL : |
| 145 | 151 | = *static_cast<f0r_param_position*>(param); |
| 146 | 152 | break; |
| 147 | 153 | 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; | |
| 150 | 158 | break; |
| 151 | 159 | } |
| 152 | ||
| 153 | } | |
| 154 | ||
| 160 | } | |
| 161 | ||
| 162 | } | |
| 163 | ||
| 155 | 164 | virtual void update(double time, |
| 156 | 165 | uint32_t* out, |
| 157 | 166 | const uint32_t* in1, |
| 296 | 305 | |
| 297 | 306 | void f0r_get_param_info(f0r_param_info_t* info, int param_index) |
| 298 | 307 | { |
| 308 | if (!info || param_index < 0 || param_index >= (int)frei0r::s_params.size()) | |
| 309 | return; | |
| 310 | ||
| 299 | 311 | info->name=frei0r::s_params[param_index].m_name.c_str(); |
| 300 | 312 | info->type=frei0r::s_params[param_index].m_type; |
| 301 | 313 | info->explanation=frei0r::s_params[param_index].m_desc.c_str(); |
| 315 | 327 | delete static_cast<frei0r::fx*>(instance); |
| 316 | 328 | } |
| 317 | 329 | |
| 318 | void f0r_set_param_value(f0r_instance_t instance, | |
| 330 | void f0r_set_param_value(f0r_instance_t instance, | |
| 319 | 331 | f0r_param_t param, int param_index) |
| 320 | 332 | { |
| 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); | |
| 322 | 335 | } |
| 323 | 336 | |
| 324 | 337 | void f0r_get_param_value(f0r_instance_t instance, |
| 325 | 338 | f0r_param_t param, int param_index) |
| 326 | 339 | { |
| 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); | |
| 328 | 342 | } |
| 329 | 343 | |
| 330 | 344 | void f0r_update2(f0r_instance_t instance, double time, |
| 346 | 360 | { |
| 347 | 361 | f0r_update2(instance, time, inframe, 0, 0, outframe); |
| 348 | 362 | } |
| 349 | ||
| 26 | 26 | add_subdirectory (brightness) |
| 27 | 27 | add_subdirectory (bw0r) |
| 28 | 28 | add_subdirectory (cartoon) |
| 29 | add_subdirectory (camerashake) | |
| 29 | 30 | add_subdirectory (cluster) |
| 30 | 31 | add_subdirectory (colgate) |
| 31 | 32 | add_subdirectory (coloradj) |
| 57 | 58 | add_subdirectory (heatmap0r) |
| 58 | 59 | add_subdirectory (hueshift0r) |
| 59 | 60 | add_subdirectory (invert0r) |
| 60 | add_subdirectory (kaleid0sc0pe) | |
| 61 | add_subdirectory (kaleid0sc0pe) | |
| 61 | 62 | add_subdirectory (keyspillm0pup) |
| 62 | 63 | add_subdirectory (lenscorrection) |
| 63 | 64 | add_subdirectory (letterb0xed) |
| 102 | 103 | add_subdirectory (twolay0r) |
| 103 | 104 | add_subdirectory (vertigo) |
| 104 | 105 | add_subdirectory (vignette) |
| 106 | add_subdirectory (water) | |
| 185 | 185 | } |
| 186 | 186 | } |
| 187 | 187 | |
| 188 | void f0r_destruct(f0r_instance_t s) | |
| 188 | void f0r_destruct(f0r_instance_t inst) | |
| 189 | 189 | { |
| 190 | s0ft0tsu_t* s = inst; | |
| 191 | free(s->lumaframe); | |
| 190 | 192 | free(s); |
| 191 | 193 | } |
| 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 | } |
| 330 | 330 | break; |
| 331 | 331 | case 3: |
| 332 | 332 | inst->pointNumber = floor(*((f0r_param_double *)param) * 10); |
| 333 | inst->pointNumber = MIN(inst->pointNumber, 5); | |
| 333 | 334 | break; |
| 334 | 335 | case 4: |
| 335 | 336 | inst->formula = *((f0r_param_double *)param); |
| 737 | 738 | double *points = (double*)calloc(inst->pointNumber * 2, sizeof(double)); |
| 738 | 739 | int i = inst->pointNumber * 2; |
| 739 | 740 | //copy point values |
| 740 | while(--i > 0) | |
| 741 | while(--i >= 0) | |
| 741 | 742 | points[i] = inst->points[i]; |
| 742 | 743 | //sort point values by X component |
| 743 | 744 | for(i = 1; i < inst->pointNumber; i++) |
| 798 | 799 | points = (double*)calloc(inst->pointNumber * 2, sizeof(double)); |
| 799 | 800 | i = inst->pointNumber * 2; |
| 800 | 801 | //copy point values |
| 801 | while(--i > 0) | |
| 802 | while(--i >= 0) | |
| 802 | 803 | points[i] = inst->points[i]; |
| 803 | 804 | //sort point values by X component |
| 804 | 805 | for(i = 1; i < inst->pointNumber; i++) |
| 15 | 15 | |
| 16 | 16 | ~delay0r() |
| 17 | 17 | { |
| 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(); ) | |
| 19 | 19 | { |
| 20 | 20 | delete[] i->second; |
| 21 | 21 | i=buffer.erase(i); |
| 28 | 28 | { |
| 29 | 29 | unsigned int* reusable = 0; |
| 30 | 30 | // 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(); ) | |
| 32 | 32 | { |
| 33 | 33 | if (i->first < (time - delay) || i->first >= time) |
| 34 | 34 | { |
| 39 | 39 | reusable = i->second; |
| 40 | 40 | |
| 41 | 41 | i=buffer.erase(i); |
| 42 | } | |
| 43 | else | |
| 44 | { | |
| 45 | ++i; | |
| 42 | 46 | } |
| 43 | 47 | } |
| 44 | 48 | |
| 148 | 148 | uint32_t v = 0; |
| 149 | 149 | for(unsigned int i = 0; i < w * h; i++) |
| 150 | 150 | { |
| 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) | |
| 152 | 154 | { |
| 153 | 155 | if(larger_x_comp) |
| 154 | 156 | { |
| 22 | 22 | * |
| 23 | 23 | */ |
| 24 | 24 | #include "kaleid0sc0pe.h" |
| 25 | #include "frei0r/math.h" | |
| 25 | 26 | #include <memory> |
| 26 | 27 | #include <cstring> |
| 27 | 28 | #ifndef NO_FUTURE |
| 438 | 439 | __m128 ge_height = _mm_cmpge_ps(source_y, m_sse_height); |
| 439 | 440 | 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)); |
| 440 | 441 | |
| 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))); | |
| 443 | 444 | |
| 444 | 445 | std::int32_t* sx = reinterpret_cast<std::int32_t*>(&source_xi); |
| 445 | 446 | 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 | ||
| 446 | 453 | std::memcpy(out, lookup(block->in_frame, sx[0], sy[0]), m_pixel_size); |
| 447 | 454 | out += m_pixel_size; |
| 448 | 455 | std::memcpy(out, lookup(block->in_frame, sx[1], sy[1]), m_pixel_size); |
| 510 | 517 | } else if (source_y > m_height - 10e-4f) { |
| 511 | 518 | source_y = m_height - (source_y - m_height + 10e-4f); |
| 512 | 519 | } |
| 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)); | |
| 513 | 522 | std::memcpy(out, lookup(block->in_frame, static_cast<std::uint32_t>(source_x), static_cast<std::uint32_t>(source_y)), m_pixel_size); |
| 514 | 523 | } else { |
| 515 | 524 | process_bg(source_x, source_y, block->in_frame, out); |
| 877 | 877 | break; |
| 878 | 878 | case 2: //Mask type (list) |
| 879 | 879 | tmpch = (*(f0r_param_string*)parm); |
| 880 | if (strcmp(p->liststr, tmpch)) { | |
| 880 | if (tmpch && strcmp(p->liststr, tmpch)) { | |
| 881 | 881 | p->liststr = realloc( p->liststr, strlen(tmpch) + 1 ); |
| 882 | 882 | strcpy( p->liststr, tmpch ); |
| 883 | 883 | } |
| 909 | 909 | break; |
| 910 | 910 | case 7: //Operation 1 (list) |
| 911 | 911 | tmpch = (*(f0r_param_string*)parm); |
| 912 | if (strcmp(p->liststr, tmpch)) { | |
| 912 | if (tmpch && strcmp(p->liststr, tmpch)) { | |
| 913 | 913 | p->liststr = realloc( p->liststr, strlen(tmpch) + 1 ); |
| 914 | 914 | strcpy( p->liststr, tmpch ); |
| 915 | 915 | } |
| 926 | 926 | break; |
| 927 | 927 | case 9: //Operation 2 (list) |
| 928 | 928 | tmpch = (*(f0r_param_string*)parm); |
| 929 | if (strcmp(p->liststr, tmpch)) { | |
| 929 | if (tmpch && strcmp(p->liststr, tmpch)) { | |
| 930 | 930 | p->liststr = realloc( p->liststr, strlen(tmpch) + 1 ); |
| 931 | 931 | strcpy( p->liststr, tmpch ); |
| 932 | 932 | } |
| 0 | 0 | #include <random> |
| 1 | #include <cstdint> | |
| 1 | 2 | |
| 2 | 3 | struct pixshift0r |
| 3 | 4 | { |
| 6 | 7 | |
| 7 | 8 | unsigned int m_shift_intensity; |
| 8 | 9 | unsigned int m_block_height; |
| 9 | ||
| 10 | ||
| 10 | 11 | // If m_block_height == 0, then these bound block heights |
| 11 | 12 | unsigned int m_block_height_min; |
| 12 | 13 | unsigned int m_block_height_max; |
| 13 | 14 | |
| 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; | |
| 18 | 18 | std::uniform_int_distribution<unsigned int> m_block_height_rng; |
| 19 | 19 | |
| 20 | 20 | 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) {} | |
| 22 | 25 | |
| 23 | 26 | void process(const uint32_t *inframe, uint32_t *outframe) |
| 24 | 27 | { |
| 25 | 28 | for (unsigned int b = 0; b < m_height;) |
| 26 | 29 | { |
| 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); | |
| 28 | 31 | |
| 29 | 32 | // Number of rows to shift is either block size or |
| 30 | 33 | // what we can *safely* operate on (avoids corruption). |
| 31 | 34 | block_height = std::min(block_height, m_height - b); |
| 32 | 35 | |
| 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; | |
| 34 | 41 | |
| 35 | 42 | for (unsigned int j = 0; j < block_height; ++j) |
| 36 | 43 | { |
| 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"). | |
| 42 | 49 | // |
| 43 | 50 | // https://ffmpeg.org/doxygen/trunk/vf__frei0r_8c_source.html#l00352 |
| 44 | 51 | size_t pitch = static_cast<size_t>(b + j) * m_width; |
| 72 | 79 | this->m_block_height_max = max_bh; |
| 73 | 80 | |
| 74 | 81 | auto rng_params = decltype(this->m_block_height_rng)::param_type(min_bh, max_bh); |
| 75 | ||
| 82 | ||
| 76 | 83 | this->m_block_height_rng.param(rng_params); |
| 77 | 84 | } |
| 78 | 85 | |
| 80 | 87 | { |
| 81 | 88 | this->m_shift_intensity = shift_intensity; |
| 82 | 89 | |
| 83 | long long intensity = static_cast<long long>(shift_intensity); | |
| 90 | int64_t intensity = static_cast<int64_t>(shift_intensity); | |
| 84 | 91 | |
| 85 | 92 | auto rng_params = decltype(this->m_shift_rng)::param_type(-intensity, intensity); |
| 86 | ||
| 93 | ||
| 87 | 94 | this->m_shift_rng.param(rng_params); |
| 88 | 95 | } |
| 89 | 96 | }; |
| 101 | 108 | pixshift0r *instance = new pixshift0r(width, height); |
| 102 | 109 | |
| 103 | 110 | 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 | ||
| 106 | 113 | return instance; |
| 107 | 114 | } |
| 108 | 115 | |
| 129 | 136 | info->type = F0R_PARAM_DOUBLE; |
| 130 | 137 | info->explanation = "Aggressiveness of row/column-shifting"; |
| 131 | 138 | break; |
| 132 | ||
| 139 | ||
| 133 | 140 | case 1: |
| 134 | 141 | info->name = "block_height"; |
| 135 | 142 | info->type = F0R_PARAM_DOUBLE; |
| 153 | 160 | void f0r_get_param_value( |
| 154 | 161 | f0r_instance_t instance, |
| 155 | 162 | f0r_param_t param, |
| 156 | int param_index | |
| 163 | int param_index | |
| 157 | 164 | ) |
| 158 | 165 | { |
| 159 | 166 | pixshift0r* context = (pixshift0r*)instance; |
| 177 | 184 | void f0r_set_param_value( |
| 178 | 185 | f0r_instance_t instance, |
| 179 | 186 | f0r_param_t param, |
| 180 | int param_index | |
| 187 | int param_index | |
| 181 | 188 | ) |
| 182 | 189 | { |
| 183 | 190 | pixshift0r* context = (pixshift0r*)instance; |
| 202 | 209 | f0r_instance_t instance, |
| 203 | 210 | double, |
| 204 | 211 | const uint32_t *inframe, |
| 205 | uint32_t *outframe | |
| 212 | uint32_t *outframe | |
| 206 | 213 | ) |
| 207 | 214 | { |
| 208 | 215 | static_cast<pixshift0r*>(instance)->process(inframe, outframe); |
| 210 | 217 | |
| 211 | 218 | void f0r_deinit() {} |
| 212 | 219 | |
| 213 | void f0r_destruct (f0r_instance_t instance) | |
| 220 | void f0r_destruct (f0r_instance_t instance) | |
| 214 | 221 | { |
| 215 | 222 | delete static_cast<pixshift0r*>(instance); |
| 216 | 223 | } |
| 242 | 242 | { |
| 243 | 243 | i = (oy>>16)*w + (ox>>16); |
| 244 | 244 | if(i<0) i = 0; |
| 245 | if(i>=inst->pixels) i = inst->pixels; | |
| 245 | if(i>=inst->pixels) i = inst->pixels - 1; | |
| 246 | 246 | v = inst->current_buffer[i] & 0xfcfcff; |
| 247 | 247 | alpha = *src & 0xff000000; |
| 248 | 248 | v = (v * 3) + ((*src++) & 0xfcfcff); |
| 45 | 45 | #include <math.h> |
| 46 | 46 | |
| 47 | 47 | #include "frei0r.h" |
| 48 | #include "frei0r/math.h" | |
| 48 | 49 | |
| 49 | 50 | |
| 50 | 51 | //---------------------------------------------------------- |
| 157 | 158 | if (size<1) size=1; |
| 158 | 159 | kx=size; ky=size; |
| 159 | 160 | kx=kx/ar; //kao aspect!=1 (anamorph) |
| 161 | kx = MAX(kx, 1); | |
| 162 | ky = MAX(ky, 1); | |
| 160 | 163 | |
| 161 | 164 | black=0; |
| 162 | 165 | white=255; |
| 1 | 1 | |
| 2 | 2 | PLUGINDIR ?= ../build/src |
| 3 | 3 | |
| 4 | all: build scan-plugins | |
| 4 | CXX ?= g++ | |
| 5 | 5 | |
| 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 | |
| 9 | 46 | @echo "[" > frei0r-plugin-list.json |
| 10 | 47 | @head -n -1 tmp.json >> frei0r-plugin-list.json |
| 11 | 48 | @echo "}\n]" >> frei0r-plugin-list.json |
| 12 | 49 | @rm tmp.json |
| 13 | $(info frei0r-plugin-list.json) | |
| 50 | @printf 'frei0r-plugin-list.json\n' | |
| 14 | 51 | |
| 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} | |
| 17 | 58 | |
| 18 | 59 | clean: |
| 19 | 60 | rm -f *.o |
| 20 | rm -f frei0r-info | |
| 61 | rm -f frei0r-run frei0r-meta | |
| 21 | 62 | 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 | ||
| 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(¶m, 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(¶m, 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(¶m_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(¶m, 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 |