├── .clang-format ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── containers │ ├── fedora-common │ │ └── build.sh │ └── fedora-template │ │ ├── Dockerfile │ │ └── build.sh └── workflows │ ├── clang-format.yml │ ├── docker-build.yml │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── ci ├── ci_includes.cmd.in ├── ci_includes.sh.in ├── forum-update-info.json ├── macos │ ├── change-rpath.sh │ └── install-packagesbuild.sh ├── plugin.spec └── windows │ └── package-windows.cmd ├── cmake ├── ObsPluginHelpers.cmake └── bundle │ └── macos │ ├── Plugin-Info.plist.in │ └── entitlements.plist ├── data ├── common.effect ├── falsecolor-key.png ├── falsecolor.effect ├── focuspeaking.effect ├── histogram.effect ├── locale │ ├── en-US.ini │ ├── fr-FR.ini │ ├── ja-JP.ini │ ├── pt-BR.ini │ └── zh-CN.ini ├── vectorscope-graticule.png ├── vectorscope.effect ├── waveform.effect └── zebra.effect ├── doc ├── dock.md ├── falsecolor-lut-default.png ├── falsecolor.md ├── falsecolor.svg ├── focuspeaking.md ├── global_config.md ├── histogram.md ├── vectorscope.md ├── waveform.md └── zebra.md ├── installer ├── installer-Windows.iss.in └── installer-macOS.pkgproj.in ├── src-obsstudio ├── .clang-format ├── combobox-ignorewheel.cpp ├── combobox-ignorewheel.hpp ├── double-slider.cpp ├── double-slider.hpp ├── properties-view.cpp ├── properties-view.hpp ├── qt-wrappers.cpp ├── qt-wrappers.hpp ├── slider-ignorewheel.cpp ├── slider-ignorewheel.hpp ├── spinbox-ignorewheel.cpp ├── spinbox-ignorewheel.hpp ├── vertical-scroll-area.cpp └── vertical-scroll-area.hpp └── src ├── ScopeWidgetInteractiveEventFilter.cpp ├── ScopeWidgetInteractiveEventFilter.hpp ├── SurfaceEventFilter.cpp ├── SurfaceEventFilter.hpp ├── common.c ├── common.h ├── falsecolor-key.svg ├── focuspeaking.c ├── histogram.c ├── obs-convenience.c ├── obs-convenience.h ├── obsgui-helper.hpp ├── plugin-macros.h.in ├── plugin-main.c ├── roi.c ├── roi.h ├── scope-dock-new-dialog.cpp ├── scope-dock-new-dialog.hpp ├── scope-dock.cpp ├── scope-dock.hpp ├── scope-widget-properties.cpp ├── scope-widget-properties.hpp ├── scope-widget.cpp ├── scope-widget.hpp ├── util-cpp.cc ├── util.c ├── util.h ├── vectorscope-graticule.svg ├── vectorscope.c ├── waveform.c └── zebra.c /.clang-format: -------------------------------------------------------------------------------- 1 | # please use clang-format version 8 or later 2 | 3 | Standard: Cpp11 4 | AccessModifierOffset: -8 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | #AllowAllArgumentsOnNextLine: false # requires clang-format 9 12 | #AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: false 18 | #AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: false 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterClass: false 28 | AfterControlStatement: false 29 | AfterEnum: false 30 | AfterFunction: true 31 | AfterNamespace: false 32 | AfterObjCDeclaration: false 33 | AfterStruct: true 34 | AfterUnion: false 35 | AfterExternBlock: false 36 | BeforeCatch: false 37 | BeforeElse: false 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyRecord: true 41 | SplitEmptyNamespace: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakStringLiterals: false # apparently unpredictable 47 | ColumnLimit: 120 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 8 51 | ContinuationIndentWidth: 8 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | FixNamespaceComments: false 56 | ForEachMacros: 57 | - 'json_object_foreach' 58 | - 'json_object_foreach_safe' 59 | - 'json_array_foreach' 60 | - 'HASH_ITER' 61 | IncludeBlocks: Preserve 62 | IndentCaseLabels: false 63 | IndentPPDirectives: None 64 | IndentWidth: 8 65 | IndentWrappedFunctionNames: false 66 | KeepEmptyLinesAtTheStartOfBlocks: true 67 | MaxEmptyLinesToKeep: 1 68 | NamespaceIndentation: None 69 | #ObjCBinPackProtocolList: Auto # requires clang-format 7 70 | ObjCBlockIndentWidth: 8 71 | ObjCSpaceAfterProperty: true 72 | ObjCSpaceBeforeProtocolList: true 73 | 74 | PenaltyBreakAssignment: 10 75 | PenaltyBreakBeforeFirstCallParameter: 30 76 | PenaltyBreakComment: 10 77 | PenaltyBreakFirstLessLess: 0 78 | PenaltyBreakString: 10 79 | PenaltyExcessCharacter: 100 80 | PenaltyReturnTypeOnItsOwnLine: 60 81 | 82 | PointerAlignment: Right 83 | ReflowComments: false 84 | SortIncludes: false 85 | SortUsingDeclarations: false 86 | SpaceAfterCStyleCast: false 87 | #SpaceAfterLogicalNot: false # requires clang-format 9 88 | SpaceAfterTemplateKeyword: false 89 | SpaceBeforeAssignmentOperators: true 90 | #SpaceBeforeCtorInitializerColon: true # requires clang-format 7 91 | #SpaceBeforeInheritanceColon: true # requires clang-format 7 92 | SpaceBeforeParens: ControlStatements 93 | #SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 94 | SpaceInEmptyParentheses: false 95 | SpacesBeforeTrailingComments: 1 96 | SpacesInAngles: false 97 | SpacesInCStyleCastParentheses: false 98 | SpacesInContainerLiterals: false 99 | SpacesInParentheses: false 100 | SpacesInSquareBrackets: false 101 | #StatementMacros: # requires clang-format 8 102 | # - 'Q_OBJECT' 103 | TabWidth: 8 104 | #TypenameMacros: # requires clang-format 9 105 | # - 'DARRAY' 106 | UseTab: ForContinuationAndIndentation 107 | --- 108 | Language: ObjC 109 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | Also describe which source you use, eg. vectorscope, waveform. 13 | 14 | **Your setup** 15 | - Settings of the source to reproduce the bug 16 | - OS and OBS-Studio version 17 | - Version of the plugin 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Actual behavior** 23 | A clear and concise description of what actually happened. 24 | If it is difficult to describe by text, you may put screenshots. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | Is it listed on [Future Plans](https://github.com/norihiro/obs-color-monitor/wiki/Future-Plans)? 13 | Even if yes, you can still create the request to express your need. 14 | 15 | **Reference** 16 | If other software has the feature, please put a link to the manual or video which shows the usage. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional information** 22 | Add any other information or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/containers/fedora-common/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -ex 4 | 5 | docker_image="$1" 6 | rpmbuild="$2" 7 | 8 | PLUGIN_NAME=$(awk '/^project\(/{print gensub(/project\(([^ ()]*).*/, "\\1", 1, $0)}' CMakeLists.txt) 9 | PLUGIN_NAME_FEDORA="$(sed -e 's/^obs-/obs-studio-plugin-/' <<< "$PLUGIN_NAME")" 10 | OBS_VERSION=$(docker run $docker_image bash -c 'rpm -q --qf "%{version}" obs-studio') 11 | eval $(git describe --tag --always --long | awk ' 12 | BEGIN { 13 | VERSION="unknown"; 14 | RELEASE=0; 15 | } 16 | { 17 | if (match($0, /^(.*)-([0-9]*)-g[0-9a-f]*$/, aa)) { 18 | VERSION = aa[1] 19 | RELEASE = aa[2] 20 | } 21 | } 22 | END { 23 | VERSION = gensub(/-(alpha|beta|rc)/, "~\\1", 1, VERSION); 24 | gsub(/["'\''-]/, ".", VERSION); 25 | printf("VERSION='\''%s'\'' RELEASE=%d\n", VERSION, RELEASE + 1); 26 | }') 27 | 28 | rm -rf $rpmbuild 29 | mkdir -p $rpmbuild/{BUILD,BUILDROOT,SRPMS,SOURCES,SPECS,RPMS} 30 | rpmbuild="$(cd $rpmbuild && pwd -P)" 31 | chmod a+w $rpmbuild/{BUILD,BUILDROOT,SRPMS,RPMS} 32 | test -x /usr/sbin/selinuxenabled && /usr/sbin/selinuxenabled && chcon -Rt container_file_t $rpmbuild 33 | 34 | # Prepare files 35 | sed \ 36 | -e "s/@PLUGIN_NAME@/$PLUGIN_NAME/g" \ 37 | -e "s/@PLUGIN_NAME_FEDORA@/$PLUGIN_NAME_FEDORA/g" \ 38 | -e "s/@VERSION@/$VERSION/g" \ 39 | -e "s/@RELEASE@/$RELEASE/g" \ 40 | -e "s/@OBS_VERSION@/$OBS_VERSION/g" \ 41 | < ci/plugin.spec \ 42 | > $rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec 43 | 44 | git archive --format=tar --prefix=$PLUGIN_NAME_FEDORA-$VERSION/ HEAD | bzip2 > $rpmbuild/SOURCES/$PLUGIN_NAME_FEDORA-$VERSION.tar.bz2 45 | 46 | docker run -v $rpmbuild:/home/rpm/rpmbuild $docker_image bash -c " 47 | sudo dnf builddep -y ~/rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec && 48 | sudo chown 0:0 ~/rpmbuild/SOURCES/* && 49 | sudo chown 0:0 ~/rpmbuild/SPECS/* && 50 | rpmbuild -ba ~/rpmbuild/SPECS/$PLUGIN_NAME_FEDORA.spec 51 | " 52 | -------------------------------------------------------------------------------- /.github/containers/fedora-template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:%releasever% 2 | 3 | RUN dnf install -y rpm-build python3-dnf-plugins-core && dnf clean all 4 | RUN dnf install -y obs-studio obs-studio-devel && dnf clean all 5 | RUN dnf install -y qt5-qtbase-devel qt5-qtbase-private-devel && dnf clean all 6 | 7 | RUN useradd -s /bin/bash -m rpm 8 | RUN echo >> /etc/sudoers 9 | RUN echo "rpm ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 10 | 11 | USER rpm 12 | WORKDIR /home/rpm 13 | -------------------------------------------------------------------------------- /.github/containers/fedora-template/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -ex 3 | .github/containers/fedora-common/build.sh obs-plugin-build/fedora%releasever% fedora%releasever%-rpmbuild 4 | echo 'FILE_NAME=fedora%releasever%-rpmbuild/*RPMS/**/*.rpm' >> $GITHUB_ENV 5 | -------------------------------------------------------------------------------- /.github/workflows/clang-format.yml: -------------------------------------------------------------------------------- 1 | name: Clang Format Check 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | clang: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Clang 17 | run: | 18 | sudo apt-get install -y clang-format-18 19 | clang-format -i -fallback-style=none $(git ls-files src/ src-obsstudio/ | grep '[^/]\.[ch]') 20 | 21 | - name: Check 22 | # Build your program with the given configuration 23 | run: | 24 | dirty=$(git ls-files --modified) 25 | set +x 26 | if [[ $dirty ]]; then 27 | git diff 28 | echo "Error: File(s) are not properly formatted." 29 | echo "$dirty" 30 | exit 1 31 | fi 32 | -------------------------------------------------------------------------------- /.github/workflows/docker-build.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build on Docker 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | branches: 8 | - main 9 | tags: 10 | - '*' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | branches: 15 | - main 16 | 17 | env: 18 | artifactName: ${{ contains(github.ref_name, '/') && 'docker-artifact' || github.ref_name }}-rpm 19 | 20 | jobs: 21 | docker_build: 22 | runs-on: ubuntu-latest 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | target: 27 | - fedora41 28 | - fedora42 29 | defaults: 30 | run: 31 | shell: bash 32 | env: 33 | target: ${{ matrix.target }} 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | with: 39 | fetch-depth: 0 40 | submodules: recursive 41 | 42 | - name: Generate container directory 43 | run: | 44 | cp -a .github/containers/fedora-template .github/containers/$target 45 | releasever="$(cut -b 7- <<< "$target")" 46 | sed -i "s/%releasever%/$releasever/g" .github/containers/$target/* 47 | 48 | - name: Restore docker from cache 49 | id: docker-cache 50 | uses: actions/cache/restore@v4 51 | with: 52 | path: ${{ github.workspace }}/docker-cache 53 | key: docker-cache-${{ matrix.target }}-${{ hashFiles(format('.github/containers/{0}/Dockerfile', matrix.target)) }} 54 | 55 | - name: Build environment 56 | if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }} 57 | run: | 58 | docker build -t obs-plugin-build/$target .github/containers/$target 59 | mkdir -p docker-cache 60 | docker save obs-plugin-build/$target | gzip > docker-cache/obs-plugin-build-$target.tar.gz 61 | 62 | - name: Save docker to cache 63 | uses: actions/cache/save@v4 64 | if: ${{ steps.docker-cache.outputs.cache-hit != 'true' }} 65 | with: 66 | path: ${{ github.workspace }}/docker-cache 67 | key: docker-cache-${{ matrix.target }}-${{ hashFiles(format('.github/containers/{0}/Dockerfile', matrix.target)) }} 68 | 69 | - name: Extract cached environment 70 | if: ${{ steps.docker-cache.outputs.cache-hit == 'true' }} 71 | run: | 72 | zcat docker-cache/obs-plugin-build-$target.tar.gz | docker load 73 | 74 | - name: Build package 75 | run: .github/containers/$target/build.sh 76 | 77 | - name: Upload artifact 78 | uses: actions/upload-artifact@v4 79 | with: 80 | name: ${{ env.artifactName }}-${{ matrix.target }} 81 | path: '${{ env.FILE_NAME }}' 82 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Plugin Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | branches: 8 | - main 9 | tags: 10 | - '*' 11 | pull_request: 12 | paths-ignore: 13 | - '**.md' 14 | branches: 15 | - main 16 | 17 | env: 18 | artifactName: ${{ contains(github.ref_name, '/') && 'artifact' || github.ref_name }} 19 | 20 | jobs: 21 | linux_build: 22 | runs-on: ${{ matrix.ubuntu }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | obs: [30] 27 | ubuntu: ['ubuntu-22.04'] 28 | defaults: 29 | run: 30 | shell: bash 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | with: 35 | submodules: recursive 36 | 37 | - name: Download obs-studio development environment 38 | id: obsdeps 39 | uses: norihiro/obs-studio-devel-action@v2 40 | with: 41 | obs: ${{ matrix.obs }} 42 | verbose: true 43 | 44 | - name: Build plugin 45 | run: | 46 | set -ex 47 | cmake -S . -B build \ 48 | -D CMAKE_BUILD_TYPE=RelWithDebInfo \ 49 | -D CPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON \ 50 | -D PKG_SUFFIX=-obs${{ matrix.obs }}-${{ matrix.ubuntu }}-x86_64 \ 51 | ${{ steps.obsdeps.outputs.PLUGIN_CMAKE_OPTIONS }} 52 | cd build 53 | make -j4 54 | make package 55 | echo "FILE_NAME=$(find $PWD -name '*.deb' | head -n 1)" >> $GITHUB_ENV 56 | - name: Upload build artifact 57 | uses: actions/upload-artifact@v4 58 | with: 59 | name: ${{ env.artifactName }}-linux-obs${{ matrix.obs }}-${{ matrix.ubuntu }} 60 | path: '${{ env.FILE_NAME }}' 61 | - name: Check package 62 | run: | 63 | . build/ci/ci_includes.generated.sh 64 | set -ex 65 | sudo apt install -y '${{ env.FILE_NAME }}' 66 | ldd /usr/lib/x86_64-linux-gnu/obs-plugins/${PLUGIN_NAME}.so > ldd.out 67 | if grep not.found ldd.out ; then 68 | echo "Error: unresolved shared object." >&2 69 | exit 1 70 | fi 71 | ls /usr/share/obs/obs-plugins/${PLUGIN_NAME}/ 72 | 73 | macos_build: 74 | runs-on: macos-14 75 | strategy: 76 | fail-fast: false 77 | matrix: 78 | obs: [30] 79 | arch: ['universal'] 80 | defaults: 81 | run: 82 | shell: bash 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v4 86 | with: 87 | submodules: recursive 88 | 89 | - name: Setup Environment 90 | id: setup 91 | run: | 92 | set -e 93 | echo '::group::Set up code signing' 94 | if [[ '${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' != '' && \ 95 | '${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}' != '' && \ 96 | '${{ secrets.MACOS_SIGNING_CERT }}' != '' ]]; then 97 | echo "haveCodesignIdent=true" >> $GITHUB_OUTPUT 98 | else 99 | echo "haveCodesignIdent=false" >> $GITHUB_OUTPUT 100 | fi 101 | if [[ '${{ secrets.MACOS_NOTARIZATION_USERNAME }}' != '' && \ 102 | '${{ secrets.MACOS_NOTARIZATION_PASSWORD }}' != '' ]]; then 103 | echo "haveNotarizationUser=true" >> $GITHUB_OUTPUT 104 | else 105 | echo "haveNotarizationUser=false" >> $GITHUB_OUTPUT 106 | fi 107 | echo '::endgroup::' 108 | 109 | - name: Install Apple Developer Certificate 110 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 111 | uses: apple-actions/import-codesign-certs@v2 112 | with: 113 | keychain-password: ${{ github.run_id }} 114 | p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }} 115 | p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }} 116 | 117 | - name: Set Signing Identity 118 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' && steps.setup.outputs.haveNotarizationUser == 'true' }} 119 | run: | 120 | set -e 121 | TEAM_ID=$(echo "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" | sed 's/.*(\([A-Za-z0-9]*\))$/\1/') 122 | xcrun notarytool store-credentials AC_PASSWORD \ 123 | --apple-id "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \ 124 | --team-id "$TEAM_ID" \ 125 | --password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" 126 | 127 | - name: Download obs-studio development environment 128 | id: obsdeps 129 | uses: norihiro/obs-studio-devel-action@v2 130 | with: 131 | path: /tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 132 | arch: ${{ matrix.arch }} 133 | obs: ${{ matrix.obs }} 134 | verbose: true 135 | 136 | - name: Build plugin 137 | run: | 138 | arch=${{ matrix.arch }} 139 | deps=/tmp/deps-${{ matrix.obs }}-${{ matrix.arch }} 140 | MACOSX_DEPLOYMENT_TARGET=${{ steps.obsdeps.outputs.MACOSX_DEPLOYMENT_TARGET }} 141 | GIT_TAG=$(git describe --tags --always) 142 | PKG_SUFFIX=-${GIT_TAG}-obs${{ matrix.obs }}-macos-${{ matrix.arch }} 143 | set -e 144 | cmake -S . -B build -G Ninja \ 145 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 146 | -DCMAKE_PREFIX_PATH="$PWD/release/" \ 147 | -DCMAKE_OSX_ARCHITECTURES=${arch/#universal/x86_64;arm64} \ 148 | -D PKG_SUFFIX=$PKG_SUFFIX \ 149 | -D OBS_BUNDLE_CODESIGN_IDENTITY='${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' \ 150 | ${{ steps.obsdeps.outputs.PLUGIN_CMAKE_OPTIONS }} 151 | cmake --build build --config RelWithDebInfo 152 | 153 | - name: Prepare package 154 | run: | 155 | set -ex 156 | . build/ci/ci_includes.generated.sh 157 | cmake --install build --config RelWithDebInfo --prefix=release 158 | (cd release/${PLUGIN_NAME}.plugin/Contents && ../../../ci/macos/change-rpath.sh -obs ${{ matrix.obs }} -lib lib/ MacOS/${PLUGIN_NAME}) 159 | cp LICENSE release/${PLUGIN_NAME}.plugin/Contents/Resources/LICENSE-$PLUGIN_NAME 160 | 161 | - name: Codesign 162 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 163 | run: | 164 | . build/ci/ci_includes.generated.sh 165 | set -ex 166 | files=( 167 | $(find release/${PLUGIN_NAME}.plugin/ -name '*.dylib') 168 | release/${PLUGIN_NAME}.plugin/Contents/MacOS/${PLUGIN_NAME} 169 | ) 170 | for dylib in "${files[@]}"; do 171 | codesign --force --sign "${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}" "$dylib" 172 | done 173 | for dylib in "${files[@]}"; do 174 | codesign -vvv --deep --strict "$dylib" 175 | done 176 | 177 | - name: Package 178 | run: | 179 | . build/ci/ci_includes.generated.sh 180 | set -ex 181 | zipfile=$PWD/package/${PLUGIN_NAME}${PKG_SUFFIX}.zip 182 | mkdir package 183 | (cd release/ && zip -r $zipfile ${PLUGIN_NAME}.plugin) 184 | ci/macos/install-packagesbuild.sh 185 | packagesbuild \ 186 | --build-folder $PWD/package/ \ 187 | build/installer-macOS.generated.pkgproj 188 | 189 | - name: Productsign 190 | if: ${{ github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 191 | run: | 192 | . build/ci/ci_includes.generated.sh 193 | pkgfile=package/${PLUGIN_NAME}${PKG_SUFFIX}.pkg 194 | set -e 195 | . build/ci/ci_includes.generated.sh 196 | productsign --sign "${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}" $pkgfile package/${PLUGIN_NAME}-signed.pkg 197 | mv package/${PLUGIN_NAME}-signed.pkg $pkgfile 198 | 199 | - name: Notarize 200 | if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' && steps.setup.outputs.haveCodesignIdent == 'true' }} 201 | uses: norihiro/macos-notarize-action@v1 202 | with: 203 | path: package/* 204 | keychainProfile: AC_PASSWORD 205 | verbose: true 206 | 207 | - name: Upload build artifact 208 | uses: actions/upload-artifact@v4 209 | with: 210 | name: ${{ env.artifactName }}-macos-obs${{ matrix.obs }}-${{ matrix.arch }} 211 | path: package/* 212 | 213 | windows_build: 214 | runs-on: windows-2022 215 | strategy: 216 | fail-fast: false 217 | matrix: 218 | obs: [30] 219 | arch: [x64] 220 | env: 221 | visualStudio: 'Visual Studio 17 2022' 222 | Configuration: 'RelWithDebInfo' 223 | defaults: 224 | run: 225 | shell: pwsh 226 | steps: 227 | - name: Checkout 228 | uses: actions/checkout@v4 229 | with: 230 | submodules: recursive 231 | - name: Download obs-studio 232 | id: obsdeps 233 | uses: norihiro/obs-studio-devel-action@v2 234 | with: 235 | obs: ${{ matrix.obs }} 236 | - name: Build plugin 237 | run: | 238 | $CmakeArgs = @( 239 | '-G', "${{ env.visualStudio }}" 240 | '-DCMAKE_SYSTEM_VERSION=10.0.18363.657' 241 | ) 242 | cmake -S . -B build ${{ steps.obsdeps.outputs.PLUGIN_CMAKE_OPTIONS_PS }} @CmakeArgs 243 | cmake --build build --config RelWithDebInfo -j 4 244 | cmake --install build --config RelWithDebInfo --prefix "$(Resolve-Path -Path .)/release" 245 | - name: Package plugin 246 | run: ci/windows/package-windows.cmd ${{ matrix.obs }} 247 | - name: Upload build artifact 248 | uses: actions/upload-artifact@v4 249 | with: 250 | name: ${{ env.artifactName }}-windows-obs${{ matrix.obs }}-${{ matrix.arch }} 251 | path: package/* 252 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .*.swp 3 | .DS_Store 4 | /release/ 5 | 6 | .vscode 7 | .idea 8 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(obs-color-monitor VERSION 0.9.3) 4 | 5 | set(PLUGIN_AUTHOR "Norihiro Kamae") 6 | 7 | set(MACOS_BUNDLEID "net.nagater.obs-color-monitor") 8 | set(ID_PREFIX "net.nagater.obs-color-monitor.") 9 | set(MACOS_PACKAGE_UUID "0EA80C5E-945D-4A0A-A30D-593014BB5631") 10 | set(MACOS_INSTALLER_UUID "A5C3A087-9A85-4E45-B755-C7FBE53177A9") 11 | set(PLUGIN_URL "https://obsproject.com/forum/resources/color-monitor.1277/") 12 | 13 | set(LINUX_MAINTAINER_EMAIL "norihiro@nagater.net") 14 | 15 | option(ENABLE_PROFILE "Enable profiling to see calculation time" OFF) 16 | option(SHOW_ROI "Show ROI source to users" OFF) 17 | option(ENABLE_COVERAGE "Enable coverage option for GCC" OFF) 18 | 19 | set(CMAKE_PREFIX_PATH "${QTDIR}") 20 | set(CMAKE_AUTOMOC ON) 21 | set(CMAKE_AUTOUIC ON) 22 | 23 | # In case you need C++ 24 | set(CMAKE_CXX_STANDARD 11) 25 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 26 | 27 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) 28 | find_package(libobs REQUIRED) 29 | find_package(obs-frontend-api REQUIRED) 30 | include(cmake/ObsPluginHelpers.cmake) 31 | add_library(OBS::frontend-api ALIAS OBS::obs-frontend-api) 32 | find_qt(VERSION ${QT_VERSION} COMPONENTS Widgets Core COMPONENTS_LINUX Gui) 33 | endif() 34 | 35 | configure_file( 36 | src/plugin-macros.h.in 37 | plugin-macros.generated.h 38 | ) 39 | 40 | set(PLUGIN_SOURCES 41 | src/plugin-main.c 42 | src/vectorscope.c 43 | src/waveform.c 44 | src/histogram.c 45 | src/zebra.c 46 | src/focuspeaking.c 47 | src/roi.c 48 | src/common.c 49 | src/util.c 50 | src/util-cpp.cc 51 | src/obs-convenience.c 52 | src/scope-dock.cpp 53 | src/scope-dock-new-dialog.cpp 54 | src/scope-widget.cpp 55 | src/scope-widget-properties.cpp 56 | src/SurfaceEventFilter.cpp 57 | src/ScopeWidgetInteractiveEventFilter.cpp 58 | src-obsstudio/properties-view.cpp 59 | src-obsstudio/vertical-scroll-area.cpp 60 | src-obsstudio/combobox-ignorewheel.cpp 61 | src-obsstudio/spinbox-ignorewheel.cpp 62 | src-obsstudio/double-slider.cpp 63 | src-obsstudio/slider-ignorewheel.cpp 64 | src-obsstudio/qt-wrappers.cpp 65 | ) 66 | 67 | add_library(${PROJECT_NAME} MODULE ${PLUGIN_SOURCES}) 68 | 69 | target_link_libraries(${PROJECT_NAME} 70 | OBS::libobs 71 | OBS::frontend-api 72 | Qt::Core 73 | Qt::Widgets 74 | Qt::Gui 75 | Qt::GuiPrivate 76 | ) 77 | 78 | target_include_directories(${PROJECT_NAME} 79 | PRIVATE 80 | src-obsstudio/ 81 | ${CMAKE_CURRENT_BINARY_DIR} 82 | ) 83 | 84 | if(OS_WINDOWS) 85 | # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL when building with VS2019) 86 | if (MSVC) 87 | add_definitions(/MP /d2FH4-) 88 | endif() 89 | 90 | target_link_libraries(${PROJECT_NAME} OBS::w32-pthreads) 91 | endif() 92 | 93 | if(OS_LINUX) 94 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra) 95 | target_link_options(${PROJECT_NAME} PRIVATE -Wl,-z,defs) 96 | if(ENABLE_COVERAGE) 97 | target_compile_options(${PROJECT_NAME} PRIVATE -coverage) 98 | target_link_options(${PROJECT_NAME} PRIVATE -coverage) 99 | endif() 100 | endif() 101 | 102 | if(APPLE) 103 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -fvisibility=default") 104 | 105 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") 106 | set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") 107 | set(MACOSX_PLUGIN_BUNDLE_VERSION "${PROJECT_VERSION}") 108 | set(MACOSX_PLUGIN_SHORT_VERSION_STRING "1") 109 | endif() 110 | 111 | file(GENERATE OUTPUT .gitignore CONTENT "*\n") 112 | 113 | setup_plugin_target(${PROJECT_NAME}) 114 | 115 | if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) 116 | configure_file( 117 | installer/installer-Windows.iss.in 118 | installer-Windows.generated.iss 119 | ) 120 | 121 | configure_file( 122 | ci/ci_includes.sh.in 123 | ci/ci_includes.generated.sh 124 | ) 125 | configure_file( 126 | ci/ci_includes.cmd.in 127 | ci/ci_includes.generated.cmd 128 | ) 129 | 130 | configure_file( 131 | installer/installer-macOS.pkgproj.in 132 | installer-macOS.generated.pkgproj 133 | ) 134 | endif() 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Color Monitor plugin for OBS Studio 2 | 3 | ## Introduction 4 | 5 | This plugin provides these sources to monitor color balances. 6 | 7 | - [Vectorscope](doc/vectorscope.md) 8 | - [Waveform](doc/waveform.md) 9 | - [Histogram](doc/histogram.md) 10 | - [Zebra](doc/zebra.md) 11 | - [False Color](doc/falsecolor.md) 12 | - [Focus Peaking](doc/focuspeaking.md) 13 | 14 | In addition, a dock widget is available. 15 | - [Dock](doc/dock.md) 16 | 17 | To hide the source and filter types from the add-source and add-filter menues, refer this document. 18 | - [Global Configuration](doc/global_config.md) 19 | 20 | ## Quick Usage 21 | 22 | ### Dock 23 | 1. Install the plugin and boot OBS Studio. 24 | 1. Click `Tools` and `New Scope Dock...` in the menu of OBS Studio. 25 | 1. Input the name, optionally set the source to monitor, and clock OK. You will see a new dock containing vectorscope, waveform, and histogram for the program. 26 | 27 | ### Projector View 28 | 1. Install the plugin and boot OBS Studio. 29 | 1. Have your source to see vectorscope, waveform, or histogram, eg. a camera. 30 | 1. Create a new scene. 31 | 1. Create vectorscope, waveform, or histogram on the scene by clicking `+` button at the bottom of `Source` list. 32 | 1. At `Source` combo box, select your source. You can select both scene and source. 33 | 1. If the plugin increases rendering time too much, increase `scale` to scale down the image before processing. 34 | 1. `Bypass` checkbox will show the scaled image to ensure you've select the right source. 35 | 1. You may open a windowed projector of the scene so that you can switch preview and program scene while monitoring color. 36 | -------------------------------------------------------------------------------- /ci/ci_includes.cmd.in: -------------------------------------------------------------------------------- 1 | set PluginName=@PROJECT_NAME@ 2 | set PluginVersion=@PROJECT_VERSION@ 3 | -------------------------------------------------------------------------------- /ci/ci_includes.sh.in: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME="@PROJECT_NAME@" 2 | PLUGIN_VERSION="@PROJECT_VERSION@" 3 | MACOS_BUNDLEID="@MACOS_BUNDLEID@" 4 | LINUX_MAINTAINER_EMAIL="@LINUX_MAINTAINER_EMAIL@" 5 | PKG_SUFFIX='@PKG_SUFFIX@' 6 | -------------------------------------------------------------------------------- /ci/forum-update-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugin_url": "https://obsproject.com/forum/resources/color-monitor.1277/" 3 | } 4 | -------------------------------------------------------------------------------- /ci/macos/change-rpath.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | libdir='' 4 | obsver='' 5 | 6 | while (($# > 0)); do 7 | case "$1" in 8 | -lib) 9 | libdir="$2" 10 | shift 2;; 11 | -obs) 12 | obsver="$2" 13 | shift 2;; 14 | *) 15 | break ;; 16 | esac 17 | done 18 | 19 | set -e 20 | 21 | function copy_local_dylib 22 | { 23 | local dylib 24 | otool -L $1 | awk '/^ \/usr\/local\/(opt|Cellar)\/.*\.dylib/{print $1}' | 25 | while read -r dylib; do 26 | echo "Changing dependency $1 -> $dylib" 27 | local b=$(basename $dylib) 28 | if test ! -e $libdir/$b; then 29 | mkdir -p $libdir 30 | cp $dylib $libdir 31 | chmod +rwx $libdir/$b 32 | install_name_tool -id "@loader_path/$b" $libdir/$b 33 | copy_local_dylib $libdir/$b 34 | fi 35 | install_name_tool -change "$dylib" "@loader_path/../$libdir/$b" $1 36 | done 37 | } 38 | 39 | function change_obs27_libs 40 | { 41 | # obs-frontend-api: 42 | # OBS 27.2 provides only `libobs-frontend-api.dylib`. 43 | # OBS 28.0 will provide `libobs-frontend-api.1.dylib` and `libobs-frontend-api.dylib`. 44 | # libobs: 45 | # Both OBS 27.2 and 28.0 provides `libobs.dylib`, `libobs.0.dylib`, `libobs.framework/Versions/A/libobs`. 46 | 47 | install_name_tool \ 48 | -change @rpath/QtWidgets.framework/Versions/5/QtWidgets \ 49 | @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ 50 | -change @rpath/QtGui.framework/Versions/5/QtGui \ 51 | @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ 52 | -change @rpath/QtCore.framework/Versions/5/QtCore \ 53 | @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ 54 | -change @rpath/libobs.framework/Versions/A/libobs \ 55 | @rpath/libobs.0.dylib \ 56 | -change @rpath/libobs-frontend-api.0.dylib \ 57 | @rpath/libobs-frontend-api.dylib \ 58 | "$1" 59 | } 60 | 61 | for i in "$@"; do 62 | case "$obsver" in 63 | 27 | 27.*) 64 | change_obs27_libs "$i" 65 | ;; 66 | 28 | 28.*) 67 | : # Not necessary to change dylib paths for OBS 28 68 | ;; 69 | esac 70 | copy_local_dylib "$i" 71 | done 72 | -------------------------------------------------------------------------------- /ci/macos/install-packagesbuild.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | if which packagesbuild; then 6 | exit 0 7 | fi 8 | 9 | packages_url='http://www.nagater.net/obs-studio/Packages.dmg' 10 | packages_hash='6afdd25386295974dad8f078b8f1e41cabebd08e72d970bf92f707c7e48b16c9' 11 | 12 | for ((retry=5; retry>0; retry--)); do 13 | curl -o Packages.dmg $packages_url 14 | sha256sum -c <<<"$packages_hash Packages.dmg" && break 15 | done 16 | 17 | hdiutil attach -noverify Packages.dmg 18 | packages_volume="$(hdiutil info -plist | grep '/Volumes/Packages' | sed 's/.*\(\/Volumes\/[^<]*\)<\/string>/\1/')" 19 | 20 | sudo installer -pkg "${packages_volume}/packages/Packages.pkg" -target / 21 | hdiutil detach "${packages_volume}" 22 | -------------------------------------------------------------------------------- /ci/plugin.spec: -------------------------------------------------------------------------------- 1 | Name: @PLUGIN_NAME_FEDORA@ 2 | Version: @VERSION@ 3 | Release: @RELEASE@%{?dist} 4 | Summary: Color monitor plugin for OBS Studio 5 | License: GPLv2+ 6 | 7 | Source0: %{name}-%{version}.tar.bz2 8 | Requires: obs-studio >= @OBS_VERSION@ 9 | BuildRequires: cmake, gcc, gcc-c++ 10 | BuildRequires: obs-studio-devel 11 | BuildRequires: qt6-qtbase-devel qt6-qtbase-private-devel 12 | Obsoletes: @PLUGIN_NAME@ < %{version} 13 | 14 | %description 15 | Color monitor plugin contains vectorscope, waveform, histogram, zebra, and 16 | false color to help color correction. 17 | 18 | %prep 19 | %autosetup -p1 20 | 21 | %build 22 | %{cmake} -DLINUX_PORTABLE=OFF -DLINUX_RPATH=OFF -DQT_VERSION=6 23 | %{cmake_build} 24 | 25 | %install 26 | %{cmake_install} 27 | 28 | %files 29 | %{_libdir}/obs-plugins/@PLUGIN_NAME@.so 30 | %{_datadir}/obs/obs-plugins/@PLUGIN_NAME@/ 31 | -------------------------------------------------------------------------------- /ci/windows/package-windows.cmd: -------------------------------------------------------------------------------- 1 | call "build\ci\ci_includes.generated.cmd" 2 | 3 | mkdir package 4 | cd package 5 | 6 | git describe --tags --always > package-version.txt 7 | set /p PackageVersion= 302 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 303 | COMPONENT obs_rundir 304 | EXCLUDE_FROM_ALL) 305 | 306 | if(OS_WINDOWS) 307 | install( 308 | FILES $ 309 | CONFIGURATIONS "RelWithDebInfo" "Debug" 310 | DESTINATION ${OBS_PLUGIN_DESTINATION} 311 | COMPONENT ${target}_Runtime 312 | OPTIONAL) 313 | 314 | install( 315 | FILES $ 316 | CONFIGURATIONS "RelWithDebInfo" "Debug" 317 | DESTINATION $/${OBS_PLUGIN_DESTINATION} 318 | COMPONENT obs_rundir 319 | OPTIONAL EXCLUDE_FROM_ALL) 320 | endif() 321 | 322 | if(MSVC) 323 | target_link_options( 324 | ${target} 325 | PRIVATE 326 | "LINKER:/OPT:REF" 327 | "$<$>:LINKER\:/SAFESEH\:NO>" 328 | "$<$:LINKER\:/INCREMENTAL:NO>" 329 | "$<$:LINKER\:/INCREMENTAL:NO>") 330 | endif() 331 | 332 | setup_target_resources(${target} obs-plugins/${target}) 333 | 334 | if(OS_WINDOWS) 335 | add_custom_command( 336 | TARGET ${target} 337 | POST_BUILD 338 | COMMAND 339 | "${CMAKE_COMMAND}" -DCMAKE_INSTALL_PREFIX=${OBS_OUTPUT_DIR} 340 | -DCMAKE_INSTALL_COMPONENT=obs_rundir 341 | -DCMAKE_INSTALL_CONFIG_NAME=$ -P 342 | ${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake 343 | COMMENT "Installing to plugin rundir" 344 | VERBATIM) 345 | endif() 346 | endfunction() 347 | 348 | function(setup_target_resources target destination) 349 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/data) 350 | install( 351 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data/ 352 | DESTINATION ${OBS_DATA_DESTINATION}/${destination} 353 | USE_SOURCE_PERMISSIONS 354 | COMPONENT obs_plugins) 355 | 356 | install( 357 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data 358 | DESTINATION $/${OBS_DATA_DESTINATION}/${destination} 359 | USE_SOURCE_PERMISSIONS 360 | COMPONENT obs_rundir 361 | EXCLUDE_FROM_ALL) 362 | endif() 363 | endfunction() 364 | endif() 365 | -------------------------------------------------------------------------------- /cmake/bundle/macos/Plugin-Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | ${MACOSX_PLUGIN_BUNDLE_NAME} 7 | CFBundleIdentifier 8 | ${MACOSX_PLUGIN_GUI_IDENTIFIER} 9 | CFBundleVersion 10 | ${MACOSX_PLUGIN_BUNDLE_VERSION} 11 | CFBundleShortVersionString 12 | ${MACOSX_PLUGIN_SHORT_VERSION_STRING} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleExecutable 16 | ${MACOSX_PLUGIN_EXECUTABLE_NAME} 17 | CFBundlePackageType 18 | ${MACOSX_PLUGIN_BUNDLE_TYPE} 19 | CFBundleSupportedPlatforms 20 | 21 | MacOSX 22 | 23 | LSMinimumSystemVersion 24 | 10.13 25 | 26 | 27 | -------------------------------------------------------------------------------- /cmake/bundle/macos/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.device.audio-input 10 | 11 | com.apple.security.cs.disable-library-validation 12 | 13 | 14 | com.apple.security.cs.allow-dyld-environment-variables 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/common.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | 4 | sampler_state cnv_sampler { 5 | Filter = Point; 6 | AddressU = Clamp; 7 | AddressV = Clamp; 8 | }; 9 | 10 | struct VertInOut { 11 | float4 pos : POSITION; 12 | float2 uv : TEXCOORD0; 13 | }; 14 | 15 | VertInOut VSDefault(VertInOut vert_in) 16 | { 17 | VertInOut vert_out; 18 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 19 | vert_out.uv = vert_in.uv; 20 | return vert_out; 21 | } 22 | 23 | float4 PSConvertRGB_YUV601(VertInOut vert_in) : TARGET 24 | { 25 | float4 rgb = image.Sample(cnv_sampler, vert_in.uv); 26 | float4 uv00; 27 | uv00.z = -0.147643 * rgb.x -0.289855 * rgb.y +0.437500 * rgb.z +0.5 - 1.0/256.0; // U 28 | uv00.y = +0.299000 * rgb.x +0.587000 * rgb.y +0.114000 * rgb.z; // Y 29 | uv00.x = +0.437500 * rgb.x -0.366351 * rgb.y -0.071147 * rgb.z +0.5; // V 30 | uv00.a = 1; 31 | return uv00; 32 | } 33 | 34 | float4 PSConvertRGB_YUV709(VertInOut vert_in) : TARGET 35 | { 36 | float4 rgb = image.Sample(cnv_sampler, vert_in.uv); 37 | float4 uv00; 38 | uv00.z = -0.100643 * rgb.x -0.338571 * rgb.y +0.439216 * rgb.z +0.5 - 1.0/256.0; // U 39 | uv00.y = +0.212600 * rgb.x +0.715200 * rgb.y +0.072200 * rgb.z; // Y 40 | uv00.x = +0.439216 * rgb.x -0.398941 * rgb.y -0.040273 * rgb.z +0.5; // V 41 | uv00.a = 1; 42 | return uv00; 43 | } 44 | 45 | technique ConvertRGB_YUV601 46 | { 47 | pass 48 | { 49 | vertex_shader = VSDefault(vert_in); 50 | pixel_shader = PSConvertRGB_YUV601(vert_in); 51 | } 52 | } 53 | 54 | technique ConvertRGB_YUV709 55 | { 56 | pass 57 | { 58 | vertex_shader = VSDefault(vert_in); 59 | pixel_shader = PSConvertRGB_YUV709(vert_in); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /data/falsecolor-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norihiro/obs-color-monitor/f309fd88b0a924f4e82c2fbefbe7d627564837b2/data/falsecolor-key.png -------------------------------------------------------------------------------- /data/falsecolor.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform bool use_lut; 4 | uniform texture2d lut; 5 | 6 | sampler_state def_sampler { 7 | Filter = Linear; 8 | AddressU = Clamp; 9 | AddressV = Clamp; 10 | }; 11 | 12 | sampler_state lut_sampler { 13 | Filter = Point; 14 | AddressU = Clamp; 15 | AddressV = Clamp; 16 | }; 17 | 18 | struct VertInOut { 19 | float4 pos : POSITION; 20 | float2 uv : TEXCOORD0; 21 | }; 22 | 23 | VertInOut VSDefault(VertInOut vert_in) 24 | { 25 | VertInOut vert_out; 26 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 27 | vert_out.uv = vert_in.uv; 28 | return vert_out; 29 | } 30 | 31 | float4 PSDrawFalseColor601(VertInOut vert_in) : TARGET 32 | { 33 | float4 rgba = image.Sample(def_sampler, vert_in.uv); 34 | float y = dot(rgba.xyz, float3(0.299000, 0.587000, 0.114000)); 35 | 36 | if (use_lut) 37 | return lut.Sample(lut_sampler, float2(y, 0.5)); 38 | else if (y < 0.02) 39 | return float4(0.85, 0.22, 1.0, 1.0); // bright purple 40 | else if (y < 0.10) 41 | return float4(0.0, 0.0, 1.0, 1.0); // blue 42 | else if (y < 0.20) 43 | return float4(0.33, 0.55, 1.0, 1.0); // light blue 44 | else if (y < 0.42) 45 | return float4(0.3, 0.3, 0.3, 1.0); // dark grey 46 | else if (y < 0.48) 47 | return float4(0.6, 1.0, 0.0, 1.0); // green 48 | else if (y < 0.52) 49 | return float4(0.5, 0.5, 0.5, 1.0); // medium grey 50 | else if (y < 0.58) 51 | return float4(0.95, 0.62, 0.62, 1.0); // pink 52 | else if (y < 0.78) 53 | return float4(0.7, 0.7, 0.7, 1.0); // light grey 54 | else if (y < 0.84) 55 | return float4(0.7, 0.7, 0.0, 1.0); // dark yellow 56 | else if (y < 0.94) 57 | return float4(1.0, 1.0, 0.0, 1.0); // yellow 58 | else if (y < 1.00) 59 | return float4(0.9, 0.5, 0.0, 1.0); // orange 60 | else 61 | return float4(0.9, 0.2, 0.0, 1.0); // red 62 | } 63 | 64 | float4 PSDrawFalseColor709(VertInOut vert_in) : TARGET 65 | { 66 | float4 rgba = image.Sample(def_sampler, vert_in.uv); 67 | float y = dot(rgba.xyz, float3(0.212600, 0.715200, 0.072200)); 68 | 69 | if (use_lut) 70 | return lut.Sample(lut_sampler, float2(y, 0.5)); 71 | else if (y < 0.02) 72 | return float4(0.85, 0.22, 1.0, 1.0); // bright purple 73 | else if (y < 0.10) 74 | return float4(0.0, 0.0, 1.0, 1.0); // blue 75 | else if (y < 0.20) 76 | return float4(0.33, 0.55, 1.0, 1.0); // light blue 77 | else if (y < 0.42) 78 | return float4(0.3, 0.3, 0.3, 1.0); // dark grey 79 | else if (y < 0.48) 80 | return float4(0.6, 1.0, 0.0, 1.0); // green 81 | else if (y < 0.52) 82 | return float4(0.5, 0.5, 0.5, 1.0); // medium grey 83 | else if (y < 0.58) 84 | return float4(0.95, 0.62, 0.62, 1.0); // pink 85 | else if (y < 0.78) 86 | return float4(0.7, 0.7, 0.7, 1.0); // light grey 87 | else if (y < 0.84) 88 | return float4(0.7, 0.7, 0.0, 1.0); // dark yellow 89 | else if (y < 0.94) 90 | return float4(1.0, 1.0, 0.0, 1.0); // yellow 91 | else if (y < 1.00) 92 | return float4(0.9, 0.5, 0.0, 1.0); // orange 93 | else 94 | return float4(0.9, 0.2, 0.0, 1.0); // red 95 | } 96 | 97 | technique DrawFalseColor601 98 | { 99 | pass 100 | { 101 | vertex_shader = VSDefault(vert_in); 102 | pixel_shader = PSDrawFalseColor601(vert_in); 103 | } 104 | } 105 | technique DrawFalseColor709 106 | { 107 | pass 108 | { 109 | vertex_shader = VSDefault(vert_in); 110 | pixel_shader = PSDrawFalseColor709(vert_in); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /data/focuspeaking.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform float2 dxy; 4 | uniform float4 peaking_color; 5 | uniform float peaking_threshold; 6 | 7 | sampler_state def_sampler { 8 | Filter = Linear; 9 | AddressU = Clamp; 10 | AddressV = Clamp; 11 | }; 12 | 13 | struct VertInOut { 14 | float4 pos : POSITION; 15 | float2 uv : TEXCOORD0; 16 | }; 17 | 18 | VertInOut VSDefault(VertInOut vert_in) 19 | { 20 | VertInOut vert_out; 21 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 22 | vert_out.uv = vert_in.uv; 23 | return vert_out; 24 | } 25 | 26 | float4 PSDrawFocusPeaking(VertInOut vert_in) : TARGET 27 | { 28 | float2 dx = float2(dxy.x, 0.0); 29 | float2 dy = float2(0.0, dxy.y); 30 | float4 rgba = image.Sample(def_sampler, vert_in.uv); 31 | float3 rgb1 = image.Sample(def_sampler, vert_in.uv + dx).xyz; 32 | float3 rgb2 = image.Sample(def_sampler, vert_in.uv + dy).xyz; 33 | float3 rgb3 = image.Sample(def_sampler, vert_in.uv - dx).xyz; 34 | float3 rgb4 = image.Sample(def_sampler, vert_in.uv - dy).xyz; 35 | 36 | float3 drgb = ( 37 | abs(rgb1 - rgba.xyz) + 38 | abs(rgb2 - rgba.xyz) + 39 | abs(rgb3 - rgba.xyz) + 40 | abs(rgb4 - rgba.xyz) ) * 0.25; 41 | 42 | float d = (drgb.x + drgb.y + drgb.z) * 0.3333; 43 | 44 | if (d < peaking_threshold) 45 | return rgba; 46 | else 47 | return peaking_color; 48 | } 49 | 50 | technique DrawFocusPeaking 51 | { 52 | pass 53 | { 54 | vertex_shader = VSDefault(vert_in); 55 | pixel_shader = PSDrawFocusPeaking(vert_in); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /data/histogram.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform float3 hi_max; 4 | uniform float4x4 color = { 5 | 1.00, 0.41, 0.41, 0.0, 6 | 0.00, 1.00, 0.00, 0.0, 7 | 0.53, 0.53, 1.00, 0.0, 8 | 0.00, 0.00, 0.00, 1.0 9 | }; 10 | 11 | sampler_state def_sampler { 12 | Filter = Point; 13 | AddressU = Repeat; 14 | AddressV = Clamp; 15 | }; 16 | 17 | struct VertInOut { 18 | float4 pos : POSITION; 19 | float2 uv : TEXCOORD0; 20 | }; 21 | 22 | VertInOut VSDefault(VertInOut vert_in) 23 | { 24 | VertInOut vert_out; 25 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 26 | vert_out.uv = vert_in.uv; 27 | return vert_out; 28 | } 29 | 30 | float4 PSDrawOverlay(VertInOut vert_in) : TARGET 31 | { 32 | float4 rgb; 33 | rgb.xyz = image.Sample(def_sampler, float2(vert_in.uv.x, 0.5)).xyz; 34 | if (rgb.x >= (1-vert_in.uv.y) * hi_max.x) rgb.x=1.0; else rgb.x=0.0; 35 | if (rgb.y >= (1-vert_in.uv.y) * hi_max.y) rgb.y=1.0; else rgb.y=0.0; 36 | if (rgb.z >= (1-vert_in.uv.y) * hi_max.z) rgb.z=1.0; else rgb.z=0.0; 37 | rgb.a = 1.0; 38 | return rgb; 39 | } 40 | 41 | float4 PSDrawStack(VertInOut vert_in) : TARGET 42 | { 43 | int i = int(vert_in.uv.y * 3); 44 | float v = vert_in.uv.y * 3 - i; 45 | float r = image.Sample(def_sampler, float2(vert_in.uv.x, 0.5))[i]; 46 | if (r >= (1-v) * hi_max[i]) r=1.0; else r=0.0; 47 | return float4(color[i].xyz*r, 1.0); 48 | } 49 | 50 | float4 PSDrawParade(VertInOut vert_in) : TARGET 51 | { 52 | int i = int(vert_in.uv.x * 3); 53 | float r = image.Sample(def_sampler, float2(vert_in.uv.x*3, 0.5))[i]; 54 | if (r >= (1-vert_in.uv.y) * hi_max[i]) r=1.0; else r=0.0; 55 | return float4(color[i].xyz*r, 1.0); 56 | } 57 | 58 | float4 PSDrawStackUV(VertInOut vert_in) : TARGET 59 | { 60 | int i; 61 | float v; 62 | if (vert_in.uv.y < 0.5) { 63 | i = 0; 64 | v = vert_in.uv.y * 2; 65 | } 66 | else { 67 | i = 2; 68 | v = vert_in.uv.y * 2 - 1; 69 | } 70 | float r = image.Sample(def_sampler, float2(vert_in.uv.x, 0.5))[i]; 71 | if (r >= (1-v) * hi_max[i]) r=1.0; else r=0.0; 72 | return float4(color[i].xyz*r, 1.0); 73 | } 74 | 75 | float4 PSDrawParadeUV(VertInOut vert_in) : TARGET 76 | { 77 | int i; 78 | if (vert_in.uv.x < 0.5) 79 | i = 0; 80 | else 81 | i = 2; 82 | float r = image.Sample(def_sampler, float2(vert_in.uv.x*2, 0.5))[i]; 83 | if (r >= (1-vert_in.uv.y) * hi_max[i]) r=1.0; else r=0.0; 84 | return float4(color[i].xyz*r, 1.0); 85 | } 86 | 87 | technique DrawOverlay 88 | { 89 | pass 90 | { 91 | vertex_shader = VSDefault(vert_in); 92 | pixel_shader = PSDrawOverlay(vert_in); 93 | } 94 | } 95 | 96 | technique DrawStack 97 | { 98 | pass 99 | { 100 | vertex_shader = VSDefault(vert_in); 101 | pixel_shader = PSDrawStack(vert_in); 102 | } 103 | } 104 | 105 | technique DrawParade 106 | { 107 | pass 108 | { 109 | vertex_shader = VSDefault(vert_in); 110 | pixel_shader = PSDrawParade(vert_in); 111 | } 112 | } 113 | 114 | technique DrawStackUV 115 | { 116 | pass 117 | { 118 | vertex_shader = VSDefault(vert_in); 119 | pixel_shader = PSDrawStackUV(vert_in); 120 | } 121 | } 122 | 123 | technique DrawParadeUV 124 | { 125 | pass 126 | { 127 | vertex_shader = VSDefault(vert_in); 128 | pixel_shader = PSDrawParadeUV(vert_in); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /data/locale/en-US.ini: -------------------------------------------------------------------------------- 1 | 601="601" 2 | 709="709" 3 | Amber="Amber" 4 | "Amber, IQ"="Amber, IQ" 5 | Auto="Auto" 6 | Bypass="Bypass" 7 | Chroma="Chroma" 8 | "Color space"="Color space" 9 | Components="Components" 10 | Display="Display" 11 | "False Color"="False Color" 12 | VS.Prop.ColorType="Color Type" 13 | VS.Prop.ColorType.White="White" 14 | VS.Prop.ColorType.UV="Chroma" 15 | Graticule="Graticule" 16 | Graticule.Step.100="0%, 100%" 17 | Graticule.Step.50="0%, 50%, 100%" 18 | Graticule.Step.25="Each 25%" 19 | Graticule.Step.20="Each 20%" 20 | Graticule.Step.10="Each 10%" 21 | Green="Green" 22 | "Green, IQ"="Green, IQ" 23 | Height="Height" 24 | Histogram="Histogram" 25 | Histogram.Graticule.V="Graticule (Vertical)" 26 | Histogram.Graticule.H="Graticule (Horizontal)" 27 | Intensity="Intensity" 28 | Interleave="Interleave" 29 | "Level mode"="Level mode" 30 | "Log scale"="Log scale" 31 | Luma="Luma" 32 | "New Scope Dock..."="New Scope Dock..." 33 | None="None" 34 | Overlay="Overlay" 35 | Parade="Parade" 36 | Pixels="Pixels" 37 | Preview="Preview" 38 | Program="Program" 39 | MainView="Main view" 40 | Ratio="Ratio" 41 | RGB="RGB" 42 | ROI="ROI" 43 | Scale="Scale" 44 | "Skin tone color"="Skin tone color" 45 | Source="Source" 46 | Stack="Stack" 47 | "Threshold (high)"="Threshold (high)" 48 | "Threshold (lower)"="Threshold (lower)" 49 | FalseColor.Prop.LUT="Use LUT" 50 | FalseColor.Prop.LUTFile="LUT file name" 51 | FalseColor.Prop.LUTFile.Filter.Image="All image files" 52 | FalseColor.Prop.LUTFile.Filter.All="All files" 53 | Prop.ShowKey="Show key" 54 | Prop.ShowKey.None="None" 55 | Prop.ShowKey.Left="Left" 56 | Prop.ShowKey.Right="Right" 57 | Prop.ShowKey.Outside="Outside (Right)" 58 | Prop.ShowKey.Top="Top" 59 | Prop.ShowKey.Bottom="Bottom" 60 | Prop.ShowKey.Below="Outside (Bottom)" 61 | "Top level"="Top level" 62 | Vectorscope="Vectorscope" 63 | Waveform="Waveform" 64 | YUV="YUV" 65 | Zebra="Zebra" 66 | FocusPeaking.Name="Focus Peaking" 67 | FocusPeaking.Prop.PeakingColor="Color" 68 | FocusPeaking.Prop.PeakingThreshold="Threshold" 69 | FocusPeaking.Prop.ActualSize="Actual Size" 70 | 71 | # common 72 | srclist.prefix.scene="Scene: " 73 | srclist.prefix.source="Source: " 74 | 75 | # dock 76 | dock.menu.show.roi="Show &ROI" 77 | dock.menu.show.vectorscope="Show &Vectorscope" 78 | dock.menu.show.waveform="Show &Waveform" 79 | dock.menu.show.histogram="Show &Histogram" 80 | dock.menu.show.zebra="Show &Zebra" 81 | dock.menu.show.falsecolor="Show &False Color" 82 | dock.menu.show.focuspeaking="Show Focus &Peaking" 83 | dock.menu.properties="Properties..." 84 | dock.menu.projector="Open Pro&jector" 85 | dock.menu.close="Close (&X)" 86 | dock.dialog.title="Dock Title" 87 | dock.dialog.note="Other sources can be selected from the property after creating the dock." 88 | 89 | # src-obsstudio 90 | OK="OK" 91 | Show="Show" 92 | Hide="Hide" 93 | Basic.PropertiesWindow.AutoSelectFormat="%1 (autoselect: %2)" 94 | Basic.PropertiesWindow.SelectColor="Select color" 95 | Basic.PropertiesWindow.SelectFont="Select font" 96 | Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'" 97 | Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'" 98 | -------------------------------------------------------------------------------- /data/locale/fr-FR.ini: -------------------------------------------------------------------------------- 1 | 601="601" 2 | 709="709" 3 | Amber="Ambre" 4 | "Amber, IQ"="Ambre, IQ" 5 | Auto="Auto" 6 | Bypass="Bypass" 7 | Chroma="Chroma" 8 | "Color space"="Espace colorimétrique" 9 | Components="Composants" 10 | Display="Affichage" 11 | "False Color"="Fausse couleur" 12 | VS.Prop.ColorType="Type de couleur" 13 | VS.Prop.ColorType.White="Blanc" 14 | VS.Prop.ColorType.UV="Chroma" 15 | Graticule="Graticule" 16 | Graticule.Step.100="0 %, 100 %" 17 | Graticule.Step.50="0 %, 50 %, 100 %" 18 | Graticule.Step.25="Tous les 25 %" 19 | Graticule.Step.20="Tous les 20 %" 20 | Graticule.Step.10="Tous les 10 %" 21 | Green="Vert" 22 | "Green, IQ"="Vert, IQ" 23 | Height="Hauteur" 24 | Histogram="Histogramme" 25 | Histogram.Graticule.V="Graticule (Vertical)" 26 | Histogram.Graticule.H="Graticule (Horizontal)" 27 | Intensity="Intensité" 28 | Interleave="Entrelacement" 29 | "Level mode"="Mode de niveau" 30 | "Log scale"="Échelle logarithmique" 31 | Luma="Luma" 32 | "New Scope Dock..."="Nouveau dock d’analyse..." 33 | None="Aucun" 34 | Overlay="Superposition" 35 | Parade="Parade" 36 | Pixels="Pixels" 37 | Preview="Aperçu" 38 | Program="Programme" 39 | MainView="Vue principale" 40 | Ratio="Ratio" 41 | RGB="RGB" 42 | ROI="ROI" 43 | Scale="Échelle" 44 | "Skin tone color"="Couleur de peau" 45 | Source="Source" 46 | Stack="Pile" 47 | "Threshold (high)"="Seuil (haut)" 48 | "Threshold (lower)"="Seuil (bas)" 49 | FalseColor.Prop.LUT="Utiliser la LUT" 50 | FalseColor.Prop.LUTFile="Nom du fichier LUT" 51 | FalseColor.Prop.LUTFile.Filter.Image="Tous les fichiers image" 52 | FalseColor.Prop.LUTFile.Filter.All="Tous les fichiers" 53 | Prop.ShowKey="Afficher la légende" 54 | Prop.ShowKey.None="Aucun" 55 | Prop.ShowKey.Left="Gauche" 56 | Prop.ShowKey.Right="Droite" 57 | Prop.ShowKey.Outside="Extérieur (Droite)" 58 | Prop.ShowKey.Top="Haut" 59 | Prop.ShowKey.Bottom="Bas" 60 | Prop.ShowKey.Below="Extérieur (Bas)" 61 | "Top level"="Niveau supérieur" 62 | Vectorscope="Vecteurscope" 63 | Waveform="Forme d’onde" 64 | YUV="YUV" 65 | Zebra="Zébra" 66 | FocusPeaking.Name="Accentuation de la mise au point" 67 | FocusPeaking.Prop.PeakingColor="Couleur" 68 | FocusPeaking.Prop.PeakingThreshold="Seuil" 69 | FocusPeaking.Prop.ActualSize="Taille réelle" 70 | 71 | # commun 72 | srclist.prefix.scene="Scène : " 73 | srclist.prefix.source="Source : " 74 | 75 | # dock 76 | dock.menu.show.roi="Afficher &ROI" 77 | dock.menu.show.vectorscope="Afficher &Vecteurscope" 78 | dock.menu.show.waveform="Afficher &Forme d’onde" 79 | dock.menu.show.histogram="Afficher &Histogramme" 80 | dock.menu.show.zebra="Afficher &Zébra" 81 | dock.menu.show.falsecolor="Afficher Fausse &couleur" 82 | dock.menu.show.focuspeaking="Afficher l’&accentuation" 83 | dock.menu.properties="Propriétés..." 84 | dock.menu.projector="Ouvrir le pro&jecteur" 85 | dock.menu.close="Fermer (&X)" 86 | dock.dialog.title="Titre du dock" 87 | dock.dialog.note="D'autres sources peuvent être sélectionnées dans les propriétés après création du dock." 88 | 89 | # src-obsstudio 90 | OK="OK" 91 | Show="Afficher" 92 | Hide="Masquer" 93 | Basic.PropertiesWindow.AutoSelectFormat="%1 (sélection automatique : %2)" 94 | Basic.PropertiesWindow.SelectColor="Choisir une couleur" 95 | Basic.PropertiesWindow.SelectFont="Choisir une police" 96 | Basic.PropertiesWindow.AddEditableListEntry="Ajouter une entrée à '%1'" 97 | Basic.PropertiesWindow.EditEditableListEntry="Modifier une entrée de '%1'" 98 | -------------------------------------------------------------------------------- /data/locale/ja-JP.ini: -------------------------------------------------------------------------------- 1 | 601="601" 2 | 709="709" 3 | Amber="橙" 4 | "Amber, IQ"="橙, IQ" 5 | Auto="自動" 6 | Bypass="バイパス" 7 | Chroma="クロマ" 8 | "Color space"="色空間" 9 | Components="コンポーネント" 10 | Display="表示" 11 | "False Color"="偽色" 12 | VS.Prop.ColorType="表示" 13 | VS.Prop.ColorType.White="白" 14 | VS.Prop.ColorType.UV="色" 15 | Graticule="目盛" 16 | Graticule.Step.100="0%, 100%" 17 | Graticule.Step.50="0%, 50%, 100%" 18 | Graticule.Step.25="25%" 19 | Graticule.Step.20="20%" 20 | Graticule.Step.10="10%" 21 | Green="緑" 22 | "Green, IQ"="緑, IQ" 23 | Height="高さ" 24 | Histogram="ヒストグラム" 25 | Histogram.Graticule.V="目盛 (垂直)" 26 | Histogram.Graticule.H="目盛 (水平)" 27 | Intensity="輝度" 28 | Interleave="間引き" 29 | "Level mode"="レベルモード" 30 | "Log scale"="対数スケール" 31 | Luma="色差" 32 | "New Scope Dock..."="スコープドックを追加..." 33 | None="なし" 34 | Overlay="オーバーレイ" 35 | Parade="パレード" 36 | Pixels="ピクセル数" 37 | Preview="プレビュー" 38 | Program="プログラム" 39 | MainView="メインビュー" 40 | Ratio="割合" 41 | RGB="RGB" 42 | ROI="ROI" 43 | Scale="スケール" 44 | "Skin tone color"="Skin tone color" 45 | Source="ソース" 46 | Stack="スタック" 47 | "Threshold (high)"="しきい値 (高)" 48 | "Threshold (lower)"="しきい値 (低)" 49 | FalseColor.Prop.LUT="LUTを使用する" 50 | FalseColor.Prop.LUTFile="LUTのファイル名" 51 | FalseColor.Prop.LUTFile.Filter.Image="すべての画像ファイル" 52 | FalseColor.Prop.LUTFile.Filter.All="すべてのファイル" 53 | Prop.ShowKey="キー" 54 | Prop.ShowKey.None="なし" 55 | Prop.ShowKey.Left="左" 56 | Prop.ShowKey.Right="右" 57 | Prop.ShowKey.Outside="外側 (右)" 58 | Prop.ShowKey.Top="上" 59 | Prop.ShowKey.Bottom="下" 60 | Prop.ShowKey.Below="外側 (下)" 61 | "Top level"="最高レベル" 62 | Vectorscope="ベクトルスコープ" 63 | Waveform="波形" 64 | YUV="YUV" 65 | Zebra="ゼブラ" 66 | FocusPeaking.Name="フォーカスピーキング" 67 | FocusPeaking.Prop.PeakingColor="色" 68 | FocusPeaking.Prop.PeakingThreshold="しきい値" 69 | FocusPeaking.Prop.ActualSize="実際のサイズ" 70 | 71 | # common 72 | srclist.prefix.scene="シーン: " 73 | srclist.prefix.source="ソース: " 74 | 75 | # dock 76 | dock.menu.show.roi="&ROIを表示" 77 | dock.menu.show.vectorscope="ベクトルスコープを表示 (&V)" 78 | dock.menu.show.waveform="波形を表示 (&W)" 79 | dock.menu.show.histogram="&ヒストグラムを表示 (&H)" 80 | dock.menu.show.zebra="ゼブラを表示 (&Z)" 81 | dock.menu.show.falsecolor="&偽色を表示 (&F)" 82 | dock.menu.show.focuspeaking="フォーカスピーキングを表示 (&P)" 83 | dock.menu.properties="プロパティ..." 84 | dock.menu.projector="プロジェクタを開く (&J)" 85 | dock.menu.close="閉じる (&X)" 86 | dock.dialog.title="ドックのタイトル" 87 | dock.dialog.note="他のソースはドック作成後に選択できます。" 88 | 89 | # src-obsstudio 90 | OK="OK" 91 | Show="表示" 92 | Hide="非表示" 93 | Basic.PropertiesWindow.AutoSelectFormat="%1 (自動選択: %2)" 94 | Basic.PropertiesWindow.SelectColor="色を選択" 95 | Basic.PropertiesWindow.SelectFont="フォントを選択" 96 | Basic.PropertiesWindow.AddEditableListEntry="Add entry to '%1'" 97 | Basic.PropertiesWindow.EditEditableListEntry="Edit entry from '%1'" 98 | -------------------------------------------------------------------------------- /data/locale/pt-BR.ini: -------------------------------------------------------------------------------- 1 | 601="601" 2 | 709="709" 3 | Amber="Âmbar" 4 | "Amber, IQ"="Âmbar, IQ" 5 | Auto="Auto" 6 | Bypass="Bypass" 7 | Chroma="Chroma" 8 | "Color space"="Espaço de Cor" 9 | Components="Componentes" 10 | Display="Monitor" 11 | "False Color"="Cor Falsa" 12 | VS.Prop.ColorType="Tipo de cor" 13 | VS.Prop.ColorType.White="Branco" 14 | VS.Prop.ColorType.UV="Croma" 15 | Graticule="Gratícula" 16 | Graticule.Step.100="0%, 100%" 17 | Graticule.Step.50="0%, 50%, 100%" 18 | Graticule.Step.25="A cada 25%" 19 | Graticule.Step.20="A cada 20%" 20 | Graticule.Step.10="A cada 10%" 21 | Green="Verde" 22 | "Green, IQ"="Verde, IQ" 23 | Height="Altura" 24 | Histogram="Histograma" 25 | Histogram.Graticule.V="Gratícula (Vertical)" 26 | Histogram.Graticule.H="Gratícula (Horizontal)" 27 | Intensity="Intensidade" 28 | Interleave="Intercalação" 29 | "Level mode"="Modo do nível" 30 | "Log scale"="Escala de log" 31 | Luma="Luma" 32 | "New Scope Dock..."="Novo painel de cores..." 33 | None="Nenhum" 34 | Overlay="Sobreposição" 35 | Parade="Parada" 36 | Pixels="Pixels" 37 | Preview="Pré-visualização" 38 | Program="Programa" 39 | MainView="Visualização principal" 40 | Ratio="Proporção" 41 | RGB="RGB" 42 | ROI="ROI" 43 | Scale="Escala" 44 | "Skin tone color"="Cor do tom de pele" 45 | Source="Fonte" 46 | Stack="Pilha" 47 | "Threshold (high)"="Limite (superior)" 48 | "Threshold (lower)"="Limite (inferior)" 49 | FalseColor.Prop.LUT="Usar LUT" 50 | FalseColor.Prop.LUTFile="Nome do arquivo LUT" 51 | FalseColor.Prop.LUTFile.Filter.Image="Todos os arquivos de imagem" 52 | FalseColor.Prop.LUTFile.Filter.All="Todos os arquivos" 53 | Prop.ShowKey="Mostrar chave" 54 | Prop.ShowKey.None="Nenhuma" 55 | Prop.ShowKey.Left="Esquerda" 56 | Prop.ShowKey.Right="Direita" 57 | Prop.ShowKey.Outside="Externa (Direita)" 58 | Prop.ShowKey.Top="Superior" 59 | Prop.ShowKey.Bottom="Inferior" 60 | Prop.ShowKey.Below="Externa (Inferior)" 61 | "Top level"="Nível superior" 62 | Vectorscope="Vectorscope" 63 | Waveform="Waveform" 64 | YUV="YUV" 65 | Zebra="Zebra" 66 | FocusPeaking.Name="Pico de foco" 67 | FocusPeaking.Prop.PeakingColor="Cor" 68 | FocusPeaking.Prop.PeakingThreshold="Limite" 69 | FocusPeaking.Prop.ActualSize="Tamanho Real" 70 | 71 | # common 72 | srclist.prefix.scene="Cena: " 73 | srclist.prefix.source="Fonte: " 74 | 75 | # dock 76 | dock.menu.show.roi="Mostrar &ROI" 77 | dock.menu.show.vectorscope="Mostrar &Vectorscope" 78 | dock.menu.show.waveform="Mostrar &Waveform" 79 | dock.menu.show.histogram="Mostrar &Histograma" 80 | dock.menu.show.zebra="Mostrar &Zebra" 81 | dock.menu.show.falsecolor="Mostrar &False Color" 82 | dock.menu.show.focuspeaking="Mostrar &Pico de Foco" 83 | dock.menu.properties="Propriedades..." 84 | dock.menu.projector="Abrir Pro&jetor" 85 | dock.menu.close="Fechar (&X)" 86 | dock.dialog.title="Título do painel" 87 | dock.dialog.note="Outras fontes poderão ser selecionadas nas propriedades depois de criar o painel." 88 | 89 | # src-obsstudio 90 | OK="OK" 91 | Show="Mostrar" 92 | Hide="Ocultar" 93 | Basic.PropertiesWindow.AutoSelectFormat="%1 (seleção automática: %2)" 94 | Basic.PropertiesWindow.SelectColor="Escolher cor" 95 | Basic.PropertiesWindow.SelectFont="Escolher fonte" 96 | Basic.PropertiesWindow.AddEditableListEntry="Adicionar entrada para '%1'" 97 | Basic.PropertiesWindow.EditEditableListEntry="Editar a entrada de '%1'" 98 | -------------------------------------------------------------------------------- /data/locale/zh-CN.ini: -------------------------------------------------------------------------------- 1 | 601="601" 2 | 709="709" 3 | Amber="琥珀色" 4 | "Amber, IQ"="琥珀色, IQ" 5 | Auto="自动" 6 | Bypass="旁路" 7 | Chroma="色度" 8 | "Color space"="色彩空间" 9 | Components="组件" 10 | Display="显示" 11 | "False Color"="伪色" 12 | VS.Prop.ColorType="颜色类型" 13 | VS.Prop.ColorType.White="白色" 14 | VS.Prop.ColorType.UV="色度" 15 | Graticule="经纬网" 16 | Graticule.Step.100="0%, 100%" 17 | Graticule.Step.50="0%, 50%, 100%" 18 | Graticule.Step.25="格 25%" 19 | Graticule.Step.20="格 20%" 20 | Graticule.Step.10="格 10%" 21 | Green="绿色" 22 | "Green, IQ"="绿色, IQ" 23 | Height="高度" 24 | Histogram="直方图" 25 | Histogram.Graticule.V="经纬网 (垂直)" 26 | Histogram.Graticule.H="经纬网 (水平)" 27 | Intensity="强度" 28 | Interleave="交织" 29 | "Level mode"="电平模式" 30 | "Log scale"="对数刻度" 31 | Luma="亮度" 32 | "New Scope Dock..."="新示波器窗口..." 33 | None="无" 34 | Overlay="覆盖" 35 | Parade="彩条图" 36 | Pixels="像素" 37 | Preview="预览" 38 | Program="程序" 39 | MainView="主视图" 40 | Ratio="比率" 41 | RGB="RGB" 42 | ROI="ROI" 43 | Scale="缩放" 44 | "Skin tone color"="肤色颜色" 45 | Source="源" 46 | Stack="堆栈" 47 | "Threshold (high)"="阈值 (高)" 48 | "Threshold (lower)"="阈值 (低)" 49 | FalseColor.Prop.LUT="使用 LUT" 50 | FalseColor.Prop.LUTFile="LUT 文件名" 51 | FalseColor.Prop.LUTFile.Filter.Image="所有图像文件" 52 | FalseColor.Prop.LUTFile.Filter.All="所有文件" 53 | Prop.ShowKey="显示键位" 54 | Prop.ShowKey.None="无" 55 | Prop.ShowKey.Left="左" 56 | Prop.ShowKey.Right="右" 57 | Prop.ShowKey.Outside="外部(右部)" 58 | Prop.ShowKey.Top="顶部" 59 | Prop.ShowKey.Bottom="底部" 60 | Prop.ShowKey.Below="外部(底部)" 61 | "Top level"="顶层" 62 | Vectorscope="矢量示波器" 63 | Waveform="波形图" 64 | YUV="YUV系列" 65 | Zebra="斑马线" 66 | FocusPeaking.Name="峰值对焦" 67 | FocusPeaking.Prop.PeakingColor="颜色" 68 | FocusPeaking.Prop.PeakingThreshold="阈值" 69 | FocusPeaking.Prop.ActualSize="实际大小" 70 | 71 | # common 72 | srclist.prefix.scene="场景: " 73 | srclist.prefix.source="源: " 74 | 75 | # dock 76 | dock.menu.show.roi="显示&ROI" 77 | dock.menu.show.vectorscope="显示矢量示波器 (&V)" 78 | dock.menu.show.waveform="显示波形图 (&W)" 79 | dock.menu.show.histogram="显示直方图 (&H)" 80 | dock.menu.show.zebra="显示斑马线 (&Z)" 81 | dock.menu.show.falsecolor="显示伪色 (&F)" 82 | dock.menu.show.focuspeaking="显示聚焦峰值显示 (&P)" 83 | dock.menu.properties="属性..." 84 | dock.menu.projector="打开窗口投影 (&J)" 85 | dock.menu.close="关闭 (&X)" 86 | dock.dialog.title="停靠栏标题" 87 | dock.dialog.note="创建停靠栏后,可以从属性中选择其他源。" 88 | 89 | # src-obsstudio 90 | OK="好的" 91 | Show="显示" 92 | Hide="隐藏" 93 | Basic.PropertiesWindow.AutoSelectFormat="%1 (自动选择: %2)" 94 | Basic.PropertiesWindow.SelectColor="选择颜色" 95 | Basic.PropertiesWindow.SelectFont="选择字体" 96 | Basic.PropertiesWindow.AddEditableListEntry="添加条目到 '%1'" 97 | Basic.PropertiesWindow.EditEditableListEntry="编辑来自于 '%1'" 98 | -------------------------------------------------------------------------------- /data/vectorscope-graticule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norihiro/obs-color-monitor/f309fd88b0a924f4e82c2fbefbe7d627564837b2/data/vectorscope-graticule.png -------------------------------------------------------------------------------- /data/vectorscope.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform float intensity; 4 | uniform float4 color = {1.0, 1.0, 1.0, 1.0}; 5 | uniform float3 color_u = {0.0, 0.0, 0.0}; 6 | uniform float3 color_v = {0.0, 0.0, 0.0}; 7 | 8 | sampler_state def_sampler { 9 | Filter = Point; 10 | AddressU = Clamp; 11 | AddressV = Clamp; 12 | }; 13 | 14 | struct VertInOut { 15 | float4 pos : POSITION; 16 | float2 uv : TEXCOORD0; 17 | }; 18 | 19 | VertInOut VSDefault(VertInOut vert_in) 20 | { 21 | VertInOut vert_out; 22 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 23 | vert_out.uv = vert_in.uv; 24 | return vert_out; 25 | } 26 | 27 | float4 PSDrawBare(VertInOut vert_in) : TARGET 28 | { 29 | float3 rgb = color.xyz + color_u * (vert_in.uv.x * 2.0 - 1.0) + color_v * (1.0 - vert_in.uv.y * 2.0); 30 | float r = image.Sample(def_sampler, vert_in.uv).x * intensity; 31 | if (r>1.0) r = 1.0; 32 | return float4(rgb * r, color.a); 33 | } 34 | 35 | float4 PSDrawGraticule(VertInOut vert_in) : TARGET 36 | { 37 | float a = image.Sample(def_sampler, vert_in.uv).a; 38 | return float4(color.xyz, color.a*a); 39 | } 40 | 41 | technique Draw 42 | { 43 | pass 44 | { 45 | vertex_shader = VSDefault(vert_in); 46 | pixel_shader = PSDrawBare(vert_in); 47 | } 48 | } 49 | 50 | technique DrawGraticule 51 | { 52 | pass 53 | { 54 | vertex_shader = VSDefault(vert_in); 55 | pixel_shader = PSDrawGraticule(vert_in); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /data/waveform.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform float intensity; 4 | uniform float4x4 color = { 5 | 1.00, 0.41, 0.41, 0.0, 6 | 0.00, 1.00, 0.00, 0.0, 7 | 0.53, 0.53, 1.00, 0.0, 8 | 0.00, 0.00, 0.00, 1.0 9 | }; 10 | 11 | sampler_state def_sampler { 12 | Filter = Point; 13 | AddressU = Repeat; 14 | AddressV = Repeat; 15 | }; 16 | 17 | struct VertInOut { 18 | float4 pos : POSITION; 19 | float2 uv : TEXCOORD0; 20 | }; 21 | 22 | VertInOut VSDefault(VertInOut vert_in) 23 | { 24 | VertInOut vert_out; 25 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 26 | vert_out.uv = vert_in.uv; 27 | return vert_out; 28 | } 29 | 30 | float4 PSDrawOverlay(VertInOut vert_in) : TARGET 31 | { 32 | float4 rgb; 33 | rgb.xyz = image.Sample(def_sampler, vert_in.uv).xyz * intensity; 34 | if (rgb.x>1.0) rgb.x = 1.0; 35 | if (rgb.y>1.0) rgb.y = 1.0; 36 | if (rgb.z>1.0) rgb.z = 1.0; 37 | rgb.a = 1.0; 38 | return rgb; 39 | } 40 | 41 | float4 PSDrawStack(VertInOut vert_in) : TARGET 42 | { 43 | int i = int(vert_in.uv.y * 3); 44 | float v = vert_in.uv.y * 3 - i; 45 | float r = image.Sample(def_sampler, float2(vert_in.uv.x, v))[i] * intensity; 46 | if (r>1.0) r = 1.0; 47 | return float4(color[i].xyz*r, 1.0); 48 | } 49 | 50 | float4 PSDrawParade(VertInOut vert_in) : TARGET 51 | { 52 | int i = int(vert_in.uv.x * 3); 53 | float u = vert_in.uv.x * 3 - i; 54 | float r = image.Sample(def_sampler, float2(u, vert_in.uv.y))[i] * intensity; 55 | if (r>1.0) r = 1.0; 56 | return float4(color[i].xyz*r, 1.0); 57 | } 58 | 59 | float4 PSDrawStackUV(VertInOut vert_in) : TARGET 60 | { 61 | int i; 62 | if (vert_in.uv.y < 0.5) 63 | i = 0; 64 | else 65 | i = 2; 66 | float v = vert_in.uv.y * 2; 67 | float r = image.Sample(def_sampler, float2(vert_in.uv.x, v))[i] * intensity; 68 | if (r>1.0) r = 1.0; 69 | return float4(color[i].xyz*r, 1.0); 70 | } 71 | 72 | float4 PSDrawParadeUV(VertInOut vert_in) : TARGET 73 | { 74 | int i; 75 | if (vert_in.uv.x < 0.5) 76 | i = 0; 77 | else 78 | i = 2; 79 | float u = vert_in.uv.x * 2; 80 | float r = image.Sample(def_sampler, float2(u, vert_in.uv.y))[i] * intensity; 81 | if (r>1.0) r = 1.0; 82 | return float4(color[i].xyz*r, 1.0); 83 | } 84 | 85 | technique DrawOverlay 86 | { 87 | pass 88 | { 89 | vertex_shader = VSDefault(vert_in); 90 | pixel_shader = PSDrawOverlay(vert_in); 91 | } 92 | } 93 | 94 | technique DrawStack 95 | { 96 | pass 97 | { 98 | vertex_shader = VSDefault(vert_in); 99 | pixel_shader = PSDrawStack(vert_in); 100 | } 101 | } 102 | 103 | technique DrawParade 104 | { 105 | pass 106 | { 107 | vertex_shader = VSDefault(vert_in); 108 | pixel_shader = PSDrawParade(vert_in); 109 | } 110 | } 111 | 112 | technique DrawStackUV 113 | { 114 | pass 115 | { 116 | vertex_shader = VSDefault(vert_in); 117 | pixel_shader = PSDrawStackUV(vert_in); 118 | } 119 | } 120 | 121 | technique DrawParadeUV 122 | { 123 | pass 124 | { 125 | vertex_shader = VSDefault(vert_in); 126 | pixel_shader = PSDrawParadeUV(vert_in); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /data/zebra.effect: -------------------------------------------------------------------------------- 1 | uniform float4x4 ViewProj; 2 | uniform texture2d image; 3 | uniform float zebra_th_low; 4 | uniform float zebra_th_high; 5 | uniform float zebra_tm; 6 | 7 | sampler_state def_sampler { 8 | Filter = Linear; 9 | AddressU = Clamp; 10 | AddressV = Clamp; 11 | }; 12 | 13 | struct VertInOut { 14 | float4 pos : POSITION; 15 | float2 uv : TEXCOORD0; 16 | }; 17 | 18 | VertInOut VSDefault(VertInOut vert_in) 19 | { 20 | VertInOut vert_out; 21 | vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); 22 | vert_out.uv = vert_in.uv; 23 | return vert_out; 24 | } 25 | 26 | float4 PSDrawZebra601(VertInOut vert_in) : TARGET 27 | { 28 | float4 rgba = image.Sample(def_sampler, vert_in.uv); 29 | float y = dot(rgba.xyz, float3(0.299000, 0.587000, 0.114000)); 30 | int xy = int(vert_in.pos.x + vert_in.pos.y + zebra_tm); 31 | xy = xy - (xy / 6) * 6; 32 | if (zebra_th_low<=y && y<=zebra_th_high && xy<3) 33 | return float4(0.0, 0.0, 0.0, 1.0); 34 | else 35 | return rgba; 36 | } 37 | 38 | float4 PSDrawZebra709(VertInOut vert_in) : TARGET 39 | { 40 | float4 rgba = image.Sample(def_sampler, vert_in.uv); 41 | float y = dot(rgba.xyz, float3(0.212600, 0.715200, 0.072200)); 42 | int xy = int(vert_in.pos.x + vert_in.pos.y + zebra_tm); 43 | xy = xy - (xy / 6) * 6; 44 | if (zebra_th_low<=y && y<=zebra_th_high && xy<3) 45 | return float4(0.0, 0.0, 0.0, 1.0); 46 | else 47 | return rgba; 48 | } 49 | 50 | technique DrawZebra601 51 | { 52 | pass 53 | { 54 | vertex_shader = VSDefault(vert_in); 55 | pixel_shader = PSDrawZebra601(vert_in); 56 | } 57 | } 58 | technique DrawZebra709 59 | { 60 | pass 61 | { 62 | vertex_shader = VSDefault(vert_in); 63 | pixel_shader = PSDrawZebra709(vert_in); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /doc/dock.md: -------------------------------------------------------------------------------- 1 | # Color Scope Dock 2 | 3 | ## Introduction 4 | 5 | Color Scope Dock shows ROI (region of interest), and all other scopes including vectorscope, waveform, histogram. 6 | 7 | ## Opening the Color Scope Dock 8 | 9 | To open the dock, 10 | Click `Tools` and `New Scope Dock...` in the menu of OBS Studio. 11 | Input the name and clock OK. 12 | 13 | ## ROI Mouse Interaction 14 | 15 | ROI is a quick tool to select a rectangle to pick for the scope calculation. 16 | 17 | ### Setting the region 18 | 19 | Dragging with left-button will select a region. 20 | 21 | ### Moving the region 22 | 23 | After setting the region first, 24 | drag the center of the region to move the region. 25 | When you hover the region, green outline will appear to indicate you can drag the region. 26 | 27 | ### Resizing the region 28 | 29 | After setting the region first, 30 | drag the edge of the region to resize the edge of the region. 31 | When you move your mouse close to the edge, a square will appear in addition to the region outline. The square indicates you can resize the edge. 32 | 33 | ## Selecting Scopes 34 | The dock provides ROI and some types of scopes. 35 | All scopes are on by default. 36 | Each scope can be turned off from a right-click menu. 37 | 38 | ## ROI Properties 39 | 40 | ### Source 41 | 42 | Selects one of Program, Main view, Preview, Scene, or Source. 43 | Default is Program. 44 | 45 | If you select Main view, 46 | the texture before overlaying scenes made by [Downstream Keyer](https://github.com/exeldro/obs-downstream-keyer) plugin. 47 | Without Downstream Keyer, there is no difference between Program and Main view. 48 | 49 | ### Scale 50 | 51 | Scale factor before calculating vectorscope. 52 | The width and height of the source will be scaled by this number. 53 | Main purpose is to shorten the rendering time. 54 | Larger value will degrade the accuracy and intensity. 55 | For example, if you change scale from `1` to `2`, you need to increase intensity of Vectorscope from `1` to `4`, intensity of Waveform from `1` to `2` to get the same intensity. The intensity of Histogram won't be affected. 56 | Default is `2`, which means width and height are both scaled by half. Available range is an integer number beween `1` - `128`. 57 | 58 | ### Interleave 59 | 60 | This property controls whether the calculation will be interleaved or not. 61 | Main purpose is to hide the rendering time. 62 | If set to `0`, interleave won't happen. Every frame will be processed. 63 | If set to `1`, a frame for each 2 frames will be processed. 64 | In this setting, UV channel calculation and staging (sending data from GPU to CPU) will happen at odd frames, counting each color for each scope will happen at even frames. 65 | Default is `1`. 66 | -------------------------------------------------------------------------------- /doc/falsecolor-lut-default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/norihiro/obs-color-monitor/f309fd88b0a924f4e82c2fbefbe7d627564837b2/doc/falsecolor-lut-default.png -------------------------------------------------------------------------------- /doc/falsecolor.md: -------------------------------------------------------------------------------- 1 | # False Color 2 | 3 | ## Introduction 4 | 5 | False Color shows intensity in different colors to help adjust exposure. 6 | 7 | The false color is hardcoded in an effect file `data/falsecolor.effect`. 8 | 9 | RGB color `#000000` corresponds to 0 IRE `#FFFFFF` corresponds to 100 IRE. 10 | 11 | color table 12 | 13 | ## Properties 14 | 15 | ### Source 16 | (Not available for False Color Filter.) 17 | 18 | Selects one of Program, Main view, Preview, Scene, or Source. 19 | The default is Program. 20 | 21 | ### Use LUT 22 | 23 | Enable if you want to customize the color map. 24 | 25 | ### LUT file name 26 | 27 | If 'Use LUT' is enabled, choose an image file for the color map. 28 | The image file should be 1-pixel height and can have any width. 29 | The left most pixel represents the black and the right most pixel represents the white. 30 | 31 | An example is [available here](falsecolor-lut-default.png). 32 | 33 | ### Show key 34 | 35 | A key will be shown. 36 | Available options are as below. 37 | 38 | | Option | Description | 39 | | ------ | ----------- | 40 | | None | The key won't be displayed. | 41 | | Left | A vertical key will be displayed on the left side on the source. | 42 | | Right | A vertical key will be displayed on the right side on the source. | 43 | | Outside (Right) | A vertical key will be displayed right outside of the source. | 44 | | Top | A horizontal key will be displayed on the top on the source. | 45 | | Bottom | A horizontal key will be displayed on the bottom on the source. | 46 | | Outside (Bottom) | A horizontal key will be displayed below the source. | 47 | 48 | ### Color space 49 | 50 | Choice of color space; Auto, BT.601, or BT.709. 51 | If Auto, the color space is retrieved from the settings of OBS Studio. 52 | Coefficients to convert from RGB to Luminance will be changed. 53 | Default is Auto. 54 | -------------------------------------------------------------------------------- /doc/falsecolor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 40 | 45 | 46 | 48 | 52 | 100% 63 | 94% 74 | 84% 85 | 78% 96 | 58% 107 | 52% 118 | 48% 129 | 42% 140 | 20% 151 | 10% 162 | 2% 173 | 180 | 187 | 194 | 201 | 208 | 215 | 222 | 229 | 236 | 243 | 250 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /doc/focuspeaking.md: -------------------------------------------------------------------------------- 1 | # Focus Peaking 2 | 3 | ## Introduction 4 | 5 | Focus peaking highlights pixcels that have sharp focus. 6 | It will be useful to adjust the focus of your camera. 7 | 8 | ## Properties 9 | 10 | ### Source 11 | (Not available for Focus Peaking Filter.) 12 | 13 | Selects one of Program, Main view, Preview, Scene, or Source. 14 | The default is Program. 15 | 16 | ### Peaking Color 17 | 18 | Specify the color to highlight the focus with. 19 | 20 | ### Peaking Threshold 21 | 22 | Specify the threshold level to highlight. 23 | -------------------------------------------------------------------------------- /doc/global_config.md: -------------------------------------------------------------------------------- 1 | # Global Configuration 2 | 3 | This plugin uses `ColorMonitor` section in the global configuration file (`global.ini`). 4 | 5 | The file is located at 6 | - `%AppData%\obs-studio\global.ini` on Windows, 7 | - `~/Library/Application Support/obs-studio/global.ini` on macOS, and 8 | - `~/.config/obs-studio/global.ini` on Linux. 9 | 10 | ## Hide source and filter types 11 | 12 | To hide source types and/or filter types, replace `true` with `false` for the key `ShowSource` and/or `ShowFilter`, respectively. 13 | ```ini 14 | [ColorMonitor] 15 | ShowSource=true 16 | ShowFilter=true 17 | ``` 18 | -------------------------------------------------------------------------------- /doc/histogram.md: -------------------------------------------------------------------------------- 1 | # Histogram 2 | 3 | ## Introduction 4 | 5 | Histogram shows population of red (R), green (G), blue (B) components. 6 | 7 | X-axis corresponds to the value of color component. Bright color stays right, dark color stays left. 8 | Y-axis shows the population. The Y-axis is automatically scaled to the maximum population. 9 | 10 | ## Properties 11 | 12 | ### Source 13 | 14 | Selects one of Program, Main view, Preview, Scene, or Source. 15 | Default is Program. 16 | 17 | ### Scale 18 | 19 | Scale factor before calculating histogram. 20 | The width and height of the source will be scaled by this number. 21 | Main purpose is to shorten the rendering time. 22 | Larger value will degrade the accuracy and intensity. 23 | Default is `2`, which means width and height are both scaled by half. Available range is an integer number beween `1` - `128`. 24 | 25 | ### Display 26 | 27 | Choice of displaying mode; Overlay, Stack, or Parade. 28 | | Display | Description | 29 | |---------|-------------| 30 | | Overlay (default) | Each color components will be displayed on the same place by each color. | 31 | | Stack | R, G, B histograms are displayed from the top to the bottom. | 32 | | Parade | R, G, B histograms are displayed from the left to the right. | 33 | 34 | If setting components to YUV, CrYCb are displayed instead of RGB, respectively. 35 | If setting components to Luma only, this property is not effective. 36 | 37 | ### Components 38 | Choice of components to be displayed. 39 | | Components | Description | 40 | |------------|-------------| 41 | | RGB | R, G, B components are displayed. | 42 | | Luma | Luminance component (*Y*) is displayed. | 43 | | Chroma | Chrominance components (*Cr* and *Cb*) are displayed. | 44 | | YUV | Both luminance an chrominance components are displayed. | 45 | 46 | ### Color space 47 | 48 | Choice of color space; Auto, BT.601, or BT.709. 49 | If Auto, the color space is retrieved from the settings of OBS Studio. 50 | Coefficients for Luminance, Cr and Cb components will be changed. 51 | Default is Auto. This property is only available if the component property is Luma, Chroma, or YUV. 52 | 53 | ### Height 54 | 55 | Height of the output. 56 | Default is `200`. Available range is an integer number between `50` - `2048`. 57 | 58 | ### Log scale 59 | 60 | Check this to plot in log scale. 61 | 62 | ### Level mode 63 | Choice of the peak level 64 | | Level mode | Description | 65 | | ---------- | ----------- | 66 | | Auto | Automatically adjust the peak level to the top. | 67 | | Pixels | The peak level is defined by a number of pixels. | 68 | | Ratio | The peak level is defined by a percentage of the total area. | 69 | 70 | Note that if using ROI, the Ratio mode will calculate based on the ROI area. 71 | 72 | ### Graticule (Vertical) 73 | 74 | Choice of vertical graticule. 75 | 76 | | Choice | Description | 77 | |--------|-------------| 78 | | `None` | No graticule will be displayed. | 79 | | `0%, 100%` | 2 lines will be displayed at 0% and 100%. | 80 | | `0%, 50%, 100%` | 3 lines will be displayed. | 81 | | `each 25%` | 5 lines will be displayed. | 82 | | `each 20%` | 6 lines will be displayed. | 83 | | `each 10%` | 11 lines will be displayed. | 84 | 85 | ### Graticule (Horizontal) 86 | 87 | Only available if `Level mode` is set to `Pixels` or `Ratio`. 88 | 89 | Choice of step size for horizontal graticules. 90 | 91 | ### Bypass 92 | 93 | If you check this, image after the scaling will be displayed. 94 | 95 | ## Output 96 | 97 | Width is scaled width of the source for Overlay and Stack display, 3-times of that for Parade, scaled height for bypass. 98 | Height is controlled by the Height property for Overlay and Parade display, 3-times of that for Stack, scaled height for bypass. 99 | -------------------------------------------------------------------------------- /doc/vectorscope.md: -------------------------------------------------------------------------------- 1 | # Vectorscope 2 | 3 | ## Introduction 4 | 5 | Vectorscope shows population of each chrominance components of a source to help color correction especially *hue* and *saturation*. 6 | 7 | ## Properties 8 | 9 | ### Source 10 | 11 | Selects one of Program, Main view, Preview, Scene, or Source. 12 | Default is Program. 13 | 14 | ### Scale 15 | 16 | Scale factor before calculating vectorscope. 17 | The width and height of the source will be scaled by this number. 18 | Main purpose is to shorten the rendering time. 19 | Larger value will degrade the accuracy and intensity. 20 | For example, if you change scale from `1` to `2`, you need to increase intensity from `1` to `4` to get the same intensity. 21 | Default is `2`, which means width and height are both scaled by half. Available range is an integer number beween `1` - `128`. 22 | 23 | ### Intensity 24 | 25 | Intensity of each pixel. 26 | Population for each color is multiplied by intensity and drawn on the scope. 27 | Larger value will increase the visibility of less population colors. 28 | Default is `25`. Available range is an integer number between `1` - `255`. 29 | 30 | ### Color Type 31 | 32 | - White: The vectorscope will be displayed in grayscale. 33 | - Chroma: The vectorscope will be displayed with corresponding chrominance components. 34 | 35 | ### Graticule 36 | 37 | Choice of graticule. 38 | 39 | | Choice | Description | 40 | |--------|-------------| 41 | | `None` | No graticule will be displayed. | 42 | | `Amber` | 6 boxes for primary colors, these labels, and skin tone line will be displayed in amber. | 43 | | `Amber + IQ` (default) | 6 boxes for primary colors, these labels, and I-Q lines will be displayed in amber. | 44 | | `Green` | 6 boxes for primary colors, these labels, and skin tone line will be displayed in green. | 45 | | `Green + IQ` | 6 boxes for primary colors, these labels, and I-Q lines will be displayed in green. | 46 | 47 | ### Skin tone color 48 | 49 | The color for the skin tone line. 50 | Default is `#CBAB99`. 51 | If you set black or white, skin tone line will be hidden. 52 | 53 | ### Color space 54 | 55 | Choice of color space; Auto, BT.601, or BT.709. 56 | If Auto, the color space is retrieved from the settings of OBS Studio. 57 | Coefficients for Cr and Cb, graticule, and skin tone line will be changed. 58 | Default is Auto. 59 | 60 | ### Bypass 61 | 62 | If you check this, image after the scaling will be displayed. 63 | 64 | ## Output 65 | 66 | The output size is always `256x256` unless bypassed. 67 | -------------------------------------------------------------------------------- /doc/waveform.md: -------------------------------------------------------------------------------- 1 | # Waveform 2 | 3 | ## Introduction 4 | 5 | Waveform shows population of red (R), green (G), blue (B) components for each row of the specified source 6 | for color correction especially *brightness* or *luminance* and *gain* of each components. 7 | 8 | X-axis corresponds to the X-axis of the source, ie row. 9 | Y-axis corresponds to the value of color component for each row. Bright color stays upper, dark color stays lower. 10 | The intensity shows the population of each color. 11 | 12 | ## Properties 13 | 14 | ### Source 15 | 16 | Selects one of Program, Main view, Preview, Scene, or Source. 17 | Default is Program. 18 | 19 | ### Scale 20 | 21 | Scale factor before calculating waveform. 22 | The width and height of the source will be scaled by this number. 23 | Main purpose is to shorten the rendering time. 24 | Larger value will degrade the accuracy and intensity. 25 | For example, if you change scale from `1` to `2`, you need to increase intensity from `1` to `2` to get the same intensity. 26 | Default is `2`, which means width and height are both scaled by half. Available range is an integer number beween `1` - `128`. 27 | 28 | ### Display 29 | 30 | Choice of displaying mode; Overlay, Stack, or Parade. 31 | | Display | Description | 32 | |---------|-------------| 33 | | Overlay (default) | Each color components will be displayed on the same place by each color. | 34 | | Stack | R, G, B waveforms are displayed from the top to the bottom. | 35 | | Parade | R, G, B waveforms are displayed from the left to the right. | 36 | 37 | If setting components to YUV, CrYCb are displayed instead of RGB, respectively. 38 | If setting components to Luma only, this property is not effective. 39 | 40 | ### Components 41 | Choice of components to be displayed. 42 | | Components | Description | 43 | |------------|-------------| 44 | | RGB | R, G, B components are displayed. | 45 | | Luma | Luminance component (*Y*) is displayed. | 46 | | Chroma | Chrominance components (*Cr* and *Cb*) are displayed. | 47 | | YUV | Both luminance an chrominance components are displayed. | 48 | 49 | ### Color space 50 | 51 | Choice of color space; Auto, BT.601, or BT.709. 52 | If Auto, the color space is retrieved from the settings of OBS Studio. 53 | Coefficients for Luminance, Cr and Cb components will be changed. 54 | Default is Auto. This property is only available if the component property is Luma, Chroma, or YUV. 55 | 56 | ### Intensity 57 | 58 | Intensity of each pixel. 59 | Population for each color is multiplied by intensity and drawn on the scope. 60 | Larger value will increase the visibility of less population colors. 61 | Default is `51`. Available range is an integer number between `1` - `255`. 62 | 63 | ### Graticule 64 | 65 | Choice of graticule. 66 | 67 | | Choice | Description | 68 | |--------|-------------| 69 | | `None` | No graticule will be displayed. | 70 | | `0%, 100%` | 2 lines will be displayed at 0% and 100%. | 71 | | `0%, 50%, 100%` | 3 lines will be displayed. | 72 | | `each 25%` | 5 lines will be displayed. | 73 | | `each 20%` | 6 lines will be displayed. | 74 | | `each 10%` | 11 lines will be displayed. | 75 | 76 | ### Bypass 77 | 78 | If you check this, image after the scaling will be displayed. 79 | 80 | ## Output 81 | 82 | Width is scaled width of the source for Overlay and Stack display, 3-times of that for Parade, scaled height for bypass. 83 | Height is fixed 256 pixels for Overlay and Parade display, 768 pixels for Stack, scaled height for bypass. 84 | -------------------------------------------------------------------------------- /doc/zebra.md: -------------------------------------------------------------------------------- 1 | # Zebra 2 | 3 | ## Introduction 4 | 5 | Zebra indicates bright areas to help adjust exposure. 6 | This plugin provides lower threshold and upper threshold. 7 | 8 | ## Properties 9 | 10 | ### Source 11 | (Not available for Zebra Filter.) 12 | 13 | Selects one of Program, Main view, Preview, Scene, or Source. 14 | The default is Program. 15 | 16 | ### Threshold (lower, higher) 17 | 18 | Specify the level of the intensity where the zebra should be displayed. 19 | The default is `75%` and `100%`. 20 | The values are inclusive; the zebra will be also shown even if the intensity is same as the specified threshold. 21 | RGB color `#000000` corresponds to 0% `#FFFFFF` corresponds to 100%. 22 | 23 | ### Color space 24 | 25 | Choice of color space; Auto, BT.601, or BT.709. 26 | If Auto, the color space is retrieved from the settings of OBS Studio. 27 | Coefficients to convert from RGB to Luminance will be changed. 28 | Default is Auto. 29 | -------------------------------------------------------------------------------- /installer/installer-Windows.iss.in: -------------------------------------------------------------------------------- 1 | #define MyAppName "@PROJECT_NAME@" 2 | #define MyAppVersion "@PROJECT_VERSION@" 3 | #define MyAppPublisher "@PLUGIN_AUTHOR@" 4 | #define MyAppURL "@PLUGIN_URL@" 5 | 6 | [Setup] 7 | ; NOTE: The value of AppId uniquely identifies this application. 8 | ; Do not use the same AppId value in installers for other applications. 9 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 10 | AppId={{CD703FE5-1F2C-4837-BD3D-DD840D83C3E3} 11 | AppName={#MyAppName} 12 | AppVersion={#MyAppVersion} 13 | AppPublisher={#MyAppPublisher} 14 | AppPublisherURL={#MyAppURL} 15 | AppSupportURL={#MyAppURL} 16 | AppUpdatesURL={#MyAppURL} 17 | DefaultDirName={code:GetDirName} 18 | DefaultGroupName={#MyAppName} 19 | OutputBaseFilename={#MyAppName}-{#MyAppVersion}-Windows-Installer 20 | Compression=lzma 21 | SolidCompression=yes 22 | 23 | [Languages] 24 | Name: "english"; MessagesFile: "compiler:Default.isl" 25 | 26 | [Files] 27 | Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 28 | Source: "..\LICENSE"; Flags: dontcopy 29 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 30 | 31 | [Icons] 32 | Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}" 33 | Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" 34 | 35 | [Code] 36 | procedure InitializeWizard(); 37 | var 38 | GPLText: AnsiString; 39 | Page: TOutputMsgMemoWizardPage; 40 | begin 41 | ExtractTemporaryFile('LICENSE'); 42 | LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText); 43 | Page := CreateOutputMsgMemoPage(wpWelcome, 44 | 'License Information', 'Please review the license terms before installing {#MyAppName}', 45 | 'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.', 46 | String(GPLText) 47 | ); 48 | end; 49 | 50 | // credit where it's due : 51 | // following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45 52 | function GetDirName(Value: string): string; 53 | var 54 | InstallPath: string; 55 | begin 56 | // initialize default path, which will be returned when the following registry 57 | // key queries fail due to missing keys or for some different reason 58 | Result := '{pf}\obs-studio'; 59 | // query the first registry value; if this succeeds, return the obtained value 60 | if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then 61 | Result := InstallPath 62 | end; 63 | 64 | -------------------------------------------------------------------------------- /src-obsstudio/.clang-format: -------------------------------------------------------------------------------- 1 | # please use clang-format version 8 or later 2 | 3 | Standard: Cpp11 4 | AccessModifierOffset: -8 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | #AllowAllArgumentsOnNextLine: false # requires clang-format 9 12 | #AllowAllConstructorInitializersOnNextLine: false # requires clang-format 9 13 | AllowAllParametersOfDeclarationOnNextLine: false 14 | AllowShortBlocksOnASingleLine: false 15 | AllowShortCaseLabelsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: false 18 | #AllowShortLambdasOnASingleLine: Inline # requires clang-format 9 19 | AllowShortLoopsOnASingleLine: false 20 | AlwaysBreakAfterDefinitionReturnType: None 21 | AlwaysBreakAfterReturnType: None 22 | AlwaysBreakBeforeMultilineStrings: false 23 | AlwaysBreakTemplateDeclarations: false 24 | BinPackArguments: true 25 | BinPackParameters: true 26 | BraceWrapping: 27 | AfterClass: false 28 | AfterControlStatement: false 29 | AfterEnum: false 30 | AfterFunction: true 31 | AfterNamespace: false 32 | AfterObjCDeclaration: false 33 | AfterStruct: false 34 | AfterUnion: false 35 | AfterExternBlock: false 36 | BeforeCatch: false 37 | BeforeElse: false 38 | IndentBraces: false 39 | SplitEmptyFunction: true 40 | SplitEmptyRecord: true 41 | SplitEmptyNamespace: true 42 | BreakBeforeBinaryOperators: None 43 | BreakBeforeBraces: Custom 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializers: BeforeColon 46 | BreakStringLiterals: false # apparently unpredictable 47 | ColumnLimit: 80 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 8 51 | ContinuationIndentWidth: 8 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | FixNamespaceComments: false 56 | ForEachMacros: 57 | - 'json_object_foreach' 58 | - 'json_object_foreach_safe' 59 | - 'json_array_foreach' 60 | - 'HASH_ITER' 61 | IncludeBlocks: Preserve 62 | IndentCaseLabels: false 63 | IndentPPDirectives: None 64 | IndentWidth: 8 65 | IndentWrappedFunctionNames: false 66 | KeepEmptyLinesAtTheStartOfBlocks: true 67 | MaxEmptyLinesToKeep: 1 68 | NamespaceIndentation: None 69 | #ObjCBinPackProtocolList: Auto # requires clang-format 7 70 | ObjCBlockIndentWidth: 8 71 | ObjCSpaceAfterProperty: true 72 | ObjCSpaceBeforeProtocolList: true 73 | 74 | PenaltyBreakAssignment: 10 75 | PenaltyBreakBeforeFirstCallParameter: 30 76 | PenaltyBreakComment: 10 77 | PenaltyBreakFirstLessLess: 0 78 | PenaltyBreakString: 10 79 | PenaltyExcessCharacter: 100 80 | PenaltyReturnTypeOnItsOwnLine: 60 81 | 82 | PointerAlignment: Right 83 | ReflowComments: false 84 | SortIncludes: false 85 | SortUsingDeclarations: false 86 | SpaceAfterCStyleCast: false 87 | #SpaceAfterLogicalNot: false # requires clang-format 9 88 | SpaceAfterTemplateKeyword: false 89 | SpaceBeforeAssignmentOperators: true 90 | #SpaceBeforeCtorInitializerColon: true # requires clang-format 7 91 | #SpaceBeforeInheritanceColon: true # requires clang-format 7 92 | SpaceBeforeParens: ControlStatements 93 | #SpaceBeforeRangeBasedForLoopColon: true # requires clang-format 7 94 | SpaceInEmptyParentheses: false 95 | SpacesBeforeTrailingComments: 1 96 | SpacesInAngles: false 97 | SpacesInCStyleCastParentheses: false 98 | SpacesInContainerLiterals: false 99 | SpacesInParentheses: false 100 | SpacesInSquareBrackets: false 101 | #StatementMacros: # requires clang-format 8 102 | # - 'Q_OBJECT' 103 | TabWidth: 8 104 | #TypenameMacros: # requires clang-format 9 105 | # - 'DARRAY' 106 | UseTab: ForContinuationAndIndentation 107 | --- 108 | Language: ObjC 109 | -------------------------------------------------------------------------------- /src-obsstudio/combobox-ignorewheel.cpp: -------------------------------------------------------------------------------- 1 | #include "combobox-ignorewheel.hpp" 2 | 3 | ComboBoxIgnoreScroll::ComboBoxIgnoreScroll(QWidget *parent) : QComboBox(parent) 4 | { 5 | setFocusPolicy(Qt::StrongFocus); 6 | } 7 | 8 | void ComboBoxIgnoreScroll::wheelEvent(QWheelEvent *event) 9 | { 10 | if (!hasFocus()) 11 | event->ignore(); 12 | else 13 | QComboBox::wheelEvent(event); 14 | } 15 | -------------------------------------------------------------------------------- /src-obsstudio/combobox-ignorewheel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class ComboBoxIgnoreScroll : public QComboBox { 8 | Q_OBJECT 9 | 10 | public: 11 | ComboBoxIgnoreScroll(QWidget *parent = nullptr); 12 | 13 | protected: 14 | virtual void wheelEvent(QWheelEvent *event) override; 15 | }; 16 | -------------------------------------------------------------------------------- /src-obsstudio/double-slider.cpp: -------------------------------------------------------------------------------- 1 | #include "double-slider.hpp" 2 | 3 | #include 4 | 5 | DoubleSlider::DoubleSlider(QWidget *parent) : SliderIgnoreScroll(parent) 6 | { 7 | connect(this, SIGNAL(valueChanged(int)), this, 8 | SLOT(intValChanged(int))); 9 | } 10 | 11 | void DoubleSlider::setDoubleConstraints(double newMin, double newMax, 12 | double newStep, double val) 13 | { 14 | minVal = newMin; 15 | maxVal = newMax; 16 | minStep = newStep; 17 | 18 | double total = maxVal - minVal; 19 | int intMax = int(total / minStep); 20 | 21 | setMinimum(0); 22 | setMaximum(intMax); 23 | setSingleStep(1); 24 | setDoubleVal(val); 25 | } 26 | 27 | void DoubleSlider::intValChanged(int val) 28 | { 29 | emit doubleValChanged((minVal / minStep + val) * minStep); 30 | } 31 | 32 | void DoubleSlider::setDoubleVal(double val) 33 | { 34 | setValue(lround((val - minVal) / minStep)); 35 | } 36 | -------------------------------------------------------------------------------- /src-obsstudio/double-slider.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "slider-ignorewheel.hpp" 5 | 6 | class DoubleSlider : public SliderIgnoreScroll { 7 | Q_OBJECT 8 | 9 | double minVal, maxVal, minStep; 10 | 11 | public: 12 | DoubleSlider(QWidget *parent = nullptr); 13 | 14 | void setDoubleConstraints(double newMin, double newMax, double newStep, 15 | double val); 16 | 17 | signals: 18 | void doubleValChanged(double val); 19 | 20 | public slots: 21 | void intValChanged(int val); 22 | void setDoubleVal(double val); 23 | }; 24 | -------------------------------------------------------------------------------- /src-obsstudio/properties-view.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vertical-scroll-area.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | class QFormLayout; 9 | class OBSPropertiesView; 10 | class QLabel; 11 | 12 | typedef obs_properties_t *(*PropertiesReloadCallback)(void *obj); 13 | typedef void (*PropertiesUpdateCallback)(void *obj, obs_data_t *settings); 14 | 15 | /* ------------------------------------------------------------------------- */ 16 | 17 | class DockProp_WidgetInfo : public QObject { 18 | Q_OBJECT 19 | 20 | friend class OBSPropertiesView; 21 | 22 | private: 23 | OBSPropertiesView *view; 24 | obs_property_t *property; 25 | QWidget *widget; 26 | 27 | void BoolChanged(const char *setting); 28 | void IntChanged(const char *setting); 29 | void FloatChanged(const char *setting); 30 | void TextChanged(const char *setting); 31 | void ListChanged(const char *setting); 32 | bool ColorChanged(const char *setting); 33 | void GroupChanged(const char *setting); 34 | void EditableListChanged(); 35 | void ButtonClicked(); 36 | 37 | void TogglePasswordText(bool checked); 38 | 39 | public: 40 | inline DockProp_WidgetInfo(OBSPropertiesView *view_, 41 | obs_property_t *prop, QWidget *widget_) 42 | : view(view_), property(prop), widget(widget_) 43 | { 44 | } 45 | 46 | public slots: 47 | 48 | void ControlChanged(); 49 | 50 | /* editable list */ 51 | void EditListAdd(); 52 | void EditListAddText(); 53 | void EditListRemove(); 54 | void EditListEdit(); 55 | void EditListUp(); 56 | void EditListDown(); 57 | void EditListReordered(const QModelIndex &parent, int start, int end, 58 | const QModelIndex &destination, int row); 59 | }; 60 | 61 | /* ------------------------------------------------------------------------- */ 62 | 63 | class OBSPropertiesView : public VScrollArea { 64 | Q_OBJECT 65 | 66 | friend class DockProp_WidgetInfo; 67 | 68 | using properties_delete_t = decltype(&obs_properties_destroy); 69 | using properties_t = 70 | std::unique_ptr; 71 | 72 | private: 73 | QWidget *widget = nullptr; 74 | properties_t properties; 75 | OBSData settings; 76 | void *obj = nullptr; 77 | std::string type; 78 | PropertiesReloadCallback reloadCallback; 79 | PropertiesUpdateCallback callback = nullptr; 80 | int minSize; 81 | std::vector> children; 82 | std::string lastFocused; 83 | QWidget *lastWidget = nullptr; 84 | bool deferUpdate; 85 | 86 | QWidget *NewWidget(obs_property_t *prop, QWidget *widget, 87 | const char *signal); 88 | 89 | QWidget *AddCheckbox(obs_property_t *prop); 90 | QWidget *AddText(obs_property_t *prop, QFormLayout *layout, 91 | QLabel *&label); 92 | void AddInt(obs_property_t *prop, QFormLayout *layout, QLabel **label); 93 | void AddFloat(obs_property_t *prop, QFormLayout *layout, 94 | QLabel **label); 95 | QWidget *AddList(obs_property_t *prop, bool &warning); 96 | void AddEditableList(obs_property_t *prop, QFormLayout *layout, 97 | QLabel *&label); 98 | QWidget *AddButton(obs_property_t *prop); 99 | void AddColor(obs_property_t *prop, QFormLayout *layout, 100 | QLabel *&label); 101 | 102 | void AddGroup(obs_property_t *prop, QFormLayout *layout); 103 | 104 | void AddProperty(obs_property_t *property, QFormLayout *layout); 105 | 106 | void resizeEvent(QResizeEvent *event) override; 107 | 108 | void GetScrollPos(int &h, int &v); 109 | void SetScrollPos(int h, int v); 110 | 111 | public slots: 112 | void ReloadProperties(); 113 | void RefreshProperties(); 114 | void SignalChanged(); 115 | 116 | signals: 117 | void PropertiesResized(); 118 | void Changed(); 119 | void PropertiesRefreshed(); 120 | 121 | public: 122 | OBSPropertiesView(OBSData settings, void *obj, 123 | PropertiesReloadCallback reloadCallback, 124 | PropertiesUpdateCallback callback, int minSize = 0); 125 | OBSPropertiesView(OBSData settings, const char *type, 126 | PropertiesReloadCallback reloadCallback, 127 | int minSize = 0); 128 | 129 | inline obs_data_t *GetSettings() const { return settings; } 130 | 131 | inline void UpdateSettings() { callback(obj, settings); } 132 | inline bool DeferUpdate() const { return deferUpdate; } 133 | }; 134 | -------------------------------------------------------------------------------- /src-obsstudio/qt-wrappers.cpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2013 by Hugh Bailey 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 2 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, see . 16 | ******************************************************************************/ 17 | 18 | #include "qt-wrappers.hpp" 19 | #define QTStr(x) (QString(x)) 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | bool LineEditCanceled(QEvent *event) 26 | { 27 | if (event->type() == QEvent::KeyPress) { 28 | QKeyEvent *keyEvent = reinterpret_cast(event); 29 | return keyEvent->key() == Qt::Key_Escape; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | bool LineEditChanged(QEvent *event) 36 | { 37 | if (event->type() == QEvent::KeyPress) { 38 | QKeyEvent *keyEvent = reinterpret_cast(event); 39 | 40 | switch (keyEvent->key()) { 41 | case Qt::Key_Tab: 42 | case Qt::Key_Backtab: 43 | case Qt::Key_Enter: 44 | case Qt::Key_Return: 45 | return true; 46 | } 47 | } else if (event->type() == QEvent::FocusOut) { 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | -------------------------------------------------------------------------------- /src-obsstudio/qt-wrappers.hpp: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2013 by Hugh Bailey 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 2 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, see . 16 | ******************************************************************************/ 17 | 18 | #pragma once 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #define QT_UTF8(str) QString::fromUtf8(str) 31 | #define QT_TO_UTF8(str) str.toUtf8().constData() 32 | 33 | class QWidget; 34 | class QLayout; 35 | class QString; 36 | struct gs_window; 37 | 38 | static inline Qt::ConnectionType WaitConnection() 39 | { 40 | return QThread::currentThread() == qApp->thread() 41 | ? Qt::DirectConnection 42 | : Qt::BlockingQueuedConnection; 43 | } 44 | 45 | bool LineEditCanceled(QEvent *event); 46 | bool LineEditChanged(QEvent *event); 47 | -------------------------------------------------------------------------------- /src-obsstudio/slider-ignorewheel.cpp: -------------------------------------------------------------------------------- 1 | #include "slider-ignorewheel.hpp" 2 | 3 | SliderIgnoreScroll::SliderIgnoreScroll(QWidget *parent) : QSlider(parent) 4 | { 5 | setFocusPolicy(Qt::StrongFocus); 6 | } 7 | 8 | SliderIgnoreScroll::SliderIgnoreScroll(Qt::Orientation orientation, 9 | QWidget *parent) 10 | : QSlider(parent) 11 | { 12 | setFocusPolicy(Qt::StrongFocus); 13 | setOrientation(orientation); 14 | } 15 | 16 | void SliderIgnoreScroll::wheelEvent(QWheelEvent *event) 17 | { 18 | if (!hasFocus()) 19 | event->ignore(); 20 | else 21 | QSlider::wheelEvent(event); 22 | } 23 | -------------------------------------------------------------------------------- /src-obsstudio/slider-ignorewheel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class SliderIgnoreScroll : public QSlider { 8 | Q_OBJECT 9 | 10 | public: 11 | SliderIgnoreScroll(QWidget *parent = nullptr); 12 | SliderIgnoreScroll(Qt::Orientation orientation, 13 | QWidget *parent = nullptr); 14 | 15 | protected: 16 | virtual void wheelEvent(QWheelEvent *event) override; 17 | }; 18 | -------------------------------------------------------------------------------- /src-obsstudio/spinbox-ignorewheel.cpp: -------------------------------------------------------------------------------- 1 | #include "spinbox-ignorewheel.hpp" 2 | 3 | SpinBoxIgnoreScroll::SpinBoxIgnoreScroll(QWidget *parent) : QSpinBox(parent) 4 | { 5 | setFocusPolicy(Qt::StrongFocus); 6 | } 7 | 8 | void SpinBoxIgnoreScroll::wheelEvent(QWheelEvent *event) 9 | { 10 | if (!hasFocus()) 11 | event->ignore(); 12 | else 13 | QSpinBox::wheelEvent(event); 14 | } 15 | -------------------------------------------------------------------------------- /src-obsstudio/spinbox-ignorewheel.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class SpinBoxIgnoreScroll : public QSpinBox { 8 | Q_OBJECT 9 | 10 | public: 11 | SpinBoxIgnoreScroll(QWidget *parent = nullptr); 12 | 13 | protected: 14 | virtual void wheelEvent(QWheelEvent *event) override; 15 | }; 16 | -------------------------------------------------------------------------------- /src-obsstudio/vertical-scroll-area.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "vertical-scroll-area.hpp" 3 | 4 | void VScrollArea::resizeEvent(QResizeEvent *event) 5 | { 6 | if (!!widget()) 7 | widget()->setMaximumWidth(event->size().width()); 8 | 9 | QScrollArea::resizeEvent(event); 10 | } 11 | -------------------------------------------------------------------------------- /src-obsstudio/vertical-scroll-area.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QResizeEvent; 6 | 7 | class VScrollArea : public QScrollArea { 8 | Q_OBJECT 9 | 10 | public: 11 | inline VScrollArea(QWidget *parent = nullptr) : QScrollArea(parent) 12 | { 13 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 14 | } 15 | 16 | protected: 17 | virtual void resizeEvent(QResizeEvent *event) override; 18 | }; 19 | -------------------------------------------------------------------------------- /src/ScopeWidgetInteractiveEventFilter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "plugin-macros.generated.h" 4 | #include "ScopeWidgetInteractiveEventFilter.hpp" 5 | 6 | bool ScopeWidgetInteractiveEventFilter::eventFilter(QObject *, QEvent *event) 7 | { 8 | switch (event->type()) { 9 | case QEvent::MouseButtonPress: 10 | case QEvent::MouseButtonRelease: 11 | case QEvent::MouseButtonDblClick: 12 | return parent->HandleMouseClickEvent(static_cast(event)); 13 | 14 | case QEvent::MouseMove: 15 | return parent->HandleMouseMoveEvent(static_cast(event)); 16 | 17 | case QEvent::Wheel: 18 | return parent->HandleMouseWheelEvent(static_cast(event)); 19 | 20 | case QEvent::KeyPress: 21 | case QEvent::KeyRelease: 22 | return parent->HandleKeyEvent(static_cast(event)); 23 | 24 | default: 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ScopeWidgetInteractiveEventFilter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "scope-widget.hpp" 5 | 6 | class ScopeWidgetInteractiveEventFilter : public QObject { 7 | Q_OBJECT 8 | ScopeWidget *parent; 9 | 10 | public: 11 | ScopeWidgetInteractiveEventFilter(ScopeWidget *p) : QObject(p), parent(p) {} 12 | 13 | protected: 14 | bool eventFilter(QObject *obj, QEvent *event) override; 15 | }; 16 | -------------------------------------------------------------------------------- /src/SurfaceEventFilter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "plugin-macros.generated.h" 3 | #include "SurfaceEventFilter.hpp" 4 | 5 | bool SurfaceEventFilter::eventFilter(QObject *obj, QEvent *event) 6 | { 7 | bool result = QObject::eventFilter(obj, event); 8 | QPlatformSurfaceEvent *surfaceEvent; 9 | 10 | switch (event->type()) { 11 | case QEvent::PlatformSurface: 12 | surfaceEvent = static_cast(event); 13 | 14 | switch (surfaceEvent->surfaceEventType()) { 15 | case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: 16 | w->DestroyDisplay(); 17 | break; 18 | default: 19 | break; 20 | } 21 | break; 22 | default: 23 | break; 24 | } 25 | 26 | return result; 27 | } 28 | -------------------------------------------------------------------------------- /src/SurfaceEventFilter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "scope-widget.hpp" 5 | 6 | class SurfaceEventFilter : public QObject { 7 | Q_OBJECT 8 | ScopeWidget *w; 9 | 10 | public: 11 | SurfaceEventFilter(ScopeWidget *w_) : QObject(static_cast(w_)), w(w_) {} 12 | 13 | protected: 14 | bool eventFilter(QObject *obj, QEvent *event) override; 15 | }; 16 | -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | static inline bool is_program_name(const char *name) 10 | { 11 | return name && name[0] == 0; 12 | } 13 | 14 | static inline bool is_mainview_name(const char *name) 15 | { 16 | return name && name[0] == 0x01 && name[1] == 0; 17 | } 18 | 19 | static inline bool is_preview_name(const char *name) 20 | { 21 | return name && name[0] == 0x10 && name[1] == 0; 22 | } 23 | 24 | struct cm_surface_data 25 | { 26 | uint8_t *rgb_data, *yuv_data; 27 | uint32_t linesize, width, height; 28 | int colorspace; 29 | gs_texture_t *tex; // for bypass mode 30 | }; 31 | 32 | typedef void (*cm_surface_cb_t)(void *data, struct cm_surface_data *surface_data); 33 | 34 | struct cm_surface_queue_item 35 | { 36 | gs_texrender_t *texrender; 37 | gs_stagesurf_t *stagesurface; 38 | uint32_t width, height, sheight; 39 | uint32_t flags; // RGB or YUV 40 | int colorspace; 41 | 42 | cm_surface_cb_t cb; 43 | void *cb_data; 44 | }; 45 | 46 | #define CM_SURFACE_QUEUE_SIZE 3 47 | 48 | struct cm_source 49 | { 50 | obs_source_t *self; 51 | 52 | // graphics 53 | struct cm_surface_queue_item queue[CM_SURFACE_QUEUE_SIZE]; 54 | volatile int i_write_queue, i_staging_queue; 55 | volatile int i_read_queue; 56 | int i_bypass_queue; 57 | gs_texrender_t *texrender; 58 | uint32_t texrender_width, texrender_height; 59 | gs_effect_t *effect; 60 | bool rendered; 61 | int x0, x1, y0, y1; // for ROI 62 | 63 | // threading 64 | pthread_t pipeline_thread; 65 | pthread_mutex_t pipeline_mutex; 66 | pthread_cond_t pipeline_cond; 67 | volatile bool pipeline_thread_running; 68 | volatile bool request_exit; 69 | 70 | // upper layer 71 | cm_surface_cb_t callback; 72 | void *callback_data; 73 | 74 | bool enumerating; // not thread safe but I have no other idea. 75 | 76 | // target 77 | pthread_mutex_t target_update_mutex; 78 | obs_weak_source_t *weak_target; 79 | obs_source_t *roi_src; 80 | struct roi_source *roi; 81 | char *target_name; 82 | 83 | // properties 84 | int target_scale; 85 | int colorspace; // get from ovi if auto 86 | uint32_t flags; 87 | bool bypass; 88 | }; 89 | 90 | #define CM_FLAG_CONVERT_RGB 1 91 | #define CM_FLAG_CONVERT_YUV 2 92 | #define CM_FLAG_RAW_TEXTURE 4 93 | #define CM_FLAG_ROI 8 94 | 95 | void cm_create(struct cm_source *src, obs_data_t *settings, obs_source_t *source); 96 | void cm_destroy(struct cm_source *src); 97 | void cm_update(struct cm_source *src, obs_data_t *settings); 98 | void cm_enum_sources(void *data, obs_source_enum_proc_t enum_callback, void *param); 99 | void cm_get_properties(struct cm_source *src, obs_properties_t *props); 100 | void cm_render_target(struct cm_source *src); 101 | bool cm_stagesurface_map(struct cm_source *src, uint8_t **video_data, uint32_t *video_linesize); 102 | void cm_stagesurface_unmap(struct cm_source *src); 103 | void cm_bypass_render(struct cm_source *src); 104 | void cm_tick(void *data, float unused); 105 | 106 | void cm_request(struct cm_source *src, cm_surface_cb_t callback, void *data); 107 | 108 | uint32_t cm_bypass_get_width(struct cm_source *src); 109 | uint32_t cm_bypass_get_height(struct cm_source *src); 110 | gs_texture_t *cm_bypass_get_texture(struct cm_source *src); 111 | static inline bool cm_is_roi(const struct cm_source *src) 112 | { 113 | return src->roi_src && src->roi; 114 | } 115 | 116 | bool is_roi_source_name(const char *name); 117 | 118 | #ifdef __cplusplus 119 | } 120 | #endif 121 | -------------------------------------------------------------------------------- /src/focuspeaking.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "plugin-macros.generated.h" 7 | #include "obs-convenience.h" 8 | #include "common.h" 9 | #include "util.h" 10 | 11 | #ifdef ENABLE_PROFILE 12 | #define PROFILE_START(x) profile_start(x) 13 | #define PROFILE_END(x) profile_end(x) 14 | static const char *prof_render_name = "focuspeaking_render"; 15 | #else // ENABLE_PROFILE 16 | #define PROFILE_START(x) 17 | #define PROFILE_END(x) 18 | #endif // ! ENABLE_PROFILE 19 | 20 | #define DEFAULT_PEAKING_COLOR 0xFFFF5400 // ABGR 21 | #define DEFAULT_PEAKING_THRESHOLD 0.05 22 | 23 | /* common structure for source and filter */ 24 | struct fp_source 25 | { 26 | gs_effect_t *effect; 27 | 28 | /* properties */ 29 | uint32_t peaking_color; 30 | float peaking_threshold; 31 | bool actual_size; 32 | }; 33 | 34 | struct fps_source 35 | { 36 | struct cm_source cm; 37 | struct fp_source fp; 38 | }; 39 | 40 | struct fpf_source 41 | { 42 | struct fp_source fp; 43 | obs_source_t *context; 44 | }; 45 | 46 | static const char *fp_get_name(void *unused) 47 | { 48 | UNUSED_PARAMETER(unused); 49 | return obs_module_text("FocusPeaking.Name"); 50 | } 51 | 52 | static void fps_update(void *, obs_data_t *); 53 | static void fpf_update(void *, obs_data_t *); 54 | 55 | static void fp_init(struct fp_source *src) 56 | { 57 | obs_enter_graphics(); 58 | src->effect = create_effect_from_module_file("focuspeaking.effect"); 59 | obs_leave_graphics(); 60 | } 61 | 62 | static void *fps_create(obs_data_t *settings, obs_source_t *source) 63 | { 64 | struct fps_source *src = bzalloc(sizeof(struct fps_source)); 65 | 66 | src->cm.flags = CM_FLAG_RAW_TEXTURE; 67 | cm_create(&src->cm, settings, source); 68 | fp_init(&src->fp); 69 | 70 | fps_update(src, settings); 71 | 72 | return src; 73 | } 74 | 75 | static void *fpf_create(obs_data_t *settings, obs_source_t *source) 76 | { 77 | struct fpf_source *src = bzalloc(sizeof(struct fpf_source)); 78 | 79 | fp_init(&src->fp); 80 | src->context = source; 81 | 82 | fpf_update(src, settings); 83 | 84 | return src; 85 | } 86 | 87 | static void fp_destroy(struct fp_source *src) 88 | { 89 | UNUSED_PARAMETER(src); 90 | } 91 | 92 | static void fps_destroy(void *data) 93 | { 94 | struct fps_source *src = data; 95 | 96 | cm_destroy(&src->cm); 97 | fp_destroy(&src->fp); 98 | bfree(src); 99 | } 100 | 101 | static void fpf_destroy(void *data) 102 | { 103 | struct fpf_source *src = data; 104 | fp_destroy(&src->fp); 105 | bfree(src); 106 | } 107 | 108 | static void fp_update(struct fp_source *src, obs_data_t *settings) 109 | { 110 | src->peaking_color = (uint32_t)obs_data_get_int(settings, "peaking_color"); 111 | src->peaking_threshold = (float)obs_data_get_double(settings, "peaking_threshold"); 112 | src->actual_size = obs_data_get_bool(settings, "actual_size"); 113 | } 114 | 115 | static void fps_update(void *data, obs_data_t *settings) 116 | { 117 | struct fps_source *src = data; 118 | cm_update(&src->cm, settings); 119 | fp_update(&src->fp, settings); 120 | } 121 | 122 | static void fpf_update(void *data, obs_data_t *settings) 123 | { 124 | struct fpf_source *src = data; 125 | fp_update(&src->fp, settings); 126 | } 127 | 128 | static void fp_get_defaults(obs_data_t *settings) 129 | { 130 | obs_data_set_default_int(settings, "peaking_color", DEFAULT_PEAKING_COLOR); 131 | obs_data_set_default_double(settings, "peaking_threshold", DEFAULT_PEAKING_THRESHOLD); 132 | } 133 | 134 | static void fp_get_properties(obs_properties_t *props) 135 | { 136 | obs_properties_add_color(props, "peaking_color", obs_module_text("FocusPeaking.Prop.PeakingColor")); 137 | obs_properties_add_float(props, "peaking_threshold", obs_module_text("FocusPeaking.Prop.PeakingThreshold"), 138 | 0.001, 0.1, 0.001); 139 | obs_properties_add_bool(props, "actual_size", obs_module_text("FocusPeaking.Prop.ActualSize")); 140 | } 141 | 142 | static obs_properties_t *fps_get_properties(void *data) 143 | { 144 | struct fps_source *src = data; 145 | obs_properties_t *props; 146 | props = obs_properties_create(); 147 | 148 | cm_get_properties(&src->cm, props); 149 | fp_get_properties(props); 150 | 151 | return props; 152 | } 153 | 154 | static obs_properties_t *fpf_get_properties(void *data) 155 | { 156 | UNUSED_PARAMETER(data); 157 | obs_properties_t *props; 158 | props = obs_properties_create(); 159 | 160 | fp_get_properties(props); 161 | 162 | return props; 163 | } 164 | 165 | static uint32_t fps_get_width(void *data) 166 | { 167 | struct fps_source *src = data; 168 | return cm_bypass_get_width(&src->cm); 169 | } 170 | 171 | static uint32_t fps_get_height(void *data) 172 | { 173 | struct fps_source *src = data; 174 | return cm_bypass_get_height(&src->cm); 175 | } 176 | 177 | static uint32_t fpf_get_width(void *data) 178 | { 179 | struct fpf_source *src = data; 180 | obs_source_t *target = obs_filter_get_target(src->context); 181 | return obs_source_get_base_width(target); 182 | } 183 | 184 | static uint32_t fpf_get_height(void *data) 185 | { 186 | struct fpf_source *src = data; 187 | obs_source_t *target = obs_filter_get_target(src->context); 188 | return obs_source_get_base_height(target); 189 | } 190 | 191 | static const char *draw_name() 192 | { 193 | return "DrawFocusPeaking"; 194 | } 195 | 196 | static inline uint32_t swap_rb(uint32_t c) 197 | { 198 | uint32_t r = c & 0xFF; 199 | uint32_t b = (c >> 16) & 0xFF; 200 | return (c & 0xFF00FF00) | (r << 16) | b; 201 | } 202 | 203 | static void set_actual_size_matrix(uint32_t cx, uint32_t cy) 204 | { 205 | struct gs_rect rect; 206 | gs_get_viewport(&rect); 207 | 208 | float xcoe = (float)cx / (float)rect.cx; 209 | float ycoe = (float)cy / (float)rect.cy; 210 | float xoff = (float)(rect.cx - (int)cx) * 0.5f * xcoe; 211 | float yoff = (float)(rect.cy - (int)cy) * 0.5f * ycoe; 212 | 213 | struct matrix4 tr = { 214 | {.ptr = {xcoe, 0.0f, 0.0f, 0.0f}}, 215 | {.ptr = {0.0f, ycoe, 0.0f, 0.0f}}, 216 | {.ptr = {0.0f, 0.0f, 1.0f, 0.0f}}, 217 | {.ptr = {xoff, yoff, 0.0f, 1.0f}}, 218 | }; 219 | gs_matrix_mul(&tr); 220 | } 221 | 222 | static void set_effect_params(struct fp_source *src, uint32_t cx, uint32_t cy) 223 | { 224 | gs_effect_t *e = src->effect; 225 | 226 | const struct vec2 dxy = { 227 | .x = 1.0f / cx, 228 | .y = 1.0f / cy, 229 | }; 230 | 231 | gs_effect_set_vec2(gs_effect_get_param_by_name(e, "dxy"), &dxy); 232 | gs_effect_set_color(gs_effect_get_param_by_name(e, "peaking_color"), swap_rb(src->peaking_color)); 233 | gs_effect_set_float(gs_effect_get_param_by_name(e, "peaking_threshold"), src->peaking_threshold); 234 | } 235 | 236 | static void fps_render(void *data, gs_effect_t *effect) 237 | { 238 | UNUSED_PARAMETER(effect); 239 | struct fps_source *src = data; 240 | if (src->cm.bypass) { 241 | cm_bypass_render(&src->cm); 242 | return; 243 | } 244 | 245 | cm_render_target(&src->cm); 246 | 247 | PROFILE_START(prof_render_name); 248 | 249 | gs_texture_t *tex = cm_bypass_get_texture(&src->cm); 250 | gs_effect_t *e = src->fp.effect; 251 | if (e && tex) { 252 | uint32_t cx = cm_bypass_get_width(&src->cm); 253 | uint32_t cy = cm_bypass_get_height(&src->cm); 254 | 255 | if (src->fp.actual_size) { 256 | gs_matrix_push(); 257 | set_actual_size_matrix(cx, cy); 258 | } 259 | 260 | gs_effect_set_texture(gs_effect_get_param_by_name(e, "image"), tex); 261 | set_effect_params(&src->fp, cx, cy); 262 | const char *draw = draw_name(); 263 | while (gs_effect_loop(e, draw)) 264 | gs_draw_sprite_subregion(tex, 0, 0, 0, cx, cy); 265 | 266 | if (src->fp.actual_size) 267 | gs_matrix_pop(); 268 | } 269 | 270 | PROFILE_END(prof_render_name); 271 | } 272 | 273 | static void fpf_render(void *data, gs_effect_t *effect) 274 | { 275 | UNUSED_PARAMETER(effect); 276 | struct fpf_source *src = data; 277 | gs_effect_t *e = src->fp.effect; 278 | 279 | if (!e) 280 | return; 281 | 282 | if (!obs_source_process_filter_begin(src->context, GS_RGBA, OBS_ALLOW_DIRECT_RENDERING)) 283 | return; 284 | 285 | obs_source_t *target = obs_filter_get_target(src->context); 286 | if (!target) 287 | return; 288 | 289 | uint32_t cx = obs_source_get_base_width(target); 290 | uint32_t cy = obs_source_get_base_height(target); 291 | 292 | set_effect_params(&src->fp, cx, cy); 293 | 294 | gs_blend_state_push(); 295 | gs_reset_blend_state(); 296 | 297 | if (src->fp.actual_size) { 298 | gs_matrix_push(); 299 | set_actual_size_matrix(cx, cy); 300 | } 301 | 302 | const char *draw = draw_name(); 303 | obs_source_process_filter_tech_end(src->context, e, 0, 0, draw); 304 | 305 | if (src->fp.actual_size) 306 | gs_matrix_pop(); 307 | 308 | gs_blend_state_pop(); 309 | } 310 | 311 | const struct obs_source_info colormonitor_focuspeaking = { 312 | .id = ID_PREFIX "focuspeaking_source", 313 | .type = OBS_SOURCE_TYPE_INPUT, 314 | .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW, 315 | .get_name = fp_get_name, 316 | .create = fps_create, 317 | .destroy = fps_destroy, 318 | .update = fps_update, 319 | .get_defaults = fp_get_defaults, 320 | .get_properties = fps_get_properties, 321 | .get_width = fps_get_width, 322 | .get_height = fps_get_height, 323 | .enum_active_sources = cm_enum_sources, 324 | .video_render = fps_render, 325 | .video_tick = cm_tick, 326 | }; 327 | 328 | const struct obs_source_info colormonitor_focuspeaking_filter = { 329 | .id = ID_PREFIX "focuspeaking_filter", 330 | .type = OBS_SOURCE_TYPE_FILTER, 331 | .output_flags = OBS_SOURCE_VIDEO, 332 | .get_name = fp_get_name, 333 | .create = fpf_create, 334 | .destroy = fpf_destroy, 335 | .update = fpf_update, 336 | .get_defaults = fp_get_defaults, 337 | .get_properties = fpf_get_properties, 338 | .get_width = fpf_get_width, 339 | .get_height = fpf_get_height, 340 | .video_render = fpf_render, 341 | }; 342 | -------------------------------------------------------------------------------- /src/obs-convenience.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2014 by Nibbles 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 2 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, see . 16 | ******************************************************************************/ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "obs-convenience.h" 23 | 24 | gs_vertbuffer_t *create_uv_vbuffer(uint32_t num_verts, bool add_color) 25 | { 26 | obs_enter_graphics(); 27 | 28 | gs_vertbuffer_t *tmp = NULL; 29 | struct gs_vb_data *vrect = NULL; 30 | 31 | vrect = gs_vbdata_create(); 32 | vrect->num = num_verts; 33 | vrect->points = (struct vec3 *)bmalloc(sizeof(struct vec3) * num_verts); 34 | vrect->num_tex = 1; 35 | vrect->tvarray = (struct gs_tvertarray *)bmalloc(sizeof(struct gs_tvertarray)); 36 | vrect->tvarray[0].width = 2; 37 | vrect->tvarray[0].array = bmalloc(sizeof(struct vec2) * num_verts); 38 | if (add_color) 39 | vrect->colors = (uint32_t *)bmalloc(sizeof(uint32_t) * num_verts); 40 | 41 | memset(vrect->points, 0, sizeof(struct vec3) * num_verts); 42 | memset(vrect->tvarray[0].array, 0, sizeof(struct vec2) * num_verts); 43 | if (add_color) 44 | memset(vrect->colors, 0, sizeof(uint32_t) * num_verts); 45 | 46 | tmp = gs_vertexbuffer_create(vrect, GS_DYNAMIC); 47 | 48 | if (tmp == NULL) { 49 | blog(LOG_WARNING, "Couldn't create UV vertex buffer."); 50 | } 51 | 52 | obs_leave_graphics(); 53 | 54 | return tmp; 55 | } 56 | 57 | void draw_uv_vbuffer(gs_vertbuffer_t *vbuf, gs_texture_t *tex, gs_effect_t *effect, const char *tech_name, 58 | uint32_t num_verts) 59 | { 60 | gs_texture_t *texture = tex; 61 | gs_technique_t *tech = gs_effect_get_technique(effect, tech_name); 62 | gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image"); 63 | size_t passes; 64 | 65 | if (vbuf == NULL || tex == NULL) 66 | return; 67 | 68 | gs_vertexbuffer_flush(vbuf); 69 | gs_load_vertexbuffer(vbuf); 70 | gs_load_indexbuffer(NULL); 71 | 72 | passes = gs_technique_begin(tech); 73 | 74 | for (size_t i = 0; i < passes; i++) { 75 | if (gs_technique_begin_pass(tech, i)) { 76 | gs_effect_set_texture(image, texture); 77 | 78 | gs_draw(GS_TRIS, 0, num_verts); 79 | 80 | gs_technique_end_pass(tech); 81 | } 82 | } 83 | 84 | gs_technique_end(tech); 85 | } 86 | -------------------------------------------------------------------------------- /src/obs-convenience.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Copyright (C) 2014 by Nibbles 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 2 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, see . 16 | ******************************************************************************/ 17 | 18 | #pragma once 19 | 20 | #include 21 | 22 | gs_vertbuffer_t *create_uv_vbuffer(uint32_t num_verts, bool add_color); 23 | void draw_uv_vbuffer(gs_vertbuffer_t *vbuf, gs_texture_t *tex, gs_effect_t *effect, const char *tech_name, 24 | uint32_t num_verts); 25 | 26 | #define set_v3_rect(a, x, y, w, h) \ 27 | vec3_set(a, x, y, 0.0f); \ 28 | vec3_set(a + 1, x + w, y, 0.0f); \ 29 | vec3_set(a + 2, x, y + h, 0.0f); \ 30 | vec3_set(a + 3, x, y + h, 0.0f); \ 31 | vec3_set(a + 4, x + w, y, 0.0f); \ 32 | vec3_set(a + 5, x + w, y + h, 0.0f); 33 | 34 | #define set_v2_uv(a, u, v, u2, v2) \ 35 | vec2_set(a, u, v); \ 36 | vec2_set(a + 1, u2, v); \ 37 | vec2_set(a + 2, u, v2); \ 38 | vec2_set(a + 3, u, v2); \ 39 | vec2_set(a + 4, u2, v); \ 40 | vec2_set(a + 5, u2, v2); 41 | 42 | #define set_rect_colors2(a, c1, c2) \ 43 | uint32_t *b = a; \ 44 | b[0] = b[1] = b[4] = c1; \ 45 | b[2] = b[3] = b[5] = c2; 46 | -------------------------------------------------------------------------------- /src/obsgui-helper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #if !defined(_WIN32) && !defined(__APPLE__) // if Linux 4 | #include 5 | #include 6 | #include 7 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 8 | #endif 9 | 10 | // copied from obs-studio/frontend/widgets/OBSQTDisplay.cpp 11 | static inline bool QTToGSWindow(QWindow *window, gs_window &gswindow) 12 | { 13 | bool success = true; 14 | 15 | #ifdef _WIN32 16 | gswindow.hwnd = (HWND)window->winId(); 17 | #elif __APPLE__ 18 | gswindow.view = (id)window->winId(); 19 | #else 20 | switch (obs_get_nix_platform()) { 21 | case OBS_NIX_PLATFORM_X11_EGL: 22 | gswindow.id = window->winId(); 23 | gswindow.display = obs_get_nix_platform_display(); 24 | break; 25 | #ifdef ENABLE_WAYLAND 26 | case OBS_NIX_PLATFORM_WAYLAND: { 27 | QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); 28 | gswindow.display = native->nativeResourceForWindow("surface", window); 29 | success = gswindow.display != nullptr; 30 | break; 31 | } 32 | #endif 33 | default: 34 | success = false; 35 | break; 36 | } 37 | #endif 38 | return success; 39 | } 40 | 41 | // copied from obs-studio/UI/display-helpers.hpp 42 | static inline QSize GetPixelSize(QWidget *widget) 43 | { 44 | return widget->size() * widget->devicePixelRatioF(); 45 | } 46 | -------------------------------------------------------------------------------- /src/plugin-macros.h.in: -------------------------------------------------------------------------------- 1 | /* 2 | OBS Color Monitor 3 | Copyright (C) 2021 Norihiro Kamae 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (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 General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program. If not, see 17 | */ 18 | 19 | #ifndef PLUGINNAME_H 20 | #define PLUGINNAME_H 21 | 22 | #define PLUGIN_NAME "@PROJECT_NAME@" 23 | #define PLUGIN_VERSION "@PROJECT_VERSION@" 24 | #cmakedefine ENABLE_PROFILE 25 | #cmakedefine SHOW_ROI 26 | #define ID_PREFIX "@ID_PREFIX@" 27 | 28 | #define blog(level, msg, ...) blog(level, "[" PLUGIN_NAME "] " msg, ##__VA_ARGS__) 29 | 30 | #endif // PLUGINNAME_H 31 | -------------------------------------------------------------------------------- /src/plugin-main.c: -------------------------------------------------------------------------------- 1 | /* 2 | OBS Color Monitor 3 | Copyright (C) 2021 Norihiro Kamae 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (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 General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program. If not, see 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "plugin-macros.generated.h" 25 | 26 | OBS_DECLARE_MODULE() 27 | OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") 28 | 29 | #define CONFIG_SECTION_NAME "ColorMonitor" 30 | 31 | extern const struct obs_source_info colormonitor_vectorscope_v1; 32 | extern const struct obs_source_info colormonitor_vectorscope; 33 | extern const struct obs_source_info colormonitor_waveform; 34 | extern const struct obs_source_info colormonitor_histogram; 35 | extern const struct obs_source_info colormonitor_zebra; 36 | extern const struct obs_source_info colormonitor_zebra_filter; 37 | extern const struct obs_source_info colormonitor_falsecolor; 38 | extern const struct obs_source_info colormonitor_falsecolor_filter; 39 | extern const struct obs_source_info colormonitor_focuspeaking; 40 | extern const struct obs_source_info colormonitor_focuspeaking_filter; 41 | extern const struct obs_source_info colormonitor_roi; 42 | void scope_docks_init(); 43 | 44 | static bool register_source_with_flags(const struct obs_source_info *const_info, uint32_t flags) 45 | { 46 | struct obs_source_info info = *const_info; 47 | info.output_flags |= flags; 48 | obs_register_source(&info); 49 | 50 | if (!obs_get_latest_input_type_id(info.id)) { 51 | blog(LOG_ERROR, "failed to load source '%s'", info.id); 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | bool obs_module_load(void) 59 | { 60 | int version_major = atoi(obs_get_version_string()); 61 | if (version_major && version_major < LIBOBS_API_MAJOR_VER) { 62 | blog(LOG_ERROR, "Cancel loading plugin since OBS version '%s' is older than plugin API version %d", 63 | obs_get_version_string(), LIBOBS_API_MAJOR_VER); 64 | return false; 65 | } 66 | 67 | config_t *cfg = obs_frontend_get_global_config(); 68 | config_set_default_bool(cfg, CONFIG_SECTION_NAME, "ShowSource", true); 69 | config_set_default_bool(cfg, CONFIG_SECTION_NAME, "ShowFilter", true); 70 | 71 | bool show_source = config_get_bool(cfg, CONFIG_SECTION_NAME, "ShowSource"); 72 | uint32_t src_flags = show_source ? 0 : OBS_SOURCE_CAP_DISABLED; 73 | 74 | bool show_filter = config_get_bool(cfg, CONFIG_SECTION_NAME, "ShowFilter"); 75 | uint32_t flt_flags = show_filter ? 0 : OBS_SOURCE_CAP_DISABLED; 76 | 77 | if (!register_source_with_flags(&colormonitor_vectorscope_v1, src_flags)) 78 | return false; 79 | if (!register_source_with_flags(&colormonitor_vectorscope, src_flags)) 80 | return false; 81 | if (!register_source_with_flags(&colormonitor_waveform, src_flags)) 82 | return false; 83 | if (!register_source_with_flags(&colormonitor_histogram, src_flags)) 84 | return false; 85 | if (!register_source_with_flags(&colormonitor_zebra, src_flags)) 86 | return false; 87 | if (!register_source_with_flags(&colormonitor_zebra_filter, flt_flags)) 88 | return false; 89 | if (!register_source_with_flags(&colormonitor_falsecolor, src_flags)) 90 | return false; 91 | if (!register_source_with_flags(&colormonitor_falsecolor_filter, flt_flags)) 92 | return false; 93 | if (!register_source_with_flags(&colormonitor_focuspeaking, src_flags)) 94 | return false; 95 | if (!register_source_with_flags(&colormonitor_focuspeaking_filter, flt_flags)) 96 | return false; 97 | if (!register_source_with_flags(&colormonitor_roi, src_flags)) 98 | return false; 99 | 100 | scope_docks_init(); 101 | blog(LOG_INFO, "plugin loaded (plugin version %s, API version %d.%d.%d)", PLUGIN_VERSION, LIBOBS_API_MAJOR_VER, 102 | LIBOBS_API_MINOR_VER, LIBOBS_API_PATCH_VER); 103 | return true; 104 | } 105 | -------------------------------------------------------------------------------- /src/roi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "common.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct roi_source 11 | { 12 | struct cm_source cm; 13 | int n_interleave, i_interleave; 14 | bool interleave_rendered; 15 | 16 | int x0sizing, x1sizing, y0sizing, y1sizing; 17 | int x0in, x1in, y0in, y1in; 18 | uint32_t flags_interact; 19 | uint32_t flags_interact_gs; 20 | int x_start, y_start; 21 | int x_mouse, y_mouse; 22 | 23 | pthread_mutex_t sources_mutex; 24 | DARRAY(struct cm_source *) sources; 25 | }; 26 | 27 | struct roi_source *roi_from_source(obs_source_t *); 28 | bool roi_target_render(struct roi_source *src); 29 | 30 | void roi_register_source(struct roi_source *, struct cm_source *); 31 | void roi_unregister_source(struct roi_source *, struct cm_source *); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /src/scope-dock-new-dialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "plugin-macros.generated.h" 10 | #include "scope-dock.hpp" 11 | #include "scope-dock-new-dialog.hpp" 12 | #include "scope-widget.hpp" 13 | 14 | ScopeDockNewDialog::ScopeDockNewDialog(QMainWindow *parent) : QDialog(parent) 15 | { 16 | QLabel *label; 17 | int ix = 0; 18 | mainLayout = new QGridLayout; 19 | 20 | label = new QLabel(obs_module_text("dock.dialog.title")); 21 | editTitle = new QLineEdit(); 22 | editTitle->setText("Scope Dock"); 23 | mainLayout->addWidget(label, ix, 0, Qt::AlignRight); 24 | mainLayout->addWidget(editTitle, ix++, 1, Qt::AlignCenter); 25 | 26 | label = new QLabel(obs_module_text("Source")); 27 | radioProgram = new QRadioButton(obs_module_text("Program")); 28 | radioProgram->setChecked(true); 29 | radioPreview = new QRadioButton(obs_module_text("Preview")); 30 | mainLayout->addWidget(label, ix, 0, 3, 1, Qt::AlignRight); 31 | mainLayout->addWidget(radioProgram, ix++, 1, Qt::AlignLeft); 32 | mainLayout->addWidget(radioPreview, ix++, 1, Qt::AlignLeft); 33 | mainLayout->addWidget(new QLabel(obs_module_text("dock.dialog.note")), ix++, 1, Qt::AlignLeft); 34 | // TODO: other sources 35 | 36 | QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 37 | mainLayout->addWidget(buttonBox, ix++, 1, Qt::AlignRight); 38 | connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); 39 | connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); 40 | 41 | setLayout(mainLayout); 42 | } 43 | 44 | ScopeDockNewDialog::~ScopeDockNewDialog() {} 45 | 46 | void ScopeDockNewDialog::accept() 47 | { 48 | const char *srcName = NULL; 49 | obs_data_t *props = obs_data_create(); 50 | obs_data_t *roi_prop = obs_data_create(); 51 | if (radioPreview->isChecked()) 52 | srcName = "\x10"; // preview 53 | 54 | if (srcName) 55 | obs_data_set_string(roi_prop, "target_name", srcName); 56 | obs_data_set_obj(props, "colormonitor_roi-prop", roi_prop); 57 | ScopeWidget::default_properties(props); 58 | 59 | scope_dock_add(editTitle->text().toUtf8().constData(), props, true); 60 | 61 | obs_data_release(roi_prop); 62 | obs_data_release(props); 63 | 64 | QDialog::accept(); 65 | } 66 | -------------------------------------------------------------------------------- /src/scope-dock-new-dialog.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class ScopeDockNewDialog : public QDialog { 7 | Q_OBJECT 8 | class QGridLayout *mainLayout; 9 | class QLineEdit *editTitle; 10 | class QRadioButton *radioProgram; 11 | class QRadioButton *radioPreview; 12 | // class QRadioButton *radioSource; 13 | // class QComboBox *editSourceName; 14 | 15 | public: 16 | ScopeDockNewDialog(class QMainWindow *parent = NULL); 17 | ~ScopeDockNewDialog(); 18 | 19 | public slots: 20 | void accept() override; 21 | }; 22 | -------------------------------------------------------------------------------- /src/scope-dock.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "plugin-macros.generated.h" 7 | #include "scope-dock.hpp" 8 | #include "scope-dock-new-dialog.hpp" 9 | #include "scope-widget.hpp" 10 | 11 | #define SAVE_DATA_NAME PLUGIN_NAME "-dock" 12 | #define OBJ_NAME_SUFFIX "_scope_dock" 13 | 14 | static std::vector *docks; 15 | 16 | static inline bool is_program_dock(obs_data_t *props) 17 | { 18 | bool ret = true; 19 | 20 | obs_data_t *roi_prop = obs_data_get_obj(props, "colormonitor_roi-prop"); 21 | const char *target_name = obs_data_get_string(roi_prop, "target_name"); 22 | if (target_name && *target_name) 23 | ret = false; // not program 24 | obs_data_release(roi_prop); 25 | 26 | return ret; 27 | } 28 | 29 | void scope_dock_add(const char *name, obs_data_t *props, bool show) 30 | { 31 | auto *main_window = static_cast(obs_frontend_get_main_window()); 32 | ScopeWidget *w = new ScopeWidget(main_window); 33 | w->name = name; 34 | w->load_properties(props); 35 | if (!obs_frontend_add_dock_by_id(name, name, w)) { 36 | return; 37 | } 38 | 39 | if (docks) 40 | docks->push_back(w); 41 | 42 | if (show) { 43 | QMetaObject::invokeMethod( 44 | w, 45 | [w]() { 46 | auto *main_window = static_cast(obs_frontend_get_main_window()); 47 | if (!main_window) 48 | return; 49 | QList dd = main_window->findChildren(); 50 | for (QDockWidget *d : dd) { 51 | if (d->widget() == w) { 52 | d->setVisible(true); 53 | } 54 | } 55 | }, 56 | Qt::QueuedConnection); 57 | } 58 | } 59 | 60 | static void close_all_docks() 61 | { 62 | if (docks && docks->size()) { 63 | blog(LOG_INFO, "Closing %d remaining scope docks...", (int)docks->size()); 64 | while (docks->size()) { 65 | (*docks)[docks->size() - 1]->close(); 66 | delete (*docks)[docks->size() - 1]; 67 | } 68 | blog(LOG_INFO, "Closed all remaining scope docks."); 69 | } 70 | } 71 | 72 | static void save_load_scope_docks(obs_data_t *save_data, bool saving, void *) 73 | { 74 | blog(LOG_INFO, "save_load_scope_docks saving=%d", (int)saving); 75 | if (!docks) 76 | return; 77 | if (saving) { 78 | obs_data_t *props = obs_data_create(); 79 | obs_data_array_t *array = obs_data_array_create(); 80 | for (size_t i = 0; i < docks->size(); i++) { 81 | ScopeWidget *w = (*docks)[i]; 82 | const char *name = w->name.c_str(); 83 | obs_data_t *obj = obs_data_create(); 84 | w->save_properties(obj); 85 | obs_data_set_string(obj, "name", name); 86 | obs_data_array_push_back(array, obj); 87 | obs_data_release(obj); 88 | } 89 | obs_data_set_array(props, "docks", array); 90 | obs_data_set_obj(save_data, SAVE_DATA_NAME, props); 91 | obs_data_array_release(array); 92 | obs_data_release(props); 93 | } 94 | 95 | else /* loading */ { 96 | close_all_docks(); 97 | 98 | obs_data_t *props = obs_data_get_obj(save_data, SAVE_DATA_NAME); 99 | if (!props) { 100 | blog(LOG_INFO, "save_load_scope_docks: creating default properties"); 101 | props = obs_data_create(); 102 | } 103 | 104 | obs_data_array_t *array = obs_data_get_array(props, "docks"); 105 | size_t count = obs_data_array_count(array); 106 | for (size_t i = 0; i < count; i++) { 107 | obs_data_t *obj = obs_data_array_item(array, i); 108 | ScopeWidget::default_properties(obj); 109 | const char *name = obs_data_get_string(obj, "name"); 110 | if (!name) 111 | name = "Scope: program"; 112 | scope_dock_add(name, obj, false); 113 | obs_data_release(obj); 114 | } 115 | obs_data_array_release(array); 116 | obs_data_release(props); 117 | } 118 | } 119 | 120 | static void scope_docks_release(); 121 | 122 | static void frontend_event(enum obs_frontend_event event, void *) 123 | { 124 | switch (event) { 125 | case OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN: 126 | case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP: 127 | case OBS_FRONTEND_EVENT_EXIT: 128 | close_all_docks(); 129 | 130 | if (event == OBS_FRONTEND_EVENT_EXIT) 131 | scope_docks_release(); 132 | break; 133 | default: 134 | break; 135 | } 136 | } 137 | 138 | void scope_docks_init() 139 | { 140 | docks = new std::vector; 141 | obs_frontend_add_save_callback(save_load_scope_docks, NULL); 142 | obs_frontend_add_event_callback(frontend_event, nullptr); 143 | 144 | QAction *action = 145 | static_cast(obs_frontend_add_tools_menu_qaction(obs_module_text("New Scope Dock..."))); 146 | auto cb = [] { 147 | obs_frontend_push_ui_translation(obs_module_get_string); 148 | auto *dialog = new ScopeDockNewDialog(static_cast(obs_frontend_get_main_window())); 149 | dialog->show(); 150 | dialog->setAttribute(Qt::WA_DeleteOnClose, true); 151 | obs_frontend_pop_ui_translation(); 152 | }; 153 | QAction::connect(action, &QAction::triggered, cb); 154 | } 155 | 156 | void scope_docks_release() 157 | { 158 | delete docks; 159 | docks = NULL; 160 | 161 | obs_frontend_remove_save_callback(save_load_scope_docks, NULL); 162 | obs_frontend_remove_event_callback(frontend_event, nullptr); 163 | } 164 | 165 | void scope_dock_deleted(class ScopeWidget *widget) 166 | { 167 | if (!docks) 168 | return; 169 | 170 | for (size_t i = 0; i < docks->size(); i++) { 171 | if ((*docks)[i] != widget) 172 | continue; 173 | 174 | docks->erase(docks->begin() + i); 175 | break; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/scope-dock.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern "C" void scope_dock_add(const char *name, obs_data_t *props, bool show); 4 | extern "C" void scope_dock_deleted(class ScopeWidget *); 5 | extern "C" void scope_docks_init(); 6 | -------------------------------------------------------------------------------- /src/scope-widget-properties.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "plugin-macros.generated.h" 9 | #include "scope-widget-properties.hpp" 10 | #include "obsgui-helper.hpp" 11 | #include "properties-view.hpp" 12 | 13 | static obs_properties_t *scopewidget_properties(const obs_source_t *source) 14 | { 15 | obs_properties_t *props = obs_source_properties(source); 16 | if (!props) 17 | return NULL; 18 | obs_property_set_visible(obs_properties_get(props, "target_name"), false); 19 | obs_property_set_visible(obs_properties_get(props, "target_scale"), false); 20 | obs_property_set_visible(obs_properties_get(props, "bypass"), false); 21 | return props; 22 | } 23 | 24 | ScopeWidgetProperties::ScopeWidgetProperties(QWidget *parent, obs_source_t *source_[]) : QDialog(parent) 25 | { 26 | acceptClicked = false; 27 | for (int i = 0; i < SCOPE_WIDGET_N_SRC; i++) { 28 | source[i] = source_[i]; 29 | // TODO: connect(obs_source_get_signal_handler(source[i]), "remove", ScopeWidgetProperties::SourceRemoved, this); 30 | } 31 | 32 | buttonBox = new QDialogButtonBox(this); 33 | buttonBox->setObjectName(QStringLiteral("buttonBox")); 34 | buttonBox->setStandardButtons(QDialogButtonBox::Ok); 35 | // TODO: QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); 36 | 37 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 38 | 39 | QMetaObject::connectSlotsByName(this); 40 | 41 | tabWidget = new QTabWidget(this); 42 | 43 | for (int i = 0; i < SCOPE_WIDGET_N_SRC; i++) { 44 | if (!source[i]) 45 | continue; 46 | OBSData settings = obs_source_get_settings(source[i]); 47 | obs_data_release(settings); 48 | 49 | PropertiesReloadCallback prop_cb = (PropertiesReloadCallback)scopewidget_properties; 50 | if (i == 0) 51 | prop_cb = (PropertiesReloadCallback)obs_source_properties; 52 | 53 | PropertiesUpdateCallback handle_memory = [](void *vp, obs_data_t *new_settings) { 54 | obs_source_t *source = static_cast(vp); 55 | 56 | obs_source_update(source, new_settings); 57 | }; 58 | 59 | view[i] = new OBSPropertiesView(settings, source[i], prop_cb, handle_memory); 60 | const char *name = obs_source_get_display_name(obs_source_get_id(source[i])); 61 | tabWidget->addTab(view[i], name); 62 | } 63 | 64 | setLayout(new QVBoxLayout(this)); 65 | layout()->addWidget(tabWidget); 66 | layout()->addWidget(buttonBox); 67 | } 68 | 69 | ScopeWidgetProperties::~ScopeWidgetProperties() 70 | { 71 | static_cast(parent())->properties = NULL; 72 | // TODO: main->SaveProject(); 73 | } 74 | 75 | void ScopeWidgetProperties::setTabIndex(int ix) 76 | { 77 | blog(LOG_INFO, "ScopeWidgetProperties::setTabIndex(%d)", ix); 78 | if (tabWidget && 0 <= ix && ix < tabWidget->count()) 79 | tabWidget->setCurrentIndex(ix); 80 | } 81 | 82 | void ScopeWidgetProperties::Init() 83 | { 84 | show(); 85 | } 86 | 87 | void ScopeWidgetProperties::Cleanup() {} 88 | 89 | void ScopeWidgetProperties::closeEvent(QCloseEvent *event) 90 | { 91 | QDialog::closeEvent(event); 92 | if (!event->isAccepted()) 93 | return; 94 | 95 | Cleanup(); 96 | } 97 | 98 | void ScopeWidgetProperties::on_buttonBox_clicked(QAbstractButton *button) 99 | { 100 | QDialogButtonBox::ButtonRole val = buttonBox->buttonRole(button); 101 | if (val == QDialogButtonBox::AcceptRole) { 102 | acceptClicked = true; 103 | close(); 104 | } else if (val == QDialogButtonBox::RejectRole) { 105 | // TODO: clear data 106 | static_cast(parent())->load_properties(oldSettings); 107 | close(); 108 | } else if (val == QDialogButtonBox::ResetRole) { 109 | // TODO: implement me 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/scope-widget-properties.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "scope-widget.hpp" 12 | 13 | class ScopeWidgetProperties : public QDialog { 14 | Q_OBJECT 15 | 16 | private: 17 | OBSSource source[SCOPE_WIDGET_N_SRC]; 18 | OBSData oldSettings; 19 | 20 | OBSSignal removedSignal[SCOPE_WIDGET_N_SRC]; 21 | OBSSignal renamedSignal[SCOPE_WIDGET_N_SRC]; 22 | class OBSPropertiesView *view[SCOPE_WIDGET_N_SRC]; 23 | class QTabWidget *tabWidget; 24 | class QDialogButtonBox *buttonBox; 25 | class QSplitter *splitter; 26 | bool acceptClicked; 27 | 28 | private slots: 29 | void on_buttonBox_clicked(QAbstractButton *button); 30 | 31 | public: 32 | ScopeWidgetProperties(QWidget *parent, obs_source_t *source_[]); 33 | ~ScopeWidgetProperties(); 34 | 35 | void setTabIndex(int); 36 | void Init(); 37 | void Cleanup(); 38 | 39 | protected: 40 | void closeEvent(QCloseEvent *event) override; 41 | }; 42 | -------------------------------------------------------------------------------- /src/scope-widget.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define SCOPE_WIDGET_N_SRC 7 9 | 10 | class ScopeWidget : public QWidget { 11 | Q_OBJECT 12 | 13 | struct scope_widget_s *data; 14 | class ScopeWidgetProperties *properties; 15 | 16 | public: 17 | std::string name; 18 | 19 | private: 20 | void resizeEvent(QResizeEvent *event) override; 21 | void paintEvent(QPaintEvent *event) override; 22 | class QPaintEngine *paintEngine() const override; 23 | void closeEvent(QCloseEvent *event) override; 24 | 25 | // for interactions 26 | bool HandleMouseClickEvent(QMouseEvent *event); 27 | bool HandleMouseMoveEvent(QMouseEvent *event); 28 | bool HandleMouseWheelEvent(QWheelEvent *event); 29 | bool HandleKeyEvent(QKeyEvent *event); 30 | bool openMenu(QMouseEvent *event); 31 | 32 | public slots: 33 | void createProperties(); 34 | void RemoveDock(); 35 | 36 | public: 37 | ScopeWidget(QWidget *parent); 38 | ~ScopeWidget(); 39 | void CreateDisplay(); 40 | void DestroyDisplay(); 41 | static void default_properties(obs_data_t *); 42 | void save_properties(obs_data_t *); 43 | void load_properties(obs_data_t *); 44 | void setShown(bool shown); 45 | 46 | friend class ScopeWidgetProperties; 47 | friend class ScopeWidgetInteractiveEventFilter; 48 | }; 49 | -------------------------------------------------------------------------------- /src/util-cpp.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "plugin-macros.generated.h" 7 | #include "util.h" 8 | 9 | struct add_sources_s 10 | { 11 | obs_source_t *self; 12 | std::vector source_names; 13 | }; 14 | 15 | static bool add_sources(void *data, obs_source_t *source) 16 | { 17 | auto &ctx = *static_cast(data); 18 | 19 | if (source == ctx.self) 20 | return true; 21 | 22 | uint32_t caps = obs_source_get_output_flags(source); 23 | if (~caps & OBS_SOURCE_VIDEO) 24 | return true; 25 | 26 | if (obs_source_is_group(source)) 27 | return true; 28 | 29 | const char *name = obs_source_get_name(source); 30 | ctx.source_names.push_back(name); 31 | return true; 32 | } 33 | 34 | void property_list_add_sources(obs_property_t *prop, obs_source_t *self) 35 | { 36 | // current scene 37 | obs_property_list_add_string(prop, obs_module_text("Program"), ""); 38 | obs_property_list_add_string(prop, obs_module_text("MainView"), "\x01"); 39 | obs_property_list_add_string(prop, obs_module_text("Preview"), "\x10"); 40 | 41 | // scenes, same order as the scene list 42 | obs_frontend_source_list sceneList = {}; 43 | obs_frontend_get_scenes(&sceneList); 44 | for (size_t i = 0; i < sceneList.sources.num; i++) { 45 | obs_source_t *source = sceneList.sources.array[i]; 46 | const char *c_name = obs_source_get_name(source); 47 | std::string name = obs_module_text("srclist.prefix.scene"); 48 | name += c_name; 49 | obs_property_list_add_string(prop, name.c_str(), c_name); 50 | } 51 | obs_frontend_source_list_free(&sceneList); 52 | 53 | // sources, alphabetical order 54 | add_sources_s ctx; 55 | ctx.self = self; 56 | obs_enum_sources(add_sources, &ctx); 57 | 58 | std::sort(ctx.source_names.begin(), ctx.source_names.end()); 59 | 60 | for (size_t i = 0; i < ctx.source_names.size(); i++) { 61 | const std::string name = obs_module_text("srclist.prefix.source") + ctx.source_names[i]; 62 | obs_property_list_add_string(prop, name.c_str(), ctx.source_names[i].c_str()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "plugin-macros.generated.h" 3 | #include "util.h" 4 | 5 | gs_effect_t *create_effect_from_module_file(const char *basename) 6 | { 7 | char *f = obs_module_file(basename); 8 | gs_effect_t *effect = gs_effect_create_from_file(f, NULL); 9 | if (!effect) 10 | blog(LOG_ERROR, "Cannot load '%s' '%s'", basename, f); 11 | bfree(f); 12 | return effect; 13 | } 14 | 15 | obs_property_t *properties_add_colorspace(obs_properties_t *props, const char *name, const char *description) 16 | { 17 | obs_property_t *prop = 18 | obs_properties_add_list(props, name, description, OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); 19 | obs_property_list_add_int(prop, obs_module_text("Auto"), 0); 20 | obs_property_list_add_int(prop, obs_module_text("601"), 1); 21 | obs_property_list_add_int(prop, obs_module_text("709"), 2); 22 | return prop; 23 | } 24 | 25 | int calc_colorspace(int colorspace) 26 | { 27 | if (1 <= colorspace && colorspace <= 2) 28 | return colorspace; 29 | struct obs_video_info ovi; 30 | if (obs_get_video_info(&ovi)) { 31 | switch (ovi.colorspace) { 32 | case VIDEO_CS_601: 33 | return 1; 34 | case VIDEO_CS_709: 35 | return 2; 36 | default: 37 | return 2; // TODO: Implement 38 | } 39 | } 40 | return 2; // default 41 | } 42 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | void property_list_add_sources(obs_property_t *prop, obs_source_t *self); 8 | obs_property_t *properties_add_colorspace(obs_properties_t *props, const char *name, const char *description); 9 | 10 | int calc_colorspace(int); 11 | 12 | gs_effect_t *create_effect_from_module_file(const char *basename); 13 | 14 | #ifdef __cplusplus 15 | } 16 | #endif 17 | -------------------------------------------------------------------------------- /src/vectorscope-graticule.svg: -------------------------------------------------------------------------------- 1 | 2 | 20 | 22 | 45 | 54 | 55 | 57 | 58 | 60 | image/svg+xml 61 | 63 | 64 | 65 | 66 | 67 | 71 | R 82 | G 93 | B 104 | Yl 115 | Cy 126 | Mg 137 | 138 | 139 | -------------------------------------------------------------------------------- /src/vectorscope.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "plugin-macros.generated.h" 6 | #include "obs-convenience.h" 7 | #include "common.h" 8 | #include "util.h" 9 | 10 | #ifdef ENABLE_PROFILE 11 | #define PROFILE_START(x) profile_start(x) 12 | #define PROFILE_END(x) profile_end(x) 13 | static const char *prof_render_name = "vss_render"; 14 | static const char *prof_draw_vectorscope_name = "draw_vectorscope"; 15 | static const char *prof_draw_name = "draw"; 16 | static const char *prof_draw_graticule_name = "graticule"; 17 | #else // ENABLE_PROFILE 18 | #define PROFILE_START(x) 19 | #define PROFILE_END(x) 20 | #endif // ! ENABLE_PROFILE 21 | 22 | #define VS_SIZE 256 23 | #define N_GRATICULES 18 24 | #define GRATICULES_IQ 256 25 | #define GRATICULES_COLOR_MASK 3 26 | #define SKIN_TONE_LINE 0x0054FF // BGR 27 | 28 | #define RGB2Y_601(r, g, b) ((+306 * (r) + 601 * (g) + 117 * (b)) / 1024 + 0) 29 | #define RGB2U_601(r, g, b) ((-150 * (r) - 296 * (g) + 448 * (b)) / 1024 + 128) 30 | #define RGB2V_601(r, g, b) ((+448 * (r) - 374 * (g) - 72 * (b)) / 1024 + 128) 31 | 32 | #define RGB2Y_709(r, g, b) ((+218 * (r) + 732 * (g) + 74 * (b)) / 1024 + 16) 33 | #define RGB2U_709(r, g, b) ((-102 * (r) - 346 * (g) + 450 * (b)) / 1024 + 128) 34 | #define RGB2V_709(r, g, b) ((+450 * (r) - 408 * (g) - 40 * (b)) / 1024 + 128) 35 | 36 | enum color_type { 37 | color_type_white = 0, 38 | color_type_uv, 39 | }; 40 | 41 | struct vss_source 42 | { 43 | struct cm_source cm; 44 | 45 | gs_texture_t *tex_vs; 46 | uint8_t *tex_buf[2]; 47 | int tex_cs[2]; 48 | volatile int w_tex_buf; 49 | 50 | gs_image_file_t graticule_img; 51 | gs_vertbuffer_t *graticule_vbuf; 52 | gs_vertbuffer_t *graticule_line_vbuf; 53 | gs_effect_t *effect; 54 | 55 | int intensity; 56 | enum color_type color_type; 57 | int graticule; 58 | int graticule_color; 59 | int graticule_skintone_color; 60 | int graticule_cs; 61 | bool update_graticule; 62 | 63 | float zoom; 64 | }; 65 | 66 | static void vss_update(void *, obs_data_t *); 67 | static void vss_surface_cb(void *data, struct cm_surface_data *surface_data); 68 | 69 | static const char *vss_get_name(void *unused) 70 | { 71 | UNUSED_PARAMETER(unused); 72 | return obs_module_text("Vectorscope"); 73 | } 74 | 75 | static void *vss_create(obs_data_t *settings, obs_source_t *source) 76 | { 77 | struct vss_source *src = bzalloc(sizeof(struct vss_source)); 78 | 79 | src->cm.flags = CM_FLAG_CONVERT_YUV; 80 | src->zoom = 1.0f; 81 | cm_create(&src->cm, settings, source); 82 | cm_request(&src->cm, vss_surface_cb, src); 83 | 84 | { 85 | // The file is generated by 86 | // inkscape --export-png=data/vectorscope-graticule.png --export-area-page src/vectorscope-graticule.svg 87 | char *f = obs_module_file("vectorscope-graticule.png"); 88 | gs_image_file_init(&src->graticule_img, f); 89 | if (!src->graticule_img.loaded) 90 | blog(LOG_ERROR, "Cannot load '%s'", f); 91 | obs_enter_graphics(); 92 | gs_image_file_init_texture(&src->graticule_img); 93 | obs_leave_graphics(); 94 | bfree(f); 95 | } 96 | 97 | obs_enter_graphics(); 98 | src->effect = create_effect_from_module_file("vectorscope.effect"); 99 | obs_leave_graphics(); 100 | 101 | vss_update(src, settings); 102 | 103 | return src; 104 | } 105 | 106 | static void vss_destroy(void *data) 107 | { 108 | struct vss_source *src = data; 109 | 110 | obs_enter_graphics(); 111 | gs_texture_destroy(src->tex_vs); 112 | gs_image_file_free(&src->graticule_img); 113 | gs_vertexbuffer_destroy(src->graticule_vbuf); 114 | gs_vertexbuffer_destroy(src->graticule_line_vbuf); 115 | obs_leave_graphics(); 116 | 117 | cm_destroy(&src->cm); 118 | 119 | bfree(src->tex_buf[0]); 120 | bfree(src->tex_buf[1]); 121 | bfree(src); 122 | } 123 | 124 | static void vss_update(void *data, obs_data_t *settings) 125 | { 126 | struct vss_source *src = data; 127 | cm_update(&src->cm, settings); 128 | 129 | src->intensity = (int)obs_data_get_int(settings, "intensity"); 130 | if (src->intensity < 1) 131 | src->intensity = 1; 132 | 133 | src->color_type = (enum color_type)obs_data_get_int(settings, "color_type"); 134 | 135 | int graticule = (int)obs_data_get_int(settings, "graticule"); 136 | if ((graticule ^ src->graticule) & GRATICULES_IQ) 137 | src->update_graticule = 1; 138 | src->graticule = graticule; 139 | switch (graticule & GRATICULES_COLOR_MASK) { 140 | case 1: 141 | src->graticule_color = 0x80FFBF00; 142 | break; // amber 143 | case 2: 144 | src->graticule_color = 0x8000FF00; 145 | break; // green 146 | } 147 | 148 | int graticule_skintone_color = (int)obs_data_get_int(settings, "graticule_skintone_color") & 0xFFFFFF; 149 | if (graticule_skintone_color != src->graticule_skintone_color) { 150 | src->graticule_skintone_color = graticule_skintone_color; 151 | src->update_graticule = 1; 152 | } 153 | } 154 | 155 | static void vss_get_defaults_v1(obs_data_t *settings) 156 | { 157 | obs_data_set_default_int(settings, "target_scale", 2); 158 | obs_data_set_default_int(settings, "intensity", 25); 159 | obs_data_set_default_int(settings, "graticule", 1 | GRATICULES_IQ); 160 | obs_data_set_default_int(settings, "graticule_skintone_color", SKIN_TONE_LINE); 161 | } 162 | 163 | static void vss_get_defaults(obs_data_t *settings) 164 | { 165 | vss_get_defaults_v1(settings); 166 | obs_data_set_default_int(settings, "color_type", (long long)color_type_uv); 167 | } 168 | 169 | static obs_properties_t *vss_get_properties(void *data) 170 | { 171 | struct vss_source *src = data; 172 | obs_properties_t *props; 173 | obs_property_t *prop; 174 | props = obs_properties_create(); 175 | 176 | cm_get_properties(&src->cm, props); 177 | 178 | obs_properties_add_int(props, "intensity", obs_module_text("Intensity"), 1, 255, 1); 179 | 180 | prop = obs_properties_add_list(props, "color_type", obs_module_text("VS.Prop.ColorType"), OBS_COMBO_TYPE_LIST, 181 | OBS_COMBO_FORMAT_INT); 182 | obs_property_list_add_int(prop, obs_module_text("VS.Prop.ColorType.White"), color_type_white); 183 | obs_property_list_add_int(prop, obs_module_text("VS.Prop.ColorType.UV"), color_type_uv); 184 | 185 | prop = obs_properties_add_list(props, "graticule", obs_module_text("Graticule"), OBS_COMBO_TYPE_LIST, 186 | OBS_COMBO_FORMAT_INT); 187 | obs_property_list_add_int(prop, obs_module_text("None"), 0); 188 | obs_property_list_add_int(prop, obs_module_text("Amber"), 1); 189 | obs_property_list_add_int(prop, obs_module_text("Amber, IQ"), 1 + GRATICULES_IQ); 190 | obs_property_list_add_int(prop, obs_module_text("Green"), 2); 191 | obs_property_list_add_int(prop, obs_module_text("Green, IQ"), 2 + GRATICULES_IQ); 192 | 193 | obs_properties_add_color(props, "graticule_skintone_color", obs_module_text("Skin tone color")); 194 | 195 | prop = properties_add_colorspace(props, "colorspace", obs_module_text("Color space")); 196 | if (src) { 197 | // Disable colorspace setting if the target is ROI 198 | bool vis = !cm_is_roi(&src->cm); 199 | obs_property_set_visible(prop, vis); 200 | } 201 | 202 | return props; 203 | } 204 | 205 | static uint32_t vss_get_width(void *data) 206 | { 207 | struct vss_source *src = data; 208 | return src->cm.bypass ? cm_bypass_get_width(&src->cm) : VS_SIZE; 209 | } 210 | 211 | static uint32_t vss_get_height(void *data) 212 | { 213 | struct vss_source *src = data; 214 | return src->cm.bypass ? cm_bypass_get_height(&src->cm) : VS_SIZE; 215 | } 216 | 217 | static inline void vss_draw_vectorscope(uint8_t *dbuf, struct cm_surface_data *surface_data) 218 | { 219 | for (int i = 0; i < VS_SIZE * VS_SIZE; i++) 220 | dbuf[i] = 0; 221 | 222 | const uint32_t height = surface_data->height; 223 | const uint32_t width = surface_data->width; 224 | uint8_t *vd = surface_data->yuv_data; 225 | uint32_t vd_add = surface_data->linesize - width * 4; 226 | for (uint32_t y = 0; y < height; y++) { 227 | for (uint32_t x = 0; x < width; x++) { 228 | const uint8_t u = *vd++; 229 | /* b */ vd++; 230 | const uint8_t v = *vd++; 231 | /* a */ vd++; 232 | uint8_t *c = dbuf + (u + VS_SIZE * (255 - v)); 233 | if (*c < 255) 234 | ++*c; 235 | } 236 | vd += vd_add; 237 | } 238 | } 239 | 240 | static void vss_set_image(struct vss_source *src, const uint8_t *tex_buf) 241 | { 242 | if (!src->tex_vs) 243 | src->tex_vs = gs_texture_create(VS_SIZE, VS_SIZE, GS_R8, 1, &tex_buf, GS_DYNAMIC); 244 | else 245 | gs_texture_set_image(src->tex_vs, tex_buf, VS_SIZE, false); 246 | } 247 | 248 | static void vss_surface_cb(void *data, struct cm_surface_data *surface_data) 249 | { 250 | struct vss_source *src = data; 251 | 252 | if (!surface_data->yuv_data) 253 | return; 254 | 255 | if (!src->tex_buf[src->w_tex_buf]) 256 | src->tex_buf[src->w_tex_buf] = bzalloc(VS_SIZE * VS_SIZE); 257 | 258 | PROFILE_START(prof_draw_vectorscope_name); 259 | vss_draw_vectorscope(src->tex_buf[src->w_tex_buf], surface_data); 260 | PROFILE_END(prof_draw_vectorscope_name); 261 | 262 | src->tex_cs[src->w_tex_buf] = surface_data->colorspace; 263 | 264 | src->w_tex_buf ^= 1; 265 | } 266 | 267 | static void create_graticule_vbuf(struct vss_source *src, int colorspace) 268 | { 269 | if (src->update_graticule || colorspace != src->graticule_cs) { 270 | src->update_graticule = false; 271 | gs_vertexbuffer_destroy(src->graticule_vbuf); 272 | src->graticule_vbuf = NULL; 273 | gs_vertexbuffer_destroy(src->graticule_line_vbuf); 274 | src->graticule_line_vbuf = NULL; 275 | } else if (src->graticule_vbuf) { 276 | return; 277 | } 278 | 279 | src->graticule_vbuf = create_uv_vbuffer(N_GRATICULES * 6, false); 280 | struct gs_vb_data *vdata = gs_vertexbuffer_get_data(src->graticule_vbuf); 281 | struct vec2 *tvarray = (struct vec2 *)vdata->tvarray[0].array; 282 | // copied from FFmpeg vectorscope filter 283 | const float pp[2][12][2] = { 284 | { 285 | // 601 286 | {90, 240}, 287 | {240, 110}, 288 | {166, 16}, 289 | {16, 146}, 290 | {54, 34}, 291 | {202, 222}, 292 | {44, 142}, 293 | {156, 44}, 294 | {72, 58}, 295 | {184, 198}, 296 | {100, 212}, 297 | {212, 114}, 298 | }, 299 | { 300 | // 709 301 | {102, 240}, 302 | {240, 118}, 303 | {154, 16}, 304 | {16, 138}, 305 | {42, 26}, 306 | {214, 230}, 307 | {212, 120}, 308 | {109, 212}, 309 | {193, 204}, 310 | {63, 52}, 311 | {147, 44}, 312 | {44, 136}, 313 | }, 314 | }; 315 | const int ppi = colorspace - 1; 316 | 317 | // label 318 | for (int i = 0; i < 6; i++) { 319 | float x = pp[ppi][i][0]; 320 | float y = 256.f - pp[ppi][i][1]; 321 | if (x < 72) 322 | y += 20; 323 | else if (x > 184) 324 | y -= 20; 325 | else if (y > 128) 326 | x += 20; 327 | else 328 | x -= 20; 329 | set_v3_rect(vdata->points + i * 6, x - 8, y - 8, 16, 16); 330 | set_v2_uv(tvarray + i * 6, i / 6.f, 0.f, (i + 1) / 6.f, 1.f); 331 | } 332 | 333 | // box 334 | gs_vertexbuffer_destroy(src->graticule_line_vbuf); 335 | src->graticule_line_vbuf = NULL; 336 | gs_render_start(true); 337 | for (int i = 0; i < 12; i++) { 338 | const float x = pp[ppi][i][0]; 339 | const float y = 256.f - pp[ppi][i][1]; 340 | const float box[16][2] = { 341 | {-6, -6}, {-2, -6}, {-6, -6}, {-6, -2}, {+6, -6}, {+2, -6}, {+6, -6}, {+6, -2}, 342 | {-6, +6}, {-2, +6}, {-6, +6}, {-6, +2}, {+6, +6}, {+2, +6}, {+6, +6}, {+6, +2}, 343 | }; 344 | for (int j = 0; j < 16; j++) 345 | gs_vertex2f(x + box[j][0], y + box[j][1]); 346 | } 347 | 348 | // skin tone line 349 | float stl_u, stl_v, stl_norm; 350 | int stl_b = src->graticule_skintone_color >> 16 & 0xFF; 351 | int stl_g = src->graticule_skintone_color >> 8 & 0xFF; 352 | int stl_r = src->graticule_skintone_color & 0xFF; 353 | switch (colorspace) { 354 | case 1: // BT.601 355 | stl_u = (float)RGB2U_601(stl_r, stl_g, stl_b); 356 | stl_v = (float)RGB2V_601(stl_r, stl_g, stl_b); 357 | break; 358 | default: // BT.709 359 | stl_u = (float)RGB2U_709(stl_r, stl_g, stl_b); 360 | stl_v = (float)RGB2V_709(stl_r, stl_g, stl_b); 361 | break; 362 | } 363 | stl_norm = hypotf(stl_u - 128.0f, stl_v - 128.0f); 364 | if (stl_norm > 1.0f) { 365 | stl_u = (stl_u - 128.0f) * 128.f / stl_norm + 128.0f; 366 | stl_v = (stl_v - 128.0f) * 128.f / stl_norm + 128.0f; 367 | if (src->graticule & GRATICULES_IQ) { 368 | gs_vertex2f(255.f - stl_u, stl_v); 369 | gs_vertex2f(stl_u, 255.f - stl_v); 370 | gs_vertex2f(stl_v, stl_u); 371 | gs_vertex2f(255.f - stl_v, 255.f - stl_u); 372 | } else { 373 | gs_vertex2f(127.5f, 127.5f); 374 | gs_vertex2f(stl_u, 255.f - stl_v); 375 | } 376 | } 377 | 378 | // boxes and skin tone line 379 | src->graticule_line_vbuf = gs_render_save(); 380 | } 381 | 382 | static void vss_render(void *data, gs_effect_t *effect) 383 | { 384 | UNUSED_PARAMETER(effect); 385 | struct vss_source *src = data; 386 | if (src->cm.bypass) { 387 | cm_bypass_render(&src->cm); 388 | return; 389 | } 390 | 391 | PROFILE_START(prof_render_name); 392 | 393 | cm_render_target(&src->cm); 394 | 395 | const bool b_zoom = src->zoom > 1.01f; 396 | if (b_zoom) { 397 | float zoom = src->zoom; 398 | float ofst = 127.5f * (1.0f - zoom); 399 | struct matrix4 tr = { 400 | {.ptr = {zoom, 0.0f, 0.0f, 0.0f}}, 401 | {.ptr = {0.0f, zoom, 0.0f, 0.0f}}, 402 | {.ptr = {0.0f, 0.0f, 1.0f, 0.0f}}, 403 | {.ptr = {ofst, ofst, 0.0f, 1.0f}}, 404 | }; 405 | gs_matrix_push(); 406 | gs_matrix_mul(&tr); 407 | } 408 | 409 | PROFILE_START(prof_draw_name); 410 | int r_tex_buf = src->w_tex_buf ^ 1; 411 | if (src->tex_buf[r_tex_buf] && src->effect) { 412 | vss_set_image(src, src->tex_buf[r_tex_buf]); 413 | 414 | gs_effect_t *effect = src->effect; 415 | gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), src->tex_vs); 416 | gs_effect_set_float(gs_effect_get_param_by_name(effect, "intensity"), (float)src->intensity); 417 | 418 | switch (src->color_type) { 419 | case color_type_uv: 420 | if (src->tex_cs[r_tex_buf] == 1) /* BT.601 */ { 421 | const struct vec4 color = {{{0.5f, 0.5f, 0.5f, 1.0f}}}; 422 | const struct vec3 color_u = {{{0.0f, -0.3441f, +1.772f, 0.0f}}}; 423 | const struct vec3 color_v = {{{+1.402f, -0.7141f, 0.0f, 0.0f}}}; 424 | gs_effect_set_vec4(gs_effect_get_param_by_name(effect, "color"), &color); 425 | gs_effect_set_vec3(gs_effect_get_param_by_name(effect, "color_u"), &color_u); 426 | gs_effect_set_vec3(gs_effect_get_param_by_name(effect, "color_v"), &color_v); 427 | } else /* BT.709 */ { 428 | const struct vec4 color = {{{0.5f, 0.5f, 0.5f, 1.0f}}}; 429 | const struct vec3 color_u = {{{0.0f, -0.1873f, +1.8556f, 0.0f}}}; 430 | const struct vec3 color_v = {{{+1.5748f, -0.4681f, 0.0f, 0.0f}}}; 431 | gs_effect_set_vec4(gs_effect_get_param_by_name(effect, "color"), &color); 432 | gs_effect_set_vec3(gs_effect_get_param_by_name(effect, "color_u"), &color_u); 433 | gs_effect_set_vec3(gs_effect_get_param_by_name(effect, "color_v"), &color_v); 434 | } 435 | break; 436 | case color_type_white: 437 | default: 438 | gs_effect_set_default(gs_effect_get_param_by_name(effect, "color")); 439 | } 440 | 441 | while (gs_effect_loop(effect, "Draw")) { 442 | gs_draw_sprite(src->tex_vs, 0, VS_SIZE, VS_SIZE); 443 | } 444 | } 445 | PROFILE_END(prof_draw_name); 446 | 447 | PROFILE_START(prof_draw_graticule_name); 448 | if (src->graticule_img.loaded && src->graticule && src->effect) { 449 | create_graticule_vbuf(src, src->tex_cs[r_tex_buf]); 450 | gs_effect_t *effect = src->effect; 451 | gs_effect_set_color(gs_effect_get_param_by_name(effect, "color"), src->graticule_color); 452 | draw_uv_vbuffer(src->graticule_vbuf, src->graticule_img.texture, effect, "DrawGraticule", 453 | N_GRATICULES * 2); 454 | } 455 | 456 | if (src->graticule && src->graticule_line_vbuf) { 457 | gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_SOLID); 458 | gs_effect_set_color(gs_effect_get_param_by_name(effect, "color"), src->graticule_color); 459 | gs_load_vertexbuffer(src->graticule_line_vbuf); 460 | while (gs_effect_loop(effect, "Solid")) { 461 | gs_draw(GS_LINES, 0, 0); 462 | } 463 | } 464 | PROFILE_END(prof_draw_graticule_name); 465 | 466 | if (b_zoom) { 467 | gs_matrix_pop(); 468 | } 469 | 470 | PROFILE_END(prof_render_name); 471 | } 472 | 473 | static void vss_mouse_wheel(void *data, const struct obs_mouse_event *event, int x_delta, int y_delta) 474 | { 475 | UNUSED_PARAMETER(event); 476 | UNUSED_PARAMETER(x_delta); 477 | struct vss_source *src = data; 478 | 479 | src->zoom *= expf(y_delta * 5e-4f); 480 | if (src->zoom < 1.0f) 481 | src->zoom = 1.0f; 482 | } 483 | 484 | const struct obs_source_info colormonitor_vectorscope_v1 = { 485 | .id = "vectorscope_source", 486 | .type = OBS_SOURCE_TYPE_INPUT, 487 | .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_INTERACTION, 488 | .get_name = vss_get_name, 489 | .create = vss_create, 490 | .destroy = vss_destroy, 491 | .update = vss_update, 492 | .get_defaults = vss_get_defaults_v1, 493 | .get_properties = vss_get_properties, 494 | .get_width = vss_get_width, 495 | .get_height = vss_get_height, 496 | .enum_active_sources = cm_enum_sources, 497 | .video_render = vss_render, 498 | .video_tick = cm_tick, 499 | .mouse_wheel = vss_mouse_wheel, 500 | }; 501 | 502 | const struct obs_source_info colormonitor_vectorscope = { 503 | .id = "vectorscope_source", 504 | .version = 2, 505 | .type = OBS_SOURCE_TYPE_INPUT, 506 | .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW | OBS_SOURCE_INTERACTION, 507 | .get_name = vss_get_name, 508 | .create = vss_create, 509 | .destroy = vss_destroy, 510 | .update = vss_update, 511 | .get_defaults = vss_get_defaults, 512 | .get_properties = vss_get_properties, 513 | .get_width = vss_get_width, 514 | .get_height = vss_get_height, 515 | .enum_active_sources = cm_enum_sources, 516 | .video_render = vss_render, 517 | .video_tick = cm_tick, 518 | .mouse_wheel = vss_mouse_wheel, 519 | }; 520 | -------------------------------------------------------------------------------- /src/waveform.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "plugin-macros.generated.h" 4 | #include 5 | #include "common.h" 6 | #include "util.h" 7 | 8 | #ifdef ENABLE_PROFILE 9 | #define PROFILE_START(x) profile_start(x) 10 | #define PROFILE_END(x) profile_end(x) 11 | static const char *prof_render_name = "wvs_render"; 12 | static const char *prof_draw_waveform_name = "draw_waveform"; 13 | static const char *prof_draw_name = "draw"; 14 | static const char *prof_draw_graticule_name = "graticule"; 15 | #else // ENABLE_PROFILE 16 | #define PROFILE_START(x) 17 | #define PROFILE_END(x) 18 | #endif // ! ENABLE_PROFILE 19 | 20 | #define WV_SIZE 256 21 | 22 | #define DISP_OVERLAY 0 23 | #define DISP_STACK 1 24 | #define DISP_PARADE 2 25 | 26 | #define COMP_RGB 0x07 27 | #define COMP_Y 0x20 28 | #define COMP_UV 0x50 29 | #define COMP_YUV (COMP_Y | COMP_UV) 30 | 31 | struct wvs_source 32 | { 33 | struct cm_source cm; 34 | 35 | gs_effect_t *effect; 36 | gs_texture_t *tex_wv; 37 | uint32_t tex_wv_width; 38 | uint8_t *tex_buf[2]; 39 | uint32_t tex_buf_width[2]; 40 | volatile int w_tex_buf; 41 | int r_tex_buf; 42 | 43 | gs_vertbuffer_t *graticule_line_vbuf; 44 | 45 | int display; 46 | uint32_t components; 47 | int intensity; 48 | int graticule_lines, graticule_lines_prev; 49 | }; 50 | 51 | static void wvs_update(void *, obs_data_t *); 52 | static void wvs_surface_cb(void *data, struct cm_surface_data *surface_data); 53 | 54 | static const char *wvs_get_name(void *unused) 55 | { 56 | UNUSED_PARAMETER(unused); 57 | return obs_module_text("Waveform"); 58 | } 59 | 60 | static void *wvs_create(obs_data_t *settings, obs_source_t *source) 61 | { 62 | struct wvs_source *src = bzalloc(sizeof(struct wvs_source)); 63 | 64 | cm_create(&src->cm, settings, source); 65 | cm_request(&src->cm, wvs_surface_cb, src); 66 | 67 | obs_enter_graphics(); 68 | src->effect = create_effect_from_module_file("waveform.effect"); 69 | obs_leave_graphics(); 70 | 71 | wvs_update(src, settings); 72 | 73 | return src; 74 | } 75 | 76 | static void wvs_destroy(void *data) 77 | { 78 | struct wvs_source *src = data; 79 | 80 | obs_enter_graphics(); 81 | gs_texture_destroy(src->tex_wv); 82 | gs_vertexbuffer_destroy(src->graticule_line_vbuf); 83 | obs_leave_graphics(); 84 | 85 | cm_destroy(&src->cm); 86 | 87 | bfree(src->tex_buf[0]); 88 | bfree(src->tex_buf[1]); 89 | 90 | bfree(src); 91 | } 92 | 93 | static void wvs_update(void *data, obs_data_t *settings) 94 | { 95 | struct wvs_source *src = data; 96 | cm_update(&src->cm, settings); 97 | 98 | src->display = (int)obs_data_get_int(settings, "display"); 99 | 100 | src->components = (uint32_t)obs_data_get_int(settings, "components"); 101 | src->cm.flags = (src->components & COMP_RGB ? CM_FLAG_CONVERT_RGB : 0) | 102 | (src->components & COMP_YUV ? CM_FLAG_CONVERT_YUV : 0); 103 | 104 | src->intensity = (int)obs_data_get_int(settings, "intensity"); 105 | if (src->intensity < 1) 106 | src->intensity = 1; 107 | 108 | src->graticule_lines = (int)obs_data_get_int(settings, "graticule_lines"); 109 | } 110 | 111 | static void wvs_get_defaults(obs_data_t *settings) 112 | { 113 | obs_data_set_default_int(settings, "target_scale", 2); 114 | obs_data_set_default_int(settings, "intensity", 51); 115 | obs_data_set_default_int(settings, "components", COMP_RGB); 116 | obs_data_set_default_int(settings, "graticule_lines", 5); 117 | } 118 | 119 | static bool components_changed(obs_properties_t *props, obs_property_t *property, obs_data_t *settings) 120 | { 121 | UNUSED_PARAMETER(property); 122 | uint32_t components = settings ? (uint32_t)obs_data_get_int(settings, "components") : 0; 123 | obs_property_t *prop = obs_properties_get(props, "colorspace"); 124 | // TODO: temporarily disable colorspace setting if the target is ROI 125 | bool vis = !!(components & COMP_YUV); 126 | if (vis && is_roi_source_name(obs_data_get_string(settings, "target_name"))) 127 | vis = false; 128 | if (prop) 129 | obs_property_set_visible(prop, vis); 130 | return true; 131 | } 132 | 133 | static obs_properties_t *wvs_get_properties(void *data) 134 | { 135 | struct wvs_source *src = data; 136 | obs_properties_t *props; 137 | obs_property_t *prop; 138 | props = obs_properties_create(); 139 | 140 | cm_get_properties(&src->cm, props); 141 | 142 | prop = obs_properties_add_list(props, "display", obs_module_text("Display"), OBS_COMBO_TYPE_LIST, 143 | OBS_COMBO_FORMAT_INT); 144 | obs_property_list_add_int(prop, obs_module_text("Overlay"), DISP_OVERLAY); 145 | obs_property_list_add_int(prop, obs_module_text("Stack"), DISP_STACK); 146 | obs_property_list_add_int(prop, obs_module_text("Parade"), DISP_PARADE); 147 | 148 | prop = obs_properties_add_list(props, "components", obs_module_text("Components"), OBS_COMBO_TYPE_LIST, 149 | OBS_COMBO_FORMAT_INT); 150 | obs_property_set_modified_callback(prop, components_changed); 151 | obs_property_list_add_int(prop, obs_module_text("RGB"), COMP_RGB); 152 | obs_property_list_add_int(prop, obs_module_text("Luma"), COMP_Y); 153 | obs_property_list_add_int(prop, obs_module_text("Chroma"), COMP_UV); 154 | obs_property_list_add_int(prop, obs_module_text("YUV"), COMP_YUV); 155 | 156 | // TODO: Disable this property if ROI target is selected. 157 | properties_add_colorspace(props, "colorspace", obs_module_text("Color space")); 158 | 159 | obs_properties_add_int(props, "intensity", obs_module_text("Intensity"), 1, 255, 1); 160 | prop = obs_properties_add_list(props, "graticule_lines", obs_module_text("Graticule"), OBS_COMBO_TYPE_LIST, 161 | OBS_COMBO_FORMAT_INT); 162 | obs_property_list_add_int(prop, obs_module_text("None"), 0); 163 | obs_property_list_add_int(prop, obs_module_text("Graticule.Step.100"), 1); 164 | obs_property_list_add_int(prop, obs_module_text("Graticule.Step.50"), 2); 165 | obs_property_list_add_int(prop, obs_module_text("Graticule.Step.25"), 4); 166 | obs_property_list_add_int(prop, obs_module_text("Graticule.Step.20"), 5); 167 | obs_property_list_add_int(prop, obs_module_text("Graticule.Step.10"), 10); 168 | 169 | return props; 170 | } 171 | 172 | static inline uint32_t n_components(const struct wvs_source *src) 173 | { 174 | uint32_t c = src->components & (COMP_RGB | COMP_YUV); 175 | c = c - ((c >> 1) & 0x55); 176 | c = (c & 0x33) + ((c >> 2) & 0x33); 177 | c = (c & 0x0F) + ((c >> 4) & 0x0F); 178 | return c; 179 | } 180 | 181 | static uint32_t wvs_get_width(void *data) 182 | { 183 | struct wvs_source *src = data; 184 | if (src->cm.bypass) 185 | return cm_bypass_get_width(&src->cm); 186 | if (src->display == DISP_PARADE) 187 | return src->tex_buf_width[src->r_tex_buf] * n_components(src); 188 | return src->tex_buf_width[src->r_tex_buf]; 189 | } 190 | 191 | static uint32_t wvs_get_height(void *data) 192 | { 193 | struct wvs_source *src = data; 194 | if (src->cm.bypass) 195 | return cm_bypass_get_height(&src->cm); 196 | if (src->display == DISP_STACK) 197 | return WV_SIZE * n_components(src); 198 | return WV_SIZE; 199 | } 200 | 201 | static inline void inc_uint8(uint8_t *c) 202 | { 203 | if (*c < 255) 204 | ++*c; 205 | } 206 | 207 | static inline void ensure_tex_buf_size(struct wvs_source *src, const uint32_t width, int ix) 208 | { 209 | if (src->tex_buf[ix] && src->tex_buf_width[ix] == width) 210 | return; 211 | 212 | if (!width) 213 | return; 214 | 215 | bfree(src->tex_buf[ix]); 216 | src->tex_buf[ix] = bzalloc(width * WV_SIZE * 4); 217 | src->tex_buf_width[ix] = width; 218 | } 219 | 220 | static inline void wvs_draw_waveform(struct wvs_source *src, uint8_t *dbuf, const struct cm_surface_data *surface_data) 221 | { 222 | const uint32_t height = surface_data->height; 223 | const uint32_t width = surface_data->width; 224 | 225 | for (uint32_t i = 0; i < width * WV_SIZE * 4; i++) 226 | dbuf[i] = 0; 227 | 228 | const uint8_t *video_data = NULL; 229 | if (src->components & COMP_RGB) 230 | video_data = surface_data->rgb_data; 231 | else if (src->components & COMP_YUV) 232 | video_data = surface_data->yuv_data; 233 | if (!video_data) 234 | return; 235 | 236 | const bool calc_b = (src->components & 0x11) ? true : false; 237 | const bool calc_g = (src->components & 0x22) ? true : false; 238 | const bool calc_r = (src->components & 0x44) ? true : false; 239 | 240 | for (uint32_t y = 0; y < height; y++) { 241 | const uint8_t *v = video_data + surface_data->linesize * y; 242 | for (uint32_t x = 0; x < width; x++) { 243 | const uint8_t b = *v++; 244 | const uint8_t g = *v++; 245 | const uint8_t r = *v++; 246 | const uint8_t a = *v++; 247 | if (!a) 248 | continue; 249 | if (calc_b) 250 | inc_uint8(dbuf + x * 4 + (WV_SIZE - 1 - b) * width * 4 + 0); 251 | if (calc_g) 252 | inc_uint8(dbuf + x * 4 + (WV_SIZE - 1 - g) * width * 4 + 1); 253 | if (calc_r) 254 | inc_uint8(dbuf + x * 4 + (WV_SIZE - 1 - r) * width * 4 + 2); 255 | } 256 | } 257 | } 258 | 259 | static void wvs_set_image(struct wvs_source *src, const uint8_t *tex_buf, uint32_t width) 260 | { 261 | if (src->tex_wv && src->tex_wv_width == width) { 262 | gs_texture_set_image(src->tex_wv, tex_buf, width * 4, false); 263 | return; 264 | } 265 | 266 | if (src->tex_wv) 267 | gs_texture_destroy(src->tex_wv); 268 | src->tex_wv = gs_texture_create(width, WV_SIZE, GS_BGRX, 1, &tex_buf, GS_DYNAMIC); 269 | src->tex_wv_width = width; 270 | } 271 | 272 | static void wvs_surface_cb(void *data, struct cm_surface_data *surface_data) 273 | { 274 | struct wvs_source *src = data; 275 | 276 | if ((src->components & COMP_RGB) && !surface_data->rgb_data) 277 | return; 278 | if ((src->components & COMP_YUV) && !surface_data->yuv_data) 279 | return; 280 | if (!surface_data->width) 281 | return; 282 | 283 | ensure_tex_buf_size(src, surface_data->width, src->w_tex_buf); 284 | 285 | PROFILE_START(prof_draw_waveform_name); 286 | wvs_draw_waveform(src, src->tex_buf[src->w_tex_buf], surface_data); 287 | PROFILE_END(prof_draw_waveform_name); 288 | src->w_tex_buf ^= 1; 289 | } 290 | 291 | static void create_graticule_vbuf(struct wvs_source *src) 292 | { 293 | obs_enter_graphics(); 294 | gs_vertexbuffer_destroy(src->graticule_line_vbuf); 295 | src->graticule_line_vbuf = NULL; 296 | if (src->graticule_lines > 0) { 297 | gs_render_start(true); 298 | for (int i = 0; i <= src->graticule_lines; i++) { 299 | gs_vertex2f(0.0f, 256.0f * i / src->graticule_lines); 300 | gs_vertex2f(1.0f, 256.0f * i / src->graticule_lines); 301 | } 302 | src->graticule_line_vbuf = gs_render_save(); 303 | } 304 | obs_leave_graphics(); 305 | } 306 | 307 | static void wvs_render_graticule(struct wvs_source *src) 308 | { 309 | gs_effect_t *effect = obs_get_base_effect(OBS_EFFECT_SOLID); 310 | gs_effect_set_color(gs_effect_get_param_by_name(effect, "color"), 0x80FFBF00); // amber 311 | while (gs_effect_loop(effect, "Solid")) { 312 | bool stack = src->display == DISP_STACK; 313 | bool parade = src->display == DISP_PARADE; 314 | int n_stack = stack ? n_components(src) : 1; 315 | for (int i = 0; i < n_stack; i++) { 316 | const float yoff = stack ? WV_SIZE * i + 0.5f : 0.0f; 317 | const float xcoe = 318 | (float)(src->tex_buf_width[src->r_tex_buf] * (parade ? n_components(src) : 1)); 319 | struct matrix4 tr = { 320 | {.ptr = {xcoe, 0.0f, 0.0f, 0.0f}}, 321 | {.ptr = {0.0f, 1.0f, 0.0f, 0.0f}}, 322 | {.ptr = {0.0f, 0.0f, 1.0f, 0.0f}}, 323 | {.ptr = {0.0f, yoff, 0.0f, 1.0f}}, 324 | }; 325 | gs_matrix_push(); 326 | gs_matrix_mul(&tr); 327 | gs_load_vertexbuffer(src->graticule_line_vbuf); 328 | gs_draw(GS_LINES, stack && i ? 2 : 0, 0); 329 | gs_matrix_pop(); 330 | } 331 | } 332 | } 333 | 334 | static void render_waveform(struct wvs_source *src) 335 | { 336 | gs_effect_t *effect = src->effect ? src->effect : obs_get_base_effect(OBS_EFFECT_DEFAULT); 337 | gs_effect_set_texture(gs_effect_get_param_by_name(effect, "image"), src->tex_wv); 338 | gs_effect_set_float(gs_effect_get_param_by_name(effect, "intensity"), (float)src->intensity); 339 | const char *name = "Draw"; 340 | int w = src->tex_wv_width; 341 | int h = WV_SIZE; 342 | int n = n_components(src); 343 | if (src->effect) 344 | switch (src->display) { 345 | case DISP_STACK: 346 | name = n == 3 ? "DrawStack" : n == 2 ? "DrawStackUV" : "DrawOverlay"; 347 | h *= n; 348 | break; 349 | case DISP_PARADE: 350 | name = n == 3 ? "DrawParade" : n == 2 ? "DrawParadeUV" : "DrawOverlay"; 351 | w *= n; 352 | break; 353 | default: 354 | name = "DrawOverlay"; 355 | break; 356 | } 357 | 358 | while (gs_effect_loop(effect, name)) 359 | gs_draw_sprite(src->tex_wv, 0, w, h); 360 | } 361 | 362 | static void wvs_render(void *data, gs_effect_t *effect) 363 | { 364 | UNUSED_PARAMETER(effect); 365 | struct wvs_source *src = data; 366 | if (src->cm.bypass) { 367 | cm_bypass_render(&src->cm); 368 | return; 369 | } 370 | PROFILE_START(prof_render_name); 371 | 372 | cm_render_target(&src->cm); 373 | 374 | PROFILE_START(prof_draw_name); 375 | if (src->tex_buf[src->r_tex_buf]) { 376 | wvs_set_image(src, src->tex_buf[src->r_tex_buf], src->tex_buf_width[src->r_tex_buf]); 377 | render_waveform(src); 378 | } 379 | PROFILE_END(prof_draw_name); 380 | 381 | PROFILE_START(prof_draw_graticule_name); 382 | if (src->graticule_lines > 0) { 383 | if (src->graticule_lines != src->graticule_lines_prev) { 384 | create_graticule_vbuf(src); 385 | src->graticule_lines_prev = src->graticule_lines; 386 | } 387 | wvs_render_graticule(src); 388 | } 389 | PROFILE_END(prof_draw_graticule_name); 390 | 391 | PROFILE_END(prof_render_name); 392 | } 393 | 394 | static void wvs_tick(void *data, float second) 395 | { 396 | struct wvs_source *src = data; 397 | cm_tick(data, second); 398 | 399 | src->r_tex_buf = src->w_tex_buf ^ 1; 400 | } 401 | 402 | const struct obs_source_info colormonitor_waveform = { 403 | .id = "waveform_source", 404 | .type = OBS_SOURCE_TYPE_INPUT, 405 | .output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW, 406 | .get_name = wvs_get_name, 407 | .create = wvs_create, 408 | .destroy = wvs_destroy, 409 | .update = wvs_update, 410 | .get_defaults = wvs_get_defaults, 411 | .get_properties = wvs_get_properties, 412 | .get_width = wvs_get_width, 413 | .get_height = wvs_get_height, 414 | .enum_active_sources = cm_enum_sources, 415 | .video_render = wvs_render, 416 | .video_tick = wvs_tick, 417 | }; 418 | --------------------------------------------------------------------------------