Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ Read [the blog article](https://taublast.github.io/posts/FiltersCamera/) 👈
### Latest Changes

* Fixed camera album creation/permission on iOS 26+
* Use latest camera nuget with much better performance and bug fixes
* Use latest camera nuget with better performance and bug fixes
* Smooth filters menu

### Install

Expand Down
11 changes: 10 additions & 1 deletion dev/CameraApp-Refs.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11201.2 d18.0
VisualStudioVersion = 18.0.11201.2
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Refs", "Refs", "{5B1CDC4F-5ED6-4662-8EC6-3DE3FF0B05BE}"
EndProject
Expand All @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
..\.gitignore = ..\.gitignore
..\README.md = ..\README.md
..\..\DrawnUi.Maui\src\Maui\Addons\DrawnUi.Maui.Camera\README.md = ..\..\DrawnUi.Maui\src\Maui\Addons\DrawnUi.Maui.Camera\README.md
..\..\ShadersCam.targets = ..\..\ShadersCam.targets
EndProjectSection
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "DrawnUi.Shared", "..\..\DrawnUi.Maui\src\Shared\DrawnUi.Shared.shproj", "{83974207-9636-48DD-BDB3-98EDECBB1107}"
Expand All @@ -24,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShadersCarouselDemo", "..\.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastRepro", "..\..\DrawnUi.Maui\src\Maui\Samples\FastRepro\FastRepro.csproj", "{AF62CF62-A472-E87B-7225-8BD178AC3DF2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox", "..\..\DrawnUi.Maui\src\Maui\Samples\Sandbox\Sandbox.csproj", "{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,6 +46,7 @@ Global
{9F71E169-6D24-B132-9826-8A6DA9FFFBC8}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{9F71E169-6D24-B132-9826-8A6DA9FFFBC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F71E169-6D24-B132-9826-8A6DA9FFFBC8}.Release|Any CPU.Build.0 = Release|Any CPU
{9F71E169-6D24-B132-9826-8A6DA9FFFBC8}.Release|Any CPU.Deploy.0 = Release|Any CPU
{A53734E6-1896-F775-5EEA-1A175FDA2B28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A53734E6-1896-F775-5EEA-1A175FDA2B28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A53734E6-1896-F775-5EEA-1A175FDA2B28}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
Expand All @@ -54,6 +58,10 @@ Global
{AF62CF62-A472-E87B-7225-8BD178AC3DF2}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{AF62CF62-A472-E87B-7225-8BD178AC3DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF62CF62-A472-E87B-7225-8BD178AC3DF2}.Release|Any CPU.Build.0 = Release|Any CPU
{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -64,6 +72,7 @@ Global
{93E119B1-4378-87DF-2DD2-A818D1E6C2A2} = {5B1CDC4F-5ED6-4662-8EC6-3DE3FF0B05BE}
{A53734E6-1896-F775-5EEA-1A175FDA2B28} = {5B1CDC4F-5ED6-4662-8EC6-3DE3FF0B05BE}
{AF62CF62-A472-E87B-7225-8BD178AC3DF2} = {5B1CDC4F-5ED6-4662-8EC6-3DE3FF0B05BE}
{9E8CD945-AB2A-2FEF-D962-CBDC4A7248EF} = {5B1CDC4F-5ED6-4662-8EC6-3DE3FF0B05BE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {329E3D0C-A3F7-4A3E-B61C-6B2D1BD7F708}
Expand Down
2 changes: 1 addition & 1 deletion src/app/Helpers/UserSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public UserSettings()
Formats = new();
Fill = false;
Lang = "en";
Filter = "Movie";
Filter = "Street Zoom";
}

public string Lang { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/app/Platforms/Android/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.21" android:versionCode="102102">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.4" android:versionCode="104101">
<application android:allowBackup="true" android:largeHeap="true" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true">
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.appomobi.drawnui.shaderscam.provider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
Expand Down
4 changes: 3 additions & 1 deletion src/app/Platforms/iOS/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleVersion</key>
<string>104001</string>
<string>141002</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
Expand All @@ -25,6 +25,8 @@
<array>
<string>arm64</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
Expand Down
135 changes: 135 additions & 0 deletions src/app/Resources/Raw/Shaders/ripples.sksl
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
uniform float4 iMouse; // Mouse drag pos=.xy Click pos=.zw (pixels)
uniform float iTime; // Shader playback time (s)
uniform float2 iResolution; // Viewport resolution (pixels)
uniform float2 iImageResolution; // iImage1 resolution (pixels)
uniform shader iImage1; // Texture
uniform shader iImage2; // Texture for reflection
uniform float2 iOffset; // Top-left corner of DrawingRect
uniform float2 iOrigin; // Mouse drag started here

uniform float2 origins[10]; // Array for multiple mouse positions
uniform float progresses[10]; // Array for multiple animation progresses (0.0 -> 1.0)

/*
This shader is ported from the original Apple shader presented at WWDC 2024.
For more details, see the session here: https://developer.apple.com/videos/play/wwdc2024/10151/
Credit to Apple for the original implementation.
Credits to Raouf Rahiche for the GLSL single ripple version that was used to create this SKSL.
SKSL port + multi-ripples + reflection by Nick Kovalsky
*/


const float duration = 5.0; // This is the solved animation duration at speed 1
const float amplitude = 0.015; // Default amplitude of the ripple
const float frequency = 15.0; // Default frequency of the ripple
const float decay = 2.0; // Default decay rate of the ripple
const float speed = 0.8; // Default speed of the ripple
const float rippleIntensity = 0.05;

const float reflectionIntensity = 0.15;
const float minReflectionIntensity = 1.5; // minimum reflection visible

const vec3 waterTint = vec3(0.1, 0.2, 0.5);

// Define separate reflection angles to simulate 3D viewing perspective
const float reflectionAngleX = 5.0; // viewing perspective on X axis
const float reflectionAngleY = 5.0; // viewing perspective on Y axis
const float reflectionAngleZ = 1.0; // viewing perspective on Z axis

half4 main(float2 fragCoord)
{
// Precompute the scale factor
float2 renderingScale = iImageResolution.xy / iResolution.xy;
float2 inputCoord = (fragCoord - iOffset) * renderingScale;
vec2 uv = (fragCoord - iOffset) / iResolution.xy;

// Sample the base color
half4 baseColor = iImage1.eval(inputCoord);

// Initialize the combined displacement vector
vec2 combinedDisplacement = vec2(0.0, 0.0);

// Loop through the ripples
for (int i = 0; i < 10; i++)
{
float progress = progresses[i];
vec2 mouse = origins[i];

if (progress >= 0.0 && progress <= 1.0)
{
// Get the cursor position and normalize it
vec2 origin = mouse / iResolution.xy;

// Calculate the distance and direction from the origin
vec2 direction = uv - origin;
float distance = length(direction);

// Calculate the delay based on the distance
float delay = distance / speed;

// Adapt the time for the delay and clamp to 0
float time = max(0.0, progress * duration - delay);

// Calculate the ripple amount
float rippleAmount = amplitude * sin(frequency * time) * exp(-decay * time);

// Normalize the direction vector
vec2 n = direction / distance;

// Accumulate the displacement caused by this ripple
combinedDisplacement += rippleAmount * n;
Comment on lines +76 to +80
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vec2 n = direction / distance; will divide by zero when distance == 0 (e.g., fragment exactly at the ripple origin), producing NaNs that can corrupt the entire shader output. Add a small epsilon check (skip or clamp when distance is ~0).

Suggested change
// Normalize the direction vector
vec2 n = direction / distance;
// Accumulate the displacement caused by this ripple
combinedDisplacement += rippleAmount * n;
// Normalize the direction vector, guarding against zero distance
if (distance > 1e-6)
{
vec2 n = direction / distance;
// Accumulate the displacement caused by this ripple
combinedDisplacement += rippleAmount * n;
}

Copilot uses AI. Check for mistakes.
}
}

// Calculate the final position by applying the combined displacement
vec2 finalPosition = uv + combinedDisplacement;

// Sample the texture at the new combined position
vec3 finalColor = iImage1.eval(finalPosition * iResolution.xy * renderingScale).rgb;

// Define viewing direction using reflection angles
vec2 viewingDirection = normalize(vec2(reflectionAngleX, reflectionAngleY));

// Calculate Fresnel effect
float fresnelEffect = pow(1.0 - dot(normalize(combinedDisplacement), viewingDirection), 3.0);

// Adapt reflection intensity with Fresnel effect
float reflectionFactor = fresnelEffect * clamp(length(combinedDisplacement) / amplitude, 0.0, 1.0);

// Ensure minimum reflection intensity
reflectionFactor = max(reflectionFactor, minReflectionIntensity);

Comment on lines +97 to +101
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reflectionFactor is not clamped and is forced to be at least 1.5. Since it’s later multiplied by reflectionIntensity and passed to mix(...), the blend factor can exceed 1.0, causing extrapolated colors/artifacts. Consider keeping the blend factor in [0,1] (e.g., clamp the final mix factor) and use a minimum closer to 0..1 if you need a baseline reflection.

Copilot uses AI. Check for mistakes.
// Calculate dynamic perturbation factor based on combined displacement
float dynamicPerturbationFactor = length(combinedDisplacement) * 10000.0; // Adapt factor based on ripple strength

// Calculate perturbation
vec2 perturbation = vec2(sin(finalPosition.x * dynamicPerturbationFactor), cos(finalPosition.y * dynamicPerturbationFactor)) * 0.05;
Comment on lines +103 to +106
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When combinedDisplacement is 0, dynamicPerturbationFactor becomes 0 and perturbation evaluates to (0, 0.05) because cos(0) == 1, introducing a constant distortion even with no ripples. Consider scaling perturbation by ripple strength (e.g., length(combinedDisplacement)), or otherwise ensure it becomes zero when there’s no active ripple.

Suggested change
float dynamicPerturbationFactor = length(combinedDisplacement) * 10000.0; // Adapt factor based on ripple strength
// Calculate perturbation
vec2 perturbation = vec2(sin(finalPosition.x * dynamicPerturbationFactor), cos(finalPosition.y * dynamicPerturbationFactor)) * 0.05;
float rippleStrength = length(combinedDisplacement);
float dynamicPerturbationFactor = rippleStrength * 10000.0; // Adapt factor based on ripple strength
// Calculate perturbation, scaled by ripple strength so it vanishes when there is no displacement
vec2 perturbation = vec2(sin(finalPosition.x * dynamicPerturbationFactor), cos(finalPosition.y * dynamicPerturbationFactor)) * 0.05 * rippleStrength;

Copilot uses AI. Check for mistakes.

// Calculate 3D angle-based offsets
vec2 angleOffsetX = vec2(reflectionAngleX * combinedDisplacement.y, reflectionAngleX * combinedDisplacement.x);
vec2 angleOffsetY = vec2(reflectionAngleY * combinedDisplacement.y, reflectionAngleY * combinedDisplacement.x);
vec2 angleOffsetZ = vec2(reflectionAngleZ * combinedDisplacement.y, reflectionAngleZ * combinedDisplacement.x);

// Combine all 3D offsets
vec2 angleOffset = angleOffsetX + angleOffsetY + angleOffsetZ;

// Apply angle offset and perturbation to get final distorted coordinates for reflection
vec2 distortedCoord = finalPosition + angleOffset + perturbation;

// Sample the distorted reflection texture
vec3 reflectionColor = iImage2.eval(distortedCoord * iResolution.xy * renderingScale).rgb;

// Tint the reflection color based on water color
vec3 tintedReflectionColor = mix(reflectionColor, reflectionColor * waterTint, 0.5);

// Lighten or darken the color based on the combined ripple amount
finalColor += rippleIntensity * (length(combinedDisplacement) / amplitude);

// Blend the reflection with the base color
finalColor = mix(finalColor, tintedReflectionColor, reflectionFactor * reflectionIntensity);

// Set the fragment color
half4 fragColor = vec4(finalColor, 1.0);

return fragColor;
}
16 changes: 7 additions & 9 deletions src/app/ShadersCamera.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>

<!--<TargetFrameworks>net9.0-ios;</TargetFrameworks>-->

<!--<TargetFrameworks>net9.0-android;</TargetFrameworks>-->

<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
Expand Down Expand Up @@ -37,11 +36,6 @@
<Warning Text="------ Building $(TargetFramework) with DrawnUi project reference ------" />
</Target>

<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net*-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
<CodesignKey>iPhone Developer</CodesignKey>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
<Optimize>True</Optimize>
<AndroidEnableSGenConcurrent>True</AndroidEnableSGenConcurrent>
Expand All @@ -55,6 +49,12 @@
<AndroidEnableMultiDex>True</AndroidEnableMultiDex>
</PropertyGroup>

<!--DEBUG-->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net*-ios|AnyCPU'">
<CreatePackage>false</CreatePackage>
<CodesignKey>iPhone Developer</CodesignKey>
</PropertyGroup>

<!--dev personal provisioning for iPhone-->
<Import Project="../../../ShadersCam.targets" Condition="Exists('../../../ShadersCam.targets')" />

Expand Down Expand Up @@ -91,14 +91,12 @@

</ItemGroup>


<!--<ItemGroup>
<ProjectReference Include="..\..\..\DrawnUi.Maui\src\Maui\Addons\DrawnUi.Maui.Camera\DrawnUi.Maui.Camera.csproj" />
<ProjectReference Include="..\..\..\DrawnUi.Maui\src\Maui\DrawnUi\DrawnUi.Maui.csproj" />
</ItemGroup>-->

<ItemGroup>
<PackageReference Include="DrawnUi.Maui.Camera" Version="1.7.7.4" />
<PackageReference Include="DrawnUi.Maui.Camera" Version="1.9.1.1" />
<PackageReference Include="AppoMobi.Maui.FastPopups" Version="1.2.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.70" />
Expand Down
14 changes: 13 additions & 1 deletion src/app/Views/Controls/CameraWithEffects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ static void InitializeAvailableShaders()
}

private SkiaShaderEffect _shader;

private SkiaShaderEffect _shaderGlobal;
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_shaderGlobal is declared but never used (the only usage is in commented-out code). This will produce an unused-field warning and adds dead code; please remove the field (and the commented block) or gate the whole experiment behind a build flag so it’s not in production code paths.

Suggested change
private SkiaShaderEffect _shaderGlobal;

Copilot uses AI. Check for mistakes.
public void ChangeShaderCode(string code)
{
if (Display == null || _shader==null)
Expand Down Expand Up @@ -95,6 +95,18 @@ protected virtual void SetCustomShader(ShaderItem shader)
return;
}

//just having fun, add ripples to preview
/*
if (_shaderGlobal == null)
{
_shaderGlobal = new MultiRippleWithTouchEffect()
{
SecondarySource="Images/logo.png"
};
VisualEffects.Add(_shaderGlobal);
}
*/

// Remove existing shader if any
if (_shader != null && VisualEffects.Contains(_shader))
{
Expand Down
5 changes: 4 additions & 1 deletion src/app/Views/Controls/ClippedShaderEffect.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using AppoMobi.Maui.Gestures;
using System.Collections.Concurrent;

Comment on lines +1 to +3
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The added using AppoMobi.Maui.Gestures; and using System.Collections.Concurrent; directives are unused in this file. Please remove them to avoid unnecessary imports and compiler warnings (CS8019).

Suggested change
using AppoMobi.Maui.Gestures;
using System.Collections.Concurrent;

Copilot uses AI. Check for mistakes.
namespace ShadersCamera.Views.Controls;

/// <summary>
Expand Down Expand Up @@ -30,4 +33,4 @@ public override void Render(DrawingContext ctx)
base.Render(ctx.WithDestination(clipped));
}
}
}
}
Loading
Loading