diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..951d918 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..8870c24 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## What changed + +- + +## Verification + +- [ ] `msbuild SampleProject\UsbCameraSample.sln /m /p:Configuration=Debug /p:Platform="Any CPU"` +- [ ] `msbuild SampleProject\UsbCameraSample.sln /m /p:Configuration=Release /p:Platform="Any CPU"` +- [ ] `Tests\UsbCamera.Tests\bin\Debug\UsbCamera.Tests.exe` +- [ ] Camera-dependent behavior was tested manually or explicitly not touched + +## Review focus + +- [ ] `UsbCamera.cs` API behavior remains backward compatible +- [ ] Sample projects still build without external packages +- [ ] Generated `bin` / `obj` artifacts are not committed diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1d6e412 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: + - master + - codex/add-github-actions-ci + pull_request: + +jobs: + build-samples: + name: Build sample projects + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + configuration: [Debug, Release] + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Build solution + run: msbuild SampleProject\UsbCameraSample.sln /m /p:Configuration=${{ matrix.configuration }} /p:Platform="Any CPU" /p:RestorePackages=false + + - name: Run unit tests + run: Tests\UsbCamera.Tests\bin\${{ matrix.configuration }}\UsbCamera.Tests.exe diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b68668 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Visual Studio build outputs +[Bb]in/ +[Oo]bj/ +.vs/ +*.user +*.suo +*.rsuser + +# Logs and generated diagnostics +*.log +*.binlog +TestResult.xml +[Tt]est[Rr]esult*/ diff --git a/README.md b/README.md index f37ae1b..7a14e66 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ C# source code for using usb camera and web camera in WinForms/WPF. With only single CSharp source code. No external library required. +Sample projects are verified by GitHub Actions on every push and pull request. + # How to use Add UsbCamera.cs to your project. Following is how to use. see also [SampleProject](https://github.com/secile/UsbCamera/tree/master/SampleProject). diff --git a/SampleProject/UsbCameraSample.sln b/SampleProject/UsbCameraSample.sln index 669c9d2..cc95c25 100644 --- a/SampleProject/UsbCameraSample.sln +++ b/SampleProject/UsbCameraSample.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsbCameraWpf", "UsbCameraWp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsbCameraByteArray", "UsbCameraByteArray\UsbCameraByteArray.csproj", "{B0F887EF-0F54-4176-9E3C-2BCB4B3C8667}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UsbCamera.Tests", "..\Tests\UsbCamera.Tests\UsbCamera.Tests.csproj", "{C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +29,10 @@ Global {B0F887EF-0F54-4176-9E3C-2BCB4B3C8667}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0F887EF-0F54-4176-9E3C-2BCB4B3C8667}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0F887EF-0F54-4176-9E3C-2BCB4B3C8667}.Release|Any CPU.Build.0 = Release|Any CPU + {C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tests/UsbCamera.Tests/Program.cs b/Tests/UsbCamera.Tests/Program.cs new file mode 100644 index 0000000..8a7e7d1 --- /dev/null +++ b/Tests/UsbCamera.Tests/Program.cs @@ -0,0 +1,83 @@ +using System; +using Camera = GitHub.secile.Video.UsbCamera; + +namespace UsbCamera.Tests +{ + internal static class Program + { + private static int Main() + { + try + { + Rgb24SetAndGetRoundTripsBottomUpCoordinates(); + Y800SetAndGetRoundTripsTopDownCoordinates(); + Y16SetAndGetRoundTripsCommonBitDepths(); + VideoFormatDefaultHasStableLabel(); + Console.WriteLine("All UsbCamera.Tests passed."); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex); + return 1; + } + } + + private static void Rgb24SetAndGetRoundTripsBottomUpCoordinates() + { + const int width = 2; + const int height = 2; + var buffer = new byte[width * height * 3]; + + Camera.ByteArrayUtility.RGB24.SetValue(buffer, width, height, 1, 0, 0x112233); + + AssertEqual(0x112233, Camera.ByteArrayUtility.RGB24.GetValue(buffer, width, height, 1, 0), "RGB24 round trip"); + AssertEqual(0x33, buffer[9], "RGB24 blue byte"); + AssertEqual(0x22, buffer[10], "RGB24 green byte"); + AssertEqual(0x11, buffer[11], "RGB24 red byte"); + } + + private static void Y800SetAndGetRoundTripsTopDownCoordinates() + { + const int width = 3; + const int height = 2; + var buffer = new byte[width * height]; + + Camera.ByteArrayUtility.Y800.SetValue(buffer, width, height, 2, 1, 0x7F); + + AssertEqual(0x7F, Camera.ByteArrayUtility.Y800.GetValue(buffer, width, height, 2, 1), "Y800 round trip"); + AssertEqual(0x7F, buffer[5], "Y800 buffer position"); + } + + private static void Y16SetAndGetRoundTripsCommonBitDepths() + { + VerifyY16RoundTrip(8, 0xAB); + VerifyY16RoundTrip(10, 0x02AA); + VerifyY16RoundTrip(12, 0x0ABC); + } + + private static void VerifyY16RoundTrip(int bits, ushort value) + { + const int width = 2; + const int height = 2; + var buffer = new byte[width * height * 2]; + + Camera.ByteArrayUtility.Y16.SetValue(buffer, bits, width, height, 1, 1, value); + + AssertEqual(value, Camera.ByteArrayUtility.Y16.GetValue(buffer, bits, width, height, 1, 1), "Y16 round trip " + bits); + } + + private static void VideoFormatDefaultHasStableLabel() + { + AssertEqual("Default Video Format", Camera.VideoFormat.Default.ToString(), "VideoFormat default label"); + } + + private static void AssertEqual(T expected, T actual, string name) + { + if (!object.Equals(expected, actual)) + { + throw new InvalidOperationException(string.Format("{0}: expected {1}, got {2}", name, expected, actual)); + } + } + } +} diff --git a/Tests/UsbCamera.Tests/UsbCamera.Tests.csproj b/Tests/UsbCamera.Tests/UsbCamera.Tests.csproj new file mode 100644 index 0000000..f02ccb7 --- /dev/null +++ b/Tests/UsbCamera.Tests/UsbCamera.Tests.csproj @@ -0,0 +1,49 @@ + + + + + Debug + AnyCPU + {C9C7F432-6D25-43D8-86D6-04C6ED8CBB2D} + Exe + UsbCamera.Tests + UsbCamera.Tests + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + UsbCamera.cs + + + + + +