diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 2f86a5785..8589c2674 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -2,6 +2,8 @@ name: Build Changed C# Projects for PR on: pull_request: + paths-ignore: + - '**.md' jobs: build: @@ -36,7 +38,7 @@ jobs: run: | $failedProjectCount=0 # Get the list of changed files - $changedFiles = git diff --name-only -r HEAD^1 HEAD + $changedFiles = git diff --name-only -r --diff-filter=d HEAD^1 HEAD $excluded_projects_file="./eng/excluded_projects_" + "${{ runner.os }}".ToLower() + ".txt" $excluded_projects=@() @@ -77,13 +79,15 @@ jobs: } } - # Only proceed when his project has not been built yet - if (-not ($processedProjects -contains $file)) { - if ([string]::IsNullOrEmpty($projectToBuild)) { - Write-Output "::warning:: Found no csproj for file $file" - } - else { - $projectToBuild = (Resolve-Path -Path $projectToBuild -Relative).Replace("\", "/") + if (-not [string]::IsNullOrEmpty($projectToBuild)) { + $projectToBuild = (Resolve-Path -Path $projectToBuild -Relative).Replace("\", "/") + + # Only proceed when this project has not been built yet + if (-not ($processedProjects -contains $projectToBuild)) { + Write-Output "::notice:: $projectToBuild is not in processed builds yet" + + $processedProjects += $projectToBuild + Write-Output "::notice:: Added $projectToBuild to processed builds" if ($excluded_projects -contains $projectToBuild) { Write-Output "::notice:: Skipping build for excluded project: $projectToBuild" @@ -95,7 +99,6 @@ jobs: Write-Output "::group:: Building $projectToBuild" dotnet build $projectToBuild - $processedProjects += $projectToBuild if ($LASTEXITCODE -gt 0) { Write-Output "::error:: Build failed for $projectToBuild" @@ -116,6 +119,9 @@ jobs: } } } + else { + Write-Output "::warning:: Found no csproj for file $file" + } } if ($failedProjectCount -gt 0) { diff --git a/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/MacCatalyst/Info.plist b/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/MacCatalyst/Info.plist index 7fb000a1e..c362a939a 100644 --- a/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/MacCatalyst/Info.plist +++ b/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/MacCatalyst/Info.plist @@ -53,6 +53,10 @@ Camera Photos NSCalendarsUsageDescription Calendar Access + NSCalendarsFullAccessUsageDescription + Full access to the Calendar is needed to sign you up for parties! + NSCalendarsWriteOnlyAccessUsageDescription + Just writing events to the Calendar, nothing to see here. NSLocationAlwaysAndWhenInUseUsageDescription Get Location NSLocationAlwaysUsageDescription diff --git a/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/iOS/Info.plist b/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/iOS/Info.plist index 058e981a1..e82a4c2a9 100644 --- a/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/iOS/Info.plist +++ b/8.0/PlatformIntegration/PlatformIntegrationDemos/PlatformIntegrationDemo/Platforms/iOS/Info.plist @@ -47,6 +47,10 @@ Camera Photos NSCalendarsUsageDescription Calendar Access + NSCalendarsFullAccessUsageDescription + Full access to the Calendar is needed to sign you up for parties! + NSCalendarsWriteOnlyAccessUsageDescription + Just writing events to the Calendar, nothing to see here. NSLocationAlwaysAndWhenInUseUsageDescription Get Location NSLocationAlwaysUsageDescription diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation.sln b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation.sln new file mode 100644 index 000000000..51b3afec8 --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MandelbrotAnimation", "MandelbrotAnimation\MandelbrotAnimation.csproj", "{AADA2731-0754-4634-944E-1DCC3C1374EB}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {AADA2731-0754-4634-944E-1DCC3C1374EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AADA2731-0754-4634-944E-1DCC3C1374EB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9E213343-022C-4D68-9714-5D25F8876EC7} + EndGlobalSection +EndGlobal diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml new file mode 100644 index 000000000..de421799a --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml.cs b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml.cs new file mode 100644 index 000000000..5164215ea --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml.cs @@ -0,0 +1,12 @@ +namespace MandelbrotAnimation; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + + MainPage = new AppShell(); + } +} + diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml new file mode 100644 index 000000000..5609f73a4 --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml.cs b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml.cs new file mode 100644 index 000000000..e43d2a380 --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/AppShell.xaml.cs @@ -0,0 +1,10 @@ +namespace MandelbrotAnimation; + +public partial class AppShell : Shell +{ + public AppShell() + { + InitializeComponent(); + } +} + diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/BitmapInfo.cs b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/BitmapInfo.cs new file mode 100644 index 000000000..d09160536 --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/BitmapInfo.cs @@ -0,0 +1,19 @@ +namespace MandelbrotAnimation +{ + class BitmapInfo + { + public int PixelWidth { get; private set; } + + public int PixelHeight { get; private set; } + + public int[] IterationCounts { get; private set; } + + public BitmapInfo(int pixelWidth, int pixelHeight, int[] iterationCounts) + { + PixelWidth = pixelWidth; + PixelHeight = pixelHeight; + IterationCounts = iterationCounts; + } + } +} + diff --git a/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/MainPage.xaml b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/MainPage.xaml new file mode 100644 index 000000000..5a50f007b --- /dev/null +++ b/8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/MainPage.xaml @@ -0,0 +1,26 @@ + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/SaveFileFormatsPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/SaveFileFormatsPage.xaml.cs new file mode 100644 index 000000000..0bceb5b92 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/SaveFileFormatsPage.xaml.cs @@ -0,0 +1,69 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Bitmaps; + +public partial class SaveFileFormatsPage : ContentPage +{ + SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(typeof(SaveFileFormatsPage), + "SkiaSharpDemos.Media.monkeyface.png"); + + public SaveFileFormatsPage() + { + InitializeComponent(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + args.Surface.Canvas.DrawBitmap(bitmap, args.Info.Rect, BitmapStretch.Uniform); + } + + void OnFormatPickerChanged(object sender, EventArgs args) + { + if (formatPicker.SelectedIndex != -1) + { + SKEncodedImageFormat imageFormat = (SKEncodedImageFormat)formatPicker.SelectedItem; + fileNameEntry.Text = Path.ChangeExtension(fileNameEntry.Text, imageFormat.ToString()); + statusLabel.Text = "OK"; + } + } + + async void OnButtonClicked(object sender, EventArgs args) + { + if (formatPicker.SelectedIndex != -1 && fileNameEntry.Text.Length != 0) + { + SKEncodedImageFormat imageFormat = (SKEncodedImageFormat)formatPicker.SelectedItem; + int quality = (int)qualitySlider.Value; + + using (MemoryStream memStream = new MemoryStream()) + using (SKManagedWStream wstream = new SKManagedWStream(memStream)) + { + bitmap.Encode(wstream, imageFormat, quality); + byte[] data = memStream.ToArray(); + + if (data == null) + { + statusLabel.Text = "Encode returned null"; + } + else if (data.Length == 0) + { + statusLabel.Text = "Encode returned empty array"; + } + else + { + var photoLibrary = new PhotoLibrary(); + bool success = await photoLibrary.SavePhotoAsync(data, folderNameEntry.Text, fileNameEntry.Text); + + if (!success) + { + statusLabel.Text = "SavePhotoAsync return false"; + } + else + { + statusLabel.Text = "Success!"; + } + } + } + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml new file mode 100644 index 000000000..f20d43a71 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml @@ -0,0 +1,58 @@ + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml.cs new file mode 100644 index 000000000..8c8dc8935 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/ScalingModesPage.xaml.cs @@ -0,0 +1,41 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Bitmaps; + +public partial class ScalingModesPage : ContentPage +{ + SKBitmap bitmap = + BitmapExtensions.LoadBitmapResource(typeof(ScalingModesPage), + "SkiaSharpDemos.Media.banana.jpg"); + public ScalingModesPage() + { + InitializeComponent(); + + stretchPicker.SelectedIndex = 0; + horizontalPicker.SelectedIndex = 0; + verticalPicker.SelectedIndex = 0; + } + + private void OnPickerSelectedIndexChanged(object sender, EventArgs args) + { + canvasView.InvalidateSurface(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + SKRect dest = new SKRect(0, 0, info.Width, info.Height); + + BitmapStretch stretch = (BitmapStretch)stretchPicker.SelectedItem; + BitmapAlignment horizontal = (BitmapAlignment)horizontalPicker.SelectedItem; + BitmapAlignment vertical = (BitmapAlignment)verticalPicker.SelectedItem; + + canvas.DrawBitmap(bitmap, dest, stretch, horizontal, vertical); + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/UniformScalingPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/UniformScalingPage.cs new file mode 100644 index 000000000..4cdac605e --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Bitmaps/UniformScalingPage.cs @@ -0,0 +1,40 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Bitmaps +{ + public class UniformScalingPage : ContentPage + { + SKBitmap bitmap = + BitmapExtensions.LoadBitmapResource(typeof(UniformScalingPage), + "SkiaSharpDemos.Media.banana.jpg"); + public UniformScalingPage() + { + Title = "Uniform Scaling"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float scale = Math.Min((float)info.Width / bitmap.Width, + (float)info.Height / bitmap.Height); + float x = (info.Width - scale * bitmap.Width) / 2; + float y = (info.Height - scale * bitmap.Height) / 2; + SKRect destRect = new SKRect(x, y, x + scale * bitmap.Width, + y + scale * bitmap.Height); + + canvas.DrawBitmap(bitmap, destRect); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml new file mode 100644 index 000000000..1416daeaf --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml.cs new file mode 100644 index 000000000..c111994ef --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AngleArcPage.xaml.cs @@ -0,0 +1,55 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class AngleArcPage : ContentPage +{ + SKPaint outlinePaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 3, + Color = SKColors.Black + }; + + SKPaint arcPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 15, + Color = SKColors.Red + }; + + public AngleArcPage() + { + InitializeComponent(); + } + + void sliderValueChanged(object sender, ValueChangedEventArgs args) + { + if (canvasView != null) + { + canvasView.InvalidateSurface(); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100); + float startAngle = (float)startAngleSlider.Value; + float sweepAngle = (float)sweepAngleSlider.Value; + + canvas.DrawOval(rect, outlinePaint); + + using (SKPath path = new SKPath()) + { + path.AddArc(rect, startAngle, sweepAngle); + canvas.DrawPath(path, arcPaint); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnimatedDottedTextPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnimatedDottedTextPage.cs new file mode 100644 index 000000000..831ac29c0 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnimatedDottedTextPage.cs @@ -0,0 +1,90 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class AnimatedDottedTextPage : ContentPage + { + const string text = "DOTTED"; + const float strokeWidth = 10; + static readonly float[] dashArray = { 0, 2 * strokeWidth }; + + SKCanvasView canvasView; + bool pageIsActive; + + public AnimatedDottedTextPage() + { + Title = "Animated Dotted Text"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Create an SKPaint object to display the text + using (SKPaint textPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = strokeWidth, + StrokeCap = SKStrokeCap.Round, + Color = SKColors.Blue, + }) + { + // Adjust TextSize property so text is 95% of screen width + float textWidth = textPaint.MeasureText(text); + textPaint.TextSize *= 0.95f * info.Width / textWidth; + + // Find the text bounds + SKRect textBounds = new SKRect(); + textPaint.MeasureText(text, ref textBounds); + + // Calculate offsets to center the text on the screen + float xText = info.Width / 2 - textBounds.MidX; + float yText = info.Height / 2 - textBounds.MidY; + + // Animate the phase; t is 0 to 1 every second + TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); + float t = (float)(timeSpan.TotalSeconds % 1 / 1); + float phase = -t * 2 * strokeWidth; + + // Create dotted line effect based on dash array and phase + using (SKPathEffect dashEffect = SKPathEffect.CreateDash(dashArray, phase)) + { + // Set it to the paint object + textPaint.PathEffect = dashEffect; + + // And draw the text + canvas.DrawText(text, xText, yText, textPaint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnotherRoundedHeptagonPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnotherRoundedHeptagonPage.cs new file mode 100644 index 000000000..752599144 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/AnotherRoundedHeptagonPage.cs @@ -0,0 +1,68 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class AnotherRoundedHeptagonPage : ContentPage + { + public AnotherRoundedHeptagonPage() + { + Title = "Another Rounded Heptagon"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + int numVertices = 7; + float radius = 0.45f * Math.Min(info.Width, info.Height); + SKPoint[] vertices = new SKPoint[numVertices]; + double vertexAngle = -0.5f * Math.PI; // straight up + + // Coordinates of the vertices of the polygon + for (int vertex = 0; vertex < numVertices; vertex++) + { + vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle), + radius * (float)Math.Sin(vertexAngle)); + vertexAngle += 2 * Math.PI / numVertices; + } + + float cornerRadius = 100; + + // Create the path + using (SKPath path = new SKPath()) + { + path.AddPoly(vertices, true); + + // Render the path in the center of the screen + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.Color = SKColors.Blue; + paint.StrokeWidth = 10; + + // Set argument to half the desired corner radius! + paint.PathEffect = SKPathEffect.CreateCorner(cornerRadius / 2); + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.DrawPath(path, paint); + + // Uncomment DrawCircle call to verify corner radius + float offset = cornerRadius / (float)Math.Sin(Math.PI * (numVertices - 2) / numVertices / 2); + paint.Color = SKColors.Green; + // canvas.DrawCircle(vertices[0].X, vertices[0].Y + offset, cornerRadius, paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ArcInfinityPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ArcInfinityPage.cs new file mode 100644 index 000000000..87e40e5b8 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ArcInfinityPage.cs @@ -0,0 +1,53 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class ArcInfinityPage : ContentPage + { + public ArcInfinityPage() + { + Title = "Arc Infinity"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath path = new SKPath()) + { + path.LineTo(83, 75); + path.ArcTo(100, 100, 0, SKPathArcSize.Large, SKPathDirection.CounterClockwise, 83, -75); + path.LineTo(-83, 75); + path.ArcTo(100, 100, 0, SKPathArcSize.Large, SKPathDirection.Clockwise, -83, -75); + path.Close(); + + // Use path.TightBounds for coordinates without control points + SKRect pathBounds = path.Bounds; + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(Math.Min(info.Width / pathBounds.Width, + info.Height / pathBounds.Height)); + + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.Color = SKColors.Blue; + paint.StrokeWidth = 5; + + canvas.DrawPath(path, paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml new file mode 100644 index 000000000..7affd4c91 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml @@ -0,0 +1,22 @@ + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml.cs new file mode 100644 index 000000000..817ddbdac --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCircularArcPage.xaml.cs @@ -0,0 +1,116 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class BezierCircularArcPage : ContentPage +{ + SKPaint blackFill = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Black + }; + + SKPaint blackStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 3 + }; + + SKPaint dottedStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 3, + PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0) + }; + + SKPaint redStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Red, + StrokeWidth = 6 + }; + + public BezierCircularArcPage() + { + InitializeComponent(); + + angleSlider.Value = 90; + } + + void OnSliderValueChanged(object sender, ValueChangedEventArgs args) + { + canvasView.InvalidateSurface(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Translate to center + canvas.Translate(info.Width / 2, info.Height / 2); + + // Draw the circle + float radius = Math.Min(info.Width, info.Height) / 3; + canvas.DrawCircle(0, 0, radius, blackStroke); + + // Get the value of the Slider + float angle = (float)angleSlider.Value; + + // Calculate length of control point line + float length = radius * 4 * (float)Math.Tan(Math.PI * angle / 180 / 4) / 3; + + // Calculate sin and cosine for half that angle + float sin = (float)Math.Sin(Math.PI * angle / 180 / 2); + float cos = (float)Math.Cos(Math.PI * angle / 180 / 2); + + // Find the end points + SKPoint point0 = new SKPoint(-radius * sin, radius * cos); + SKPoint point3 = new SKPoint(radius * sin, radius * cos); + + // Find the control points + SKPoint point0Normalized = Normalize(point0); + SKPoint point1 = point0 + new SKPoint(length * point0Normalized.Y, + -length * point0Normalized.X); + + SKPoint point3Normalized = Normalize(point3); + SKPoint point2 = point3 + new SKPoint(-length * point3Normalized.Y, + length * point3Normalized.X); + + // Draw the points + canvas.DrawCircle(point0.X, point0.Y, 10, blackFill); + canvas.DrawCircle(point1.X, point1.Y, 10, blackFill); + canvas.DrawCircle(point2.X, point2.Y, 10, blackFill); + canvas.DrawCircle(point3.X, point3.Y, 10, blackFill); + + // Draw the tangent lines + canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke); + canvas.DrawLine(point3.X, point3.Y, point2.X, point2.Y, dottedStroke); + + // Draw the Bezier curve + using (SKPath path = new SKPath()) + { + path.MoveTo(point0); + path.CubicTo(point1, point2, point3); + canvas.DrawPath(path, redStroke); + } + } + + // Vector methods + SKPoint Normalize(SKPoint v) + { + float magnitude = Magnitude(v); + return new SKPoint(v.X / magnitude, v.Y / magnitude); + } + + float Magnitude(SKPoint v) + { + return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y); + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml new file mode 100644 index 000000000..fabac88c8 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml.cs new file mode 100644 index 000000000..1c26b7650 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierCurvePage.xaml.cs @@ -0,0 +1,61 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class BezierCurvePage : InteractivePage +{ + public BezierCurvePage() + { + touchPoints = new TouchPoint[4]; + + for (int i = 0; i < 4; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(100 + 200 * (i % 2), + 100 + 200 * i) + }; + touchPoints[i] = touchPoint; + } + + InitializeComponent(); + baseCanvasView = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Draw path with cubic Bezier curve + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.CubicTo(touchPoints[1].Center, + touchPoints[2].Center, + touchPoints[3].Center); + + canvas.DrawPath(path, strokePaint); + } + + // Draw tangent lines + canvas.DrawLine(touchPoints[0].Center.X, + touchPoints[0].Center.Y, + touchPoints[1].Center.X, + touchPoints[1].Center.Y, dottedStrokePaint); + + canvas.DrawLine(touchPoints[2].Center.X, + touchPoints[2].Center.Y, + touchPoints[3].Center.X, + touchPoints[3].Center.Y, dottedStrokePaint); + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierInfinityPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierInfinityPage.cs new file mode 100644 index 000000000..b48969566 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/BezierInfinityPage.cs @@ -0,0 +1,56 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class BezierInfinityPage : ContentPage + { + public BezierInfinityPage() + { + Title = "Bezier Infinity"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath path = new SKPath()) + { + path.MoveTo(0, 0); // Center + path.CubicTo(50, -50, 95, -100, 150, -100); // To top of right loop + path.CubicTo(205, -100, 250, -55, 250, 0); // To far right of right loop + path.CubicTo(250, 55, 205, 100, 150, 100); // To bottom of right loop + path.CubicTo(95, 100, 50, 50, 0, 0); // Back to center + path.CubicTo(-50, -50, -95, -100, -150, -100); // To top of left loop + path.CubicTo(-205, -100, -250, -55, -250, 0); // To far left of left loop + path.CubicTo(-250, 55, -205, 100, -150, 100); // To bottom of left loop + path.CubicTo(-95, 100, -50, 50, 0, 0); // Back to center + path.Close(); + + SKRect pathBounds = path.Bounds; + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(0.9f * Math.Min(info.Width / pathBounds.Width, + info.Height / pathBounds.Height)); + + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.Color = SKColors.Blue; + paint.StrokeWidth = 5; + + canvas.DrawPath(path, paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CatsInFramePage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CatsInFramePage.cs new file mode 100644 index 000000000..68771e94a --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CatsInFramePage.cs @@ -0,0 +1,94 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class CatsInFramePage : ContentPage + { + // From PathDataCatPage.cs + SKPath catPath = SKPath.ParseSvgPathData( + "M 160 140 L 150 50 220 103" + // Left ear + "M 320 140 L 330 50 260 103" + // Right ear + "M 215 230 L 40 200" + // Left whiskers + "M 215 240 L 40 240" + + "M 215 250 L 40 280" + + "M 265 230 L 440 200" + // Right whiskers + "M 265 240 L 440 240" + + "M 265 250 L 440 280" + + "M 240 100" + // Head + "A 100 100 0 0 1 240 300" + + "A 100 100 0 0 1 240 100 Z" + + "M 180 170" + // Left eye + "A 40 40 0 0 1 220 170" + + "A 40 40 0 0 1 180 170 Z" + + "M 300 170" + // Right eye + "A 40 40 0 0 1 260 170" + + "A 40 40 0 0 1 300 170 Z"); + + SKPaint catStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 5 + }; + + SKPath scallopPath = SKPath.ParseSvgPathData("M 0 0 L 50 0 A 60 60 0 0 1 -50 0 Z"); + + SKPaint framePaint = new SKPaint + { + Color = SKColors.Black + }; + + public CatsInFramePage() + { + Title = "Cats in Frame"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + // Move (0, 0) point to center of cat path + catPath.Transform(SKMatrix.CreateTranslation(-240, -175)); + + // Now catPath is 400 by 250 + // Scale it down to 160 by 100 + catPath.Transform(SKMatrix.CreateScale(0.40f, 0.40f)); + + // Get the outlines of the contours of the cat path + SKPath outlinedCatPath = new SKPath(); + catStroke.GetFillPath(catPath, outlinedCatPath); + + // Create a 2D path effect from those outlines + SKPathEffect fillEffect = SKPathEffect.Create2DPath( + new SKMatrix + { + ScaleX = 170, + ScaleY = 110, + TransX = 75, + TransY = 80, + Persp2 = 1 + }, + outlinedCatPath); + + // Create a 1D path effect from the scallop path + SKPathEffect strokeEffect = SKPathEffect.Create1DPath(scallopPath, 75, 0, SKPath1DPathEffectStyle.Rotate); + + // Set the sum the effects to frame paint + framePaint.PathEffect = SKPathEffect.CreateSum(fillEffect, strokeEffect); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + SKRect rect = new SKRect(50, 50, info.Width - 50, info.Height - 50); + canvas.ClipRect(rect); + canvas.DrawRect(rect, framePaint); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CharacterOutlineOutlinesPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CharacterOutlineOutlinesPage.cs new file mode 100644 index 000000000..83d92aa99 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CharacterOutlineOutlinesPage.cs @@ -0,0 +1,67 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class CharacterOutlineOutlinesPage : ContentPage + { + public CharacterOutlineOutlinesPage() + { + Title = "Character Outline Outlines"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint textPaint = new SKPaint()) + { + // Set Style for the character outlines + textPaint.Style = SKPaintStyle.Stroke; + + // Set TextSize based on screen size + textPaint.TextSize = Math.Min(info.Width, info.Height); + + // Measure the text + SKRect textBounds = new SKRect(); + textPaint.MeasureText("@", ref textBounds); + + // Coordinates to center text on screen + float xText = info.Width / 2 - textBounds.MidX; + float yText = info.Height / 2 - textBounds.MidY; + + // Get the path for the character outlines + using (SKPath textPath = textPaint.GetTextPath("@", xText, yText)) + { + // Create a new path for the outlines of the path + using (SKPath outlinePath = new SKPath()) + { + // Convert the path to the outlines of the stroked path + textPaint.StrokeWidth = 25; + textPaint.GetFillPath(textPath, outlinePath); + + // Stroke that new path + using (SKPaint outlinePaint = new SKPaint()) + { + outlinePaint.Style = SKPaintStyle.Stroke; + outlinePaint.StrokeWidth = 5; + outlinePaint.Color = SKColors.Red; + + canvas.DrawPath(outlinePath, outlinePaint); + } + } + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CircularTextPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CircularTextPage.cs new file mode 100644 index 000000000..ec9ef059f --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CircularTextPage.cs @@ -0,0 +1,45 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class CircularTextPage : ContentPage + { + const string text = "xt in a circle that shapes the te"; + + public CircularTextPage() + { + Title = "Circular Text"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath circularPath = new SKPath()) + { + float radius = 0.35f * Math.Min(info.Width, info.Height); + circularPath.AddCircle(info.Width / 2, info.Height / 2, radius); + + using (SKPaint textPaint = new SKPaint()) + { + textPaint.TextSize = 100; + float textWidth = textPaint.MeasureText(text); + textPaint.TextSize *= 2 * 3.14f * radius / textWidth; + + canvas.DrawTextOnPath(text, circularPath, 0, 0, textPaint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClipOperationsPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClipOperationsPage.cs new file mode 100644 index 000000000..0d837fd2c --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClipOperationsPage.cs @@ -0,0 +1,90 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class ClipOperationsPage : ContentPage + { + SKPaint textPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Black, + TextSize = 40, + TextAlign = SKTextAlign.Center + }; + + SKPaint fillPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Pink + }; + + public ClipOperationsPage() + { + Title = "Clip Operations"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float x = 0; + float y = 0; + + foreach (SKClipOperation clipOp in Enum.GetValues(typeof(SKClipOperation))) + { + // Portrait mode + if (info.Height > info.Width) + { + DisplayClipOp(canvas, new SKRect(x, y, x + info.Width, y + info.Height / 2), clipOp); + y += info.Height / 2; + } + // Landscape mode + else + { + DisplayClipOp(canvas, new SKRect(x, y, x + info.Width / 2, y + info.Height), clipOp); + x += info.Width / 2; + } + } + } + + void DisplayClipOp(SKCanvas canvas, SKRect rect, SKClipOperation clipOp) + { + float textSize = textPaint.TextSize; + canvas.DrawText(clipOp.ToString(), rect.MidX, rect.Top + textSize, textPaint); + rect.Top += textSize; + + float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2); + float xCenter = rect.MidX; + float yCenter = rect.MidY; + + canvas.Save(); + + using (SKPath path1 = new SKPath()) + { + path1.AddCircle(xCenter - radius / 2, yCenter, radius); + canvas.ClipPath(path1); + + using (SKPath path2 = new SKPath()) + { + path2.AddCircle(xCenter + radius / 2, yCenter, radius); + canvas.ClipPath(path2, clipOp); + + canvas.DrawPaint(fillPaint); + } + } + + canvas.Restore(); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClippingTextPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClippingTextPage.cs new file mode 100644 index 000000000..05ce6002f --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ClippingTextPage.cs @@ -0,0 +1,67 @@ +using System.Reflection; +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class ClippingTextPage : ContentPage + { + SKBitmap bitmap; + + public ClippingTextPage() + { + Title = "Clipping Text"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + string resourceID = "SkiaSharpDemos.Media.pageofcode.png"; + Assembly assembly = GetType().GetTypeInfo().Assembly; + + using (Stream stream = assembly.GetManifestResourceStream(resourceID)) + { + bitmap = SKBitmap.Decode(stream); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(SKColors.Blue); + + using (SKPaint paint = new SKPaint()) + { + paint.Typeface = SKTypeface.FromFamilyName(null, SKFontStyleWeight.Bold, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright); + paint.TextSize = 10; + + using (SKPath textPath = paint.GetTextPath("CODE", 0, 0)) + { + // Set transform to center and enlarge clip path to window height + SKRect bounds; + textPath.GetTightBounds(out bounds); + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(info.Width / bounds.Width, info.Height / bounds.Height); + canvas.Translate(-bounds.MidX, -bounds.MidY); + + // Set the clip path + canvas.ClipPath(textPath); + } + } + + // Reset transforms + canvas.ResetMatrix(); + + // Display bitmap to fill window but maintain aspect ratio + SKRect rect = new SKRect(0, 0, info.Width, info.Height); + canvas.DrawBitmap(bitmap, + rect.AspectFill(new SKSize(bitmap.Width, bitmap.Height))); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml new file mode 100644 index 000000000..f3492d81f --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml @@ -0,0 +1,22 @@ + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml.cs new file mode 100644 index 000000000..6efd2a62f --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCircularArcPage.xaml.cs @@ -0,0 +1,93 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class ConicCircularArcPage : ContentPage +{ + SKPaint blackFill = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Black + }; + + SKPaint blackStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 3 + }; + + SKPaint dottedStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 3, + PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0) + }; + + SKPaint redStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Red, + StrokeWidth = 6 + }; + + public ConicCircularArcPage() + { + InitializeComponent(); + + angleSlider.Value = 90; + } + + void OnSliderValueChanged(object sender, ValueChangedEventArgs args) + { + canvasView.InvalidateSurface(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Translate to center + canvas.Translate(info.Width / 2, info.Height / 2); + + // Draw the circle + float radius = Math.Min(info.Width, info.Height) / 4; + canvas.DrawCircle(0, 0, radius, blackStroke); + + // Get the value of the Slider + float angle = (float)angleSlider.Value; + + // Calculate sin and cosine for half that angle + float sin = (float)Math.Sin(Math.PI * angle / 180 / 2); + float cos = (float)Math.Cos(Math.PI * angle / 180 / 2); + + // Find the points and weight + SKPoint point0 = new SKPoint(-radius * sin, radius * cos); + SKPoint point1 = new SKPoint(0, radius / cos); + SKPoint point2 = new SKPoint(radius * sin, radius * cos); + float weight = cos; + + // Draw the points + canvas.DrawCircle(point0.X, point0.Y, 10, blackFill); + canvas.DrawCircle(point1.X, point1.Y, 10, blackFill); + canvas.DrawCircle(point2.X, point2.Y, 10, blackFill); + + // Draw the tangent lines + canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke); + canvas.DrawLine(point2.X, point2.Y, point1.X, point1.Y, dottedStroke); + + // Draw the conic + using (SKPath path = new SKPath()) + { + path.MoveTo(point0); + path.ConicTo(point1, point2, weight); + canvas.DrawPath(path, redStroke); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml new file mode 100644 index 000000000..fe73ba653 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml @@ -0,0 +1,27 @@ + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml.cs new file mode 100644 index 000000000..abc8d3a9a --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConicCurvePage.xaml.cs @@ -0,0 +1,67 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class ConicCurvePage : InteractivePage +{ + public ConicCurvePage() + { + touchPoints = new TouchPoint[3]; + + for (int i = 0; i < 3; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(100 + 100 * i, + 100 + (i == 1 ? 300 : 0)) + }; + touchPoints[i] = touchPoint; + } + + InitializeComponent(); + baseCanvasView = canvasView; + weightSlider.Value = 0.5; + } + + void OnSliderValueChanged(object sender, ValueChangedEventArgs args) + { + canvasView.InvalidateSurface(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Draw path with conic curve + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.ConicTo(touchPoints[1].Center, + touchPoints[2].Center, + (float)weightSlider.Value); + + canvas.DrawPath(path, strokePaint); + } + + // Draw tangent lines + canvas.DrawLine(touchPoints[0].Center.X, + touchPoints[0].Center.Y, + touchPoints[1].Center.X, + touchPoints[1].Center.Y, dottedStrokePaint); + + canvas.DrawLine(touchPoints[1].Center.X, + touchPoints[1].Center.Y, + touchPoints[2].Center.X, + touchPoints[2].Center.Y, dottedStrokePaint); + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConveyorBeltPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConveyorBeltPage.cs new file mode 100644 index 000000000..126d58844 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ConveyorBeltPage.cs @@ -0,0 +1,136 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class ConveyorBeltPage : ContentPage + { + SKCanvasView canvasView; + bool pageIsActive = false; + + SKPaint conveyerPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 20, + Color = SKColors.DarkGray + }; + + SKPath bucketPath = new SKPath(); + + SKPaint bucketsPaint = new SKPaint + { + Color = SKColors.BurlyWood, + }; + + public ConveyorBeltPage() + { + Title = "Conveyor Belt"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + // Create the path for the bucket starting with the handle + bucketPath.AddRect(new SKRect(-5, -3, 25, 3)); + + // Sides + bucketPath.AddRoundRect(new SKRect(25, -19, 27, 18), 10, 10, SKPathDirection.CounterClockwise); + bucketPath.AddRoundRect(new SKRect(63, -19, 65, 18), 10, 10, SKPathDirection.CounterClockwise); + + // Five slats + for (int i = 0; i < 5; i++) + { + bucketPath.MoveTo(25, -19 + 8 * i); + bucketPath.LineTo(25, -13 + 8 * i); + bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small, + SKPathDirection.CounterClockwise, 65, -13 + 8 * i); + bucketPath.LineTo(65, -19 + 8 * i); + bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small, + SKPathDirection.Clockwise, 25, -19 + 8 * i); + bucketPath.Close(); + } + + // Arc to suggest the hidden side + bucketPath.MoveTo(25, -17); + bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small, + SKPathDirection.Clockwise, 65, -17); + bucketPath.LineTo(65, -19); + bucketPath.ArcTo(50, 50, 0, SKPathArcSize.Small, + SKPathDirection.CounterClockwise, 25, -19); + bucketPath.Close(); + + // Make it a little bigger and correct the orientation + bucketPath.Transform(SKMatrix.CreateScale(-2, 2)); + bucketPath.Transform(SKMatrix.CreateRotationDegrees(90)); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float width = info.Width / 3; + float verticalMargin = width / 2 + 150; + + using (SKPath conveyerPath = new SKPath()) + { + // Straight verticals capped by semicircles on top and bottom + conveyerPath.MoveTo(width, verticalMargin); + conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large, + SKPathDirection.Clockwise, 2 * width, verticalMargin); + conveyerPath.LineTo(2 * width, info.Height - verticalMargin); + conveyerPath.ArcTo(width / 2, width / 2, 0, SKPathArcSize.Large, + SKPathDirection.Clockwise, width, info.Height - verticalMargin); + conveyerPath.Close(); + + // Draw the conveyor belt itself + canvas.DrawPath(conveyerPath, conveyerPaint); + + // Calculate spacing based on length of conveyer path + float length = 2 * (info.Height - 2 * verticalMargin) + + 2 * ((float)Math.PI * width / 2); + + // Value will be somewhere around 200 + float spacing = length / (float)Math.Round(length / 200); + + // Now animate the phase; t is 0 to 1 every 2 seconds + TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); + float t = (float)(timeSpan.TotalSeconds % 2 / 2); + float phase = -t * spacing; + + // Create the buckets PathEffect + using (SKPathEffect bucketsPathEffect = + SKPathEffect.Create1DPath(bucketPath, spacing, phase, + SKPath1DPathEffectStyle.Rotate)) + { + // Set it to the Paint object and draw the path again + bucketsPaint.PathEffect = bucketsPathEffect; + canvas.DrawPath(conveyerPath, bucketsPaint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml new file mode 100644 index 000000000..68817c104 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml.cs new file mode 100644 index 000000000..053c4eb41 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/CurvesMenuPage.xaml.cs @@ -0,0 +1,13 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves; + +public partial class CurvesMenuPage : BasePage +{ + public CurvesMenuPage() + { + InitializeComponent(); + } +} diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DashedHatchLinesPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DashedHatchLinesPage.cs new file mode 100644 index 000000000..1c65979cb --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DashedHatchLinesPage.cs @@ -0,0 +1,51 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class DashedHatchLinesPage : ContentPage + { + static SKPathEffect dashEffect = + SKPathEffect.CreateDash(new float[] { 30, 30 }, 0); + + static SKPathEffect hatchEffect = SKPathEffect.Create2DLine(20, + Multiply(SKMatrix.CreateScale(60, 60), + SKMatrix.CreateRotationDegrees(45))); + + SKPaint paint = new SKPaint() + { + PathEffect = SKPathEffect.CreateCompose(dashEffect, hatchEffect), + StrokeCap = SKStrokeCap.Round, + Color = SKColors.Blue + }; + + public DashedHatchLinesPage() + { + Title = "Dashed Hatch Lines"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + canvas.DrawOval(info.Width / 2, info.Height / 2, + 0.45f * info.Width, 0.45f * info.Height, paint); + } + + static SKMatrix Multiply(SKMatrix first, SKMatrix second) + { + SKMatrix target = SKMatrix.CreateIdentity(); + SKMatrix.Concat(ref target, first, second); + return target; + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DotDashMorphPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DotDashMorphPage.cs new file mode 100644 index 000000000..940761c00 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/DotDashMorphPage.cs @@ -0,0 +1,112 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class DotDashMorphPage : ContentPage + { + const float strokeWidth = 30; + static readonly float[] dashArray = new float[4]; + + SKCanvasView canvasView; + bool pageIsActive = false; + + SKPaint ellipsePaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = strokeWidth, + StrokeCap = SKStrokeCap.Round, + Color = SKColors.Blue + }; + + public DotDashMorphPage() + { + Title = "Dot / Dash Morph"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Create elliptical path + using (SKPath ellipsePath = new SKPath()) + { + ellipsePath.AddOval(new SKRect(50, 50, info.Width - 50, info.Height - 50)); + + // Create animated path effect + TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); + float t = (float)(timeSpan.TotalSeconds % 3 / 3); + float phase = 0; + + if (t < 0.25f) // 1, 0, 1, 2 --> 0, 2, 0, 2 + { + float tsub = 4 * t; + dashArray[0] = strokeWidth * (1 - tsub); + dashArray[1] = strokeWidth * 2 * tsub; + dashArray[2] = strokeWidth * (1 - tsub); + dashArray[3] = strokeWidth * 2; + } + else if (t < 0.5f) // 0, 2, 0, 2 --> 1, 2, 1, 0 + { + float tsub = 4 * (t - 0.25f); + dashArray[0] = strokeWidth * tsub; + dashArray[1] = strokeWidth * 2; + dashArray[2] = strokeWidth * tsub; + dashArray[3] = strokeWidth * 2 * (1 - tsub); + phase = strokeWidth * tsub; + } + else if (t < 0.75f) // 1, 2, 1, 0 --> 0, 2, 0, 2 + { + float tsub = 4 * (t - 0.5f); + dashArray[0] = strokeWidth * (1 - tsub); + dashArray[1] = strokeWidth * 2; + dashArray[2] = strokeWidth * (1 - tsub); + dashArray[3] = strokeWidth * 2 * tsub; + phase = strokeWidth * (1 - tsub); + } + else // 0, 2, 0, 2 --> 1, 0, 1, 2 + { + float tsub = 4 * (t - 0.75f); + dashArray[0] = strokeWidth * tsub; + dashArray[1] = strokeWidth * 2 * (1 - tsub); + dashArray[2] = strokeWidth * tsub; + dashArray[3] = strokeWidth * 2; + } + + using (SKPathEffect pathEffect = SKPathEffect.CreateDash(dashArray, phase)) + { + ellipsePaint.PathEffect = pathEffect; + canvas.DrawPath(ellipsePath, ellipsePaint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml new file mode 100644 index 000000000..6f2a08b55 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml.cs new file mode 100644 index 000000000..440564e48 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/EllipticalArcPage.xaml.cs @@ -0,0 +1,71 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class EllipticalArcPage : InteractivePage +{ + SKColor[] colors = { SKColors.Red, SKColors.Green, SKColors.Blue, SKColors.Gray }; + + public EllipticalArcPage() + { + touchPoints = new TouchPoint[2]; + + for (int i = 0; i < 2; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(200, 100 + 200 * i) + }; + touchPoints[i] = touchPoint; + } + InitializeComponent(); + + baseCanvasView = canvasView; + xRadiusSlider.Value = 125; + yRadiusSlider.Value = 120; + } + + void sliderValueChanged(object sender, ValueChangedEventArgs args) + { + if (canvasView != null) + { + canvasView.InvalidateSurface(); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath path = new SKPath()) + { + int colorIndex = 0; + SKPoint ellipseSize = new SKPoint((float)xRadiusSlider.Value, + (float)yRadiusSlider.Value); + float rotation = (float)rotationSlider.Value; + + foreach (SKPathArcSize arcSize in Enum.GetValues(typeof(SKPathArcSize))) + foreach (SKPathDirection direction in Enum.GetValues(typeof(SKPathDirection))) + { + path.MoveTo(touchPoints[0].Center); + path.ArcTo(ellipseSize, rotation, + arcSize, direction, + touchPoints[1].Center); + + strokePaint.Color = colors[colorIndex++]; + canvas.DrawPath(path, strokePaint); + path.Reset(); + } + } + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } +} diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ExplodedPieChartPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ExplodedPieChartPage.cs new file mode 100644 index 000000000..90a8531fc --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/ExplodedPieChartPage.cs @@ -0,0 +1,103 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class ExplodedPieChartPage : ContentPage + { + class ChartData + { + public ChartData(int value, SKColor color) + { + Value = value; + Color = color; + } + + public int Value { private set; get; } + + public SKColor Color { private set; get; } + } + + ChartData[] chartData = + { + new ChartData(45, SKColors.Red), + new ChartData(13, SKColors.Green), + new ChartData(27, SKColors.Blue), + new ChartData(19, SKColors.Magenta), + new ChartData(40, SKColors.Cyan), + new ChartData(22, SKColors.Brown), + new ChartData(29, SKColors.Gray) + }; + + public ExplodedPieChartPage() + { + Title = "Exploded Pie Chart"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + int totalValues = 0; + + foreach (ChartData item in chartData) + { + totalValues += item.Value; + } + + SKPoint center = new SKPoint(info.Width / 2, info.Height / 2); + float explodeOffset = 50; + float radius = Math.Min(info.Width / 2, info.Height / 2) - 2 * explodeOffset; + SKRect rect = new SKRect(center.X - radius, center.Y - radius, + center.X + radius, center.Y + radius); + + float startAngle = 0; + + foreach (ChartData item in chartData) + { + float sweepAngle = 360f * item.Value / totalValues; + + using (SKPath path = new SKPath()) + using (SKPaint fillPaint = new SKPaint()) + using (SKPaint outlinePaint = new SKPaint()) + { + path.MoveTo(center); + path.ArcTo(rect, startAngle, sweepAngle, false); + path.Close(); + + fillPaint.Style = SKPaintStyle.Fill; + fillPaint.Color = item.Color; + + outlinePaint.Style = SKPaintStyle.Stroke; + outlinePaint.StrokeWidth = 5; + outlinePaint.Color = SKColors.Black; + + // Calculate "explode" transform + float angle = startAngle + 0.5f * sweepAngle; + float x = explodeOffset * (float)Math.Cos(Math.PI * angle / 180); + float y = explodeOffset * (float)Math.Sin(Math.PI * angle / 180); + + canvas.Save(); + canvas.Translate(x, y); + + // Fill and stroke the path + canvas.DrawPath(path, fillPaint); + canvas.DrawPath(path, outlinePaint); + canvas.Restore(); + } + + startAngle += sweepAngle; + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourCircleIntersectClipPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourCircleIntersectClipPage.cs new file mode 100644 index 000000000..ccb59e6fd --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourCircleIntersectClipPage.cs @@ -0,0 +1,60 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class FourCircleIntersectClipPage : ContentPage + { + public FourCircleIntersectClipPage() + { + Title = "Four Circle Intersect Clip"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float size = Math.Min(info.Width, info.Height); + float radius = 0.4f * size; + float offset = size / 2 - radius; + + // Translate to center + canvas.Translate(info.Width / 2, info.Height / 2); + + using (SKPath path = new SKPath()) + { + path.AddCircle(-offset, -offset, radius); + canvas.ClipPath(path, SKClipOperation.Intersect); + + path.Reset(); + path.AddCircle(-offset, offset, radius); + canvas.ClipPath(path, SKClipOperation.Intersect); + + path.Reset(); + path.AddCircle(offset, -offset, radius); + canvas.ClipPath(path, SKClipOperation.Intersect); + + path.Reset(); + path.AddCircle(offset, offset, radius); + canvas.ClipPath(path, SKClipOperation.Intersect); + + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Fill; + paint.Color = SKColors.Blue; + canvas.DrawPaint(paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourLeafCloverPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourLeafCloverPage.cs new file mode 100644 index 000000000..f8d610b7b --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/FourLeafCloverPage.cs @@ -0,0 +1,95 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class FourLeafCloverPage : ContentPage + { + public FourLeafCloverPage() + { + Title = "Four-Leaf Clover"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float xCenter = info.Width / 2; + float yCenter = info.Height / 2; + float radius = 0.24f * Math.Min(info.Width, info.Height); + + using (SKRegion wholeScreenRegion = new SKRegion()) + { + wholeScreenRegion.SetRect(new SKRectI(0, 0, info.Width, info.Height)); + + using (SKRegion leftRegion = new SKRegion(wholeScreenRegion)) + using (SKRegion rightRegion = new SKRegion(wholeScreenRegion)) + using (SKRegion topRegion = new SKRegion(wholeScreenRegion)) + using (SKRegion bottomRegion = new SKRegion(wholeScreenRegion)) + { + using (SKPath circlePath = new SKPath()) + { + // Make basic circle path + circlePath.AddCircle(xCenter, yCenter, radius); + + // Left leaf + circlePath.Transform(SKMatrix.CreateTranslation(-radius, 0)); + leftRegion.SetPath(circlePath); + + // Right leaf + circlePath.Transform(SKMatrix.CreateTranslation(2 * radius, 0)); + rightRegion.SetPath(circlePath); + + // Make union of right with left + leftRegion.Op(rightRegion, SKRegionOperation.Union); + + // Top leaf + circlePath.Transform(SKMatrix.CreateTranslation(-radius, -radius)); + topRegion.SetPath(circlePath); + + // Combine with bottom leaf + circlePath.Transform(SKMatrix.CreateTranslation(0, 2 * radius)); + bottomRegion.SetPath(circlePath); + + // Make union of top with bottom + bottomRegion.Op(topRegion, SKRegionOperation.Union); + + // Exclusive-OR left and right with top and bottom + leftRegion.Op(bottomRegion, SKRegionOperation.XOR); + + // Set that as clip region + canvas.ClipRegion(leftRegion); + + // Set transform for drawing lines from center + canvas.Translate(xCenter, yCenter); + + // Draw 360 lines + for (double angle = 0; angle < 360; angle++) + { + float x = 2 * radius * (float)Math.Cos(Math.PI * angle / 180); + float y = 2 * radius * (float)Math.Sin(Math.PI * angle / 180); + + using (SKPaint strokePaint = new SKPaint()) + { + strokePaint.Color = SKColors.Green; + strokePaint.StrokeWidth = 2; + + canvas.DrawLine(0, 0, x, y, strokePaint); + } + } + } + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/GlobularTextPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/GlobularTextPage.cs new file mode 100644 index 000000000..4745ffd3b --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/GlobularTextPage.cs @@ -0,0 +1,70 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class GlobularTextPage : ContentPage + { + SKPath globePath; + + public GlobularTextPage() + { + Title = "Globular Text"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + using (SKPaint textPaint = new SKPaint()) + { + textPaint.Typeface = SKTypeface.FromFamilyName("Times New Roman"); + textPaint.TextSize = 100; + + using (SKPath textPath = textPaint.GetTextPath("HELLO", 0, 0)) + { + SKRect textPathBounds; + textPath.GetBounds(out textPathBounds); + + globePath = textPath.CloneWithTransform((SKPoint pt) => + { + double longitude = (Math.PI / textPathBounds.Width) * + (pt.X - textPathBounds.Left) - Math.PI / 2; + double latitude = (Math.PI / textPathBounds.Height) * + (pt.Y - textPathBounds.Top) - Math.PI / 2; + + longitude *= 0.75; + latitude *= 0.75; + + float x = (float)(Math.Cos(latitude) * Math.Sin(longitude)); + float y = (float)Math.Sin(latitude); + + return new SKPoint(x, y); + }); + } + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint pathPaint = new SKPaint()) + { + pathPaint.Style = SKPaintStyle.Fill; + pathPaint.Color = SKColors.Blue; + pathPaint.StrokeWidth = 3; + pathPaint.IsAntialias = true; + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(0.45f * Math.Min(info.Width, info.Height)); // radius + canvas.DrawPath(globePath, pathPaint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/HatchFillPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/HatchFillPage.cs new file mode 100644 index 000000000..8de0ba1b8 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/HatchFillPage.cs @@ -0,0 +1,80 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class HatchFillPage : ContentPage + { + SKPaint fillPaint = new SKPaint(); + + SKPathEffect horzLinesPath = SKPathEffect.Create2DLine(3, SKMatrix.CreateScale(6, 6)); + + SKPathEffect vertLinesPath = SKPathEffect.Create2DLine(6, + Multiply(SKMatrix.CreateRotationDegrees(90), SKMatrix.CreateScale(24, 24))); + + SKPathEffect diagLinesPath = SKPathEffect.Create2DLine(12, + Multiply(SKMatrix.CreateScale(36, 36), SKMatrix.CreateRotationDegrees(45))); + + SKPaint strokePaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 3, + Color = SKColors.Black + }; + + public HatchFillPage() + { + Title = "Hatch Fill"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath roundRectPath = new SKPath()) + { + // Create a path + roundRectPath.AddRoundRect(new SKRect(50, 50, info.Width - 50, info.Height - 50), 100, 100); + + // Horizontal hatch marks + fillPaint.PathEffect = horzLinesPath; + fillPaint.Color = SKColors.Red; + canvas.DrawPath(roundRectPath, fillPaint); + + // Vertical hatch marks + fillPaint.PathEffect = vertLinesPath; + fillPaint.Color = SKColors.Blue; + canvas.DrawPath(roundRectPath, fillPaint); + + // Diagonal hatch marks -- use clipping + fillPaint.PathEffect = diagLinesPath; + fillPaint.Color = SKColors.Green; + + canvas.Save(); + canvas.ClipPath(roundRectPath); + canvas.DrawRect(new SKRect(0, 0, info.Width, info.Height), fillPaint); + canvas.Restore(); + + // Outline the path + canvas.DrawPath(roundRectPath, strokePaint); + } + } + + static SKMatrix Multiply(SKMatrix first, SKMatrix second) + { + SKMatrix target = SKMatrix.CreateIdentity(); + SKMatrix.Concat(ref target, first, second); + return target; + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml new file mode 100644 index 000000000..b9c2934f1 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml.cs new file mode 100644 index 000000000..32e127a05 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterExperimentPage.xaml.cs @@ -0,0 +1,47 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class JitterExperimentPage : ContentPage +{ + public JitterExperimentPage() + { + InitializeComponent(); + } + + void sliderValueChanged(object sender, ValueChangedEventArgs args) + { + if (canvasView != null) + { + canvasView.InvalidateSurface(); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float segLength = (float)segLengthSlider.Value; + float deviation = (float)deviationSlider.Value; + + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.StrokeWidth = 5; + paint.Color = SKColors.Blue; + + using (SKPathEffect pathEffect = SKPathEffect.CreateDiscrete(segLength, deviation)) + { + paint.PathEffect = pathEffect; + + SKRect rect = new SKRect(100, 100, info.Width - 100, info.Height - 100); + canvas.DrawRect(rect, paint); + } + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterTextPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterTextPage.cs new file mode 100644 index 000000000..82b62d013 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/JitterTextPage.cs @@ -0,0 +1,50 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class JitterTextPage : ContentPage + { + public JitterTextPage() + { + Title = "Jitter Text"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + string text = "FUZZY"; + + using (SKPaint textPaint = new SKPaint()) + { + textPaint.Color = SKColors.Purple; + textPaint.PathEffect = SKPathEffect.CreateDiscrete(3f, 10f); + + // Adjust TextSize property so text is 95% of screen width + float textWidth = textPaint.MeasureText(text); + textPaint.TextSize *= 0.95f * info.Width / textWidth; + + // Find the text bounds + SKRect textBounds = new SKRect(); + textPaint.MeasureText(text, ref textBounds); + + // Calculate offsets to center the text on the screen + float xText = info.Width / 2 - textBounds.MidX; + float yText = info.Height / 2 - textBounds.MidY; + + canvas.DrawText(text, xText, yText, textPaint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/LinkedChainPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/LinkedChainPage.cs new file mode 100644 index 000000000..5b4949345 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/LinkedChainPage.cs @@ -0,0 +1,110 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class LinkedChainPage : ContentPage + { + const float linkRadius = 30; + const float linkThickness = 5; + + Func catenary = (float a, float x) => (float)(a * Math.Cosh(x / a)); + + SKPaint linksPaint = new SKPaint + { + Color = SKColors.Silver + }; + + public LinkedChainPage() + { + Title = "Linked Chain"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + // Create the path for the individual links + SKRect outer = new SKRect(-linkRadius, -linkRadius, linkRadius, linkRadius); + SKRect inner = outer; + inner.Inflate(-linkThickness, -linkThickness); + + using (SKPath linkPath = new SKPath()) + { + linkPath.AddArc(outer, 55, 160); + linkPath.ArcTo(inner, 215, -160, false); + linkPath.Close(); + + linkPath.AddArc(outer, 235, 160); + linkPath.ArcTo(inner, 395, -160, false); + linkPath.Close(); + + // Set that path as the 1D path effect for linksPaint + linksPaint.PathEffect = + SKPathEffect.Create1DPath(linkPath, 1.3f * linkRadius, 0, + SKPath1DPathEffectStyle.Rotate); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(SKColors.Black); + + // Width and height of catenary + int width = info.Width; + float height = info.Height - linkRadius; + + // Find the optimum 'a' for this width and height + float optA = FindOptimumA(width, height); + + // Calculate the vertical offset for that value of 'a' + float yOffset = catenary(optA, -width / 2); + + // Create a path for the catenary + SKPoint[] points = new SKPoint[width]; + + for (int x = 0; x < width; x++) + { + points[x] = new SKPoint(x, yOffset - catenary(optA, x - width / 2)); + } + + using (SKPath path = new SKPath()) + { + path.AddPoly(points, false); + + // And render that path with the linksPaint object + canvas.DrawPath(path, linksPaint); + } + } + + float FindOptimumA(float width, float height) + { + Func left = (float a) => (float)Math.Cosh(width / 2 / a); + Func right = (float a) => 1 + height / a; + + float gtA = 1; // starting value for left > right + float ltA = 10000; // starting value for left < right + + while (Math.Abs(gtA - ltA) > 0.1f) + { + float avgA = (gtA + ltA) / 2; + + if (left(avgA) < right(avgA)) + { + ltA = avgA; + } + else + { + gtA = avgA; + } + } + + return (gtA + ltA) / 2; + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/MonkeyThroughKeyholePage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/MonkeyThroughKeyholePage.cs new file mode 100644 index 000000000..af782c75e --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/MonkeyThroughKeyholePage.cs @@ -0,0 +1,58 @@ +using System.Reflection; +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class MonkeyThroughKeyholePage : ContentPage + { + SKBitmap bitmap; + SKPath keyholePath = SKPath.ParseSvgPathData( + "M 300 130 L 250 350 L 450 350 L 400 130 A 70 70 0 1 0 300 130 Z"); + + public MonkeyThroughKeyholePage() + { + Title = "Monkey through Keyhole"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + Assembly assembly = GetType().GetTypeInfo().Assembly; + using (Stream stream = assembly.GetManifestResourceStream("SkiaSharpDemos.Media.seatedmonkey.jpg")) + { + bitmap = SKBitmap.Decode(stream); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Set transform to center and enlarge clip path to window height + SKRect bounds; + keyholePath.GetTightBounds(out bounds); + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(0.98f * info.Height / bounds.Height); + canvas.Translate(-bounds.MidX, -bounds.MidY); + + // Set the clip path + canvas.ClipPath(keyholePath); + + // Reset transforms + canvas.ResetMatrix(); + + // Display monkey to fill height of window but maintain aspect ratio + canvas.DrawBitmap(bitmap, + new SKRect((info.Width - info.Height) / 2, 0, + (info.Width + info.Height) / 2, info.Height)); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml new file mode 100644 index 000000000..35e9feeca --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml @@ -0,0 +1,25 @@ + + + + + + + Translate + Rotate + Morph + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml.cs new file mode 100644 index 000000000..d45fb7173 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/OneDimensionalPathEffectPage.xaml.cs @@ -0,0 +1,71 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class OneDimensionalPathEffectPage : ContentPage +{ + SKPathEffect translatePathEffect = + SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 -10 L 10 -10, 10 10, -10 10 Z"), + 24, 0, SKPath1DPathEffectStyle.Translate); + + SKPathEffect rotatePathEffect = + SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -10 0 L 0 -10, 10 0, 0 10 Z"), + 20, 0, SKPath1DPathEffectStyle.Rotate); + + SKPathEffect morphPathEffect = + SKPathEffect.Create1DPath(SKPath.ParseSvgPathData("M -25 -10 L 25 -10, 25 10, -25 10 Z"), + 55, 0, SKPath1DPathEffectStyle.Morph); + + SKPaint pathPaint = new SKPaint + { + Color = SKColors.Blue + }; + + public OneDimensionalPathEffectPage() + { + InitializeComponent(); + } + + void OnPickerSelectedIndexChanged(object sender, EventArgs args) + { + if (canvasView != null) + { + canvasView.InvalidateSurface(); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath path = new SKPath()) + { + path.MoveTo(new SKPoint(0, 0)); + path.CubicTo(new SKPoint(2 * info.Width, info.Height), + new SKPoint(-info.Width, info.Height), + new SKPoint(info.Width, 0)); + + switch ((string)effectStylePicker.SelectedItem) + { + case "Translate": + pathPaint.PathEffect = translatePathEffect; + break; + + case "Rotate": + pathPaint.PathEffect = rotatePathEffect; + break; + + case "Morph": + pathPaint.PathEffect = morphPathEffect; + break; + } + + canvas.DrawPath(path, pathPaint); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataCatPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataCatPage.cs new file mode 100644 index 000000000..d74756bc0 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataCatPage.cs @@ -0,0 +1,66 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class PathDataCatPage : ContentPage + { + SKPath catPath = SKPath.ParseSvgPathData( + "M 160 140 L 150 50 220 103" + // Left ear + "M 320 140 L 330 50 260 103" + // Right ear + "M 215 230 L 40 200" + // Left whiskers + "M 215 240 L 40 240" + + "M 215 250 L 40 280" + + "M 265 230 L 440 200" + // Right whiskers + "M 265 240 L 440 240" + + "M 265 250 L 440 280" + + "M 240 100" + // Head + "A 100 100 0 0 1 240 300" + + "A 100 100 0 0 1 240 100 Z" + + "M 180 170" + // Left eye + "A 40 40 0 0 1 220 170" + + "A 40 40 0 0 1 180 170 Z" + + "M 300 170" + // Right eye + "A 40 40 0 0 1 260 170" + + "A 40 40 0 0 1 300 170 Z"); + + SKPaint paint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Orange, + StrokeWidth = 5 + }; + + public PathDataCatPage() + { + Title = "Path Data Cat"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(SKColors.Black); + + SKRect bounds; + catPath.GetBounds(out bounds); + + canvas.Translate(info.Width / 2, info.Height / 2); + + canvas.Scale(0.9f * Math.Min(info.Width / bounds.Width, + info.Height / bounds.Height)); + + canvas.Translate(-bounds.MidX, -bounds.MidY); + + canvas.DrawPath(catPath, paint); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataHelloPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataHelloPage.cs new file mode 100644 index 000000000..16661c23b --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathDataHelloPage.cs @@ -0,0 +1,56 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class PathDataHelloPage : ContentPage + { + SKPath helloPath = SKPath.ParseSvgPathData( + "M 0 0 L 0 100 M 0 50 L 50 50 M 50 0 L 50 100" + // H + "M 125 0 C 60 -10, 60 60, 125 50, 60 40, 60 110, 125 100" + // E + "M 150 0 L 150 100, 200 100" + // L + "M 225 0 L 225 100, 275 100" + // L + "M 300 50 A 25 50 0 1 0 300 49.9 Z"); // O + + SKPaint paint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Blue, + StrokeWidth = 10, + StrokeCap = SKStrokeCap.Round, + StrokeJoin = SKStrokeJoin.Round + }; + + public PathDataHelloPage() + { + Title = "Path Data Hello"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + SKRect bounds; + helloPath.GetTightBounds(out bounds); + + canvas.Translate(info.Width / 2, info.Height / 2); + + canvas.Scale(info.Width / (bounds.Width + paint.StrokeWidth), + info.Height / (bounds.Height + paint.StrokeWidth)); + + canvas.Translate(-bounds.MidX, -bounds.MidY); + + canvas.DrawPath(helloPath, paint); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathExtensions.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathExtensions.cs new file mode 100644 index 000000000..dfedf1944 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathExtensions.cs @@ -0,0 +1,167 @@ +using SkiaSharp; + +namespace SkiaSharpDemos.Curves +{ + static class PathExtensions + { + public static SKPath CloneWithTransform(this SKPath pathIn, Func transform) + { + SKPath pathOut = new SKPath(); + + using (SKPath.RawIterator iterator = pathIn.CreateRawIterator()) + { + SKPoint[] points = new SKPoint[4]; + SKPathVerb pathVerb = SKPathVerb.Move; + SKPoint firstPoint = new SKPoint(); + SKPoint lastPoint = new SKPoint(); + + while ((pathVerb = iterator.Next(points)) != SKPathVerb.Done) + { + switch (pathVerb) + { + case SKPathVerb.Move: + pathOut.MoveTo(transform(points[0])); + firstPoint = lastPoint = points[0]; + break; + + case SKPathVerb.Line: + SKPoint[] linePoints = Interpolate(points[0], points[1]); + + foreach (SKPoint pt in linePoints) + { + pathOut.LineTo(transform(pt)); + } + + lastPoint = points[1]; + break; + + case SKPathVerb.Cubic: + SKPoint[] cubicPoints = FlattenCubic(points[0], points[1], points[2], points[3]); + + foreach (SKPoint pt in cubicPoints) + { + pathOut.LineTo(transform(pt)); + } + + lastPoint = points[3]; + break; + + case SKPathVerb.Quad: + SKPoint[] quadPoints = FlattenQuadratic(points[0], points[1], points[2]); + + foreach (SKPoint pt in quadPoints) + { + pathOut.LineTo(transform(pt)); + } + + lastPoint = points[2]; + break; + + case SKPathVerb.Conic: + SKPoint[] conicPoints = FlattenConic(points[0], points[1], points[2], iterator.ConicWeight()); + + foreach (SKPoint pt in conicPoints) + { + pathOut.LineTo(transform(pt)); + } + + lastPoint = points[2]; + break; + + case SKPathVerb.Close: + SKPoint[] closePoints = Interpolate(lastPoint, firstPoint); + + foreach (SKPoint pt in closePoints) + { + pathOut.LineTo(transform(pt)); + } + + firstPoint = lastPoint = new SKPoint(0, 0); + pathOut.Close(); + break; + } + } + } + return pathOut; + } + + static SKPoint[] Interpolate(SKPoint pt0, SKPoint pt1) + { + int count = (int)Math.Max(1, Length(pt0, pt1)); + SKPoint[] points = new SKPoint[count]; + + for (int i = 0; i < count; i++) + { + float t = (i + 1f) / count; + float x = (1 - t) * pt0.X + t * pt1.X; + float y = (1 - t) * pt0.Y + t * pt1.Y; + points[i] = new SKPoint(x, y); + } + + return points; + } + + static SKPoint[] FlattenCubic(SKPoint pt0, SKPoint pt1, SKPoint pt2, SKPoint pt3) + { + int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2) + Length(pt2, pt3)); + SKPoint[] points = new SKPoint[count]; + + for (int i = 0; i < count; i++) + { + float t = (i + 1f) / count; + float x = (1 - t) * (1 - t) * (1 - t) * pt0.X + + 3 * t * (1 - t) * (1 - t) * pt1.X + + 3 * t * t * (1 - t) * pt2.X + + t * t * t * pt3.X; + float y = (1 - t) * (1 - t) * (1 - t) * pt0.Y + + 3 * t * (1 - t) * (1 - t) * pt1.Y + + 3 * t * t * (1 - t) * pt2.Y + + t * t * t * pt3.Y; + points[i] = new SKPoint(x, y); + } + + return points; + } + + static SKPoint[] FlattenQuadratic(SKPoint pt0, SKPoint pt1, SKPoint pt2) + { + int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2)); + SKPoint[] points = new SKPoint[count]; + + for (int i = 0; i < count; i++) + { + float t = (i + 1f) / count; + float x = (1 - t) * (1 - t) * pt0.X + 2 * t * (1 - t) * pt1.X + t * t * pt2.X; + float y = (1 - t) * (1 - t) * pt0.Y + 2 * t * (1 - t) * pt1.Y + t * t * pt2.Y; + points[i] = new SKPoint(x, y); + } + + return points; + } + + static SKPoint[] FlattenConic(SKPoint pt0, SKPoint pt1, SKPoint pt2, float weight) + { + int count = (int)Math.Max(1, Length(pt0, pt1) + Length(pt1, pt2)); + SKPoint[] points = new SKPoint[count]; + + for (int i = 0; i < count; i++) + { + float t = (i + 1f) / count; + float denominator = (1 - t) * (1 - t) + 2 * weight * t * (1 - t) + t * t; + float x = (1 - t) * (1 - t) * pt0.X + 2 * weight * t * (1 - t) * pt1.X + t * t * pt2.X; + float y = (1 - t) * (1 - t) * pt0.Y + 2 * weight * t * (1 - t) * pt1.Y + t * t * pt2.Y; + x /= denominator; + y /= denominator; + points[i] = new SKPoint(x, y); + } + + return points; + } + + static double Length(SKPoint pt0, SKPoint pt1) + { + return Math.Sqrt(Math.Pow(pt1.X - pt0.X, 2) + Math.Pow(pt1.Y - pt0.Y, 2)); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml new file mode 100644 index 000000000..1cfb7362c --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml.cs new file mode 100644 index 000000000..5b2c16b92 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathLengthPage.xaml.cs @@ -0,0 +1,70 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class PathLengthPage : InteractivePage +{ + const string text = "Compute length of path"; + + static SKPaint textPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Black, + TextSize = 10, + }; + + static readonly float baseTextWidth = textPaint.MeasureText(text); + + public PathLengthPage() + { + touchPoints = new TouchPoint[4]; + + for (int i = 0; i < 4; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(100 + 200 * (i % 2), + 100 + 200 * i) + }; + touchPoints[i] = touchPoint; + } + + InitializeComponent(); + baseCanvasView = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Draw path with cubic Bezier curve + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.CubicTo(touchPoints[1].Center, + touchPoints[2].Center, + touchPoints[3].Center); + + canvas.DrawPath(path, strokePaint); + + // Get path length + SKPathMeasure pathMeasure = new SKPathMeasure(path, false, 1); + + // Find new text size + textPaint.TextSize = pathMeasure.Length / baseTextWidth * 10; + + // Draw text on path + canvas.DrawTextOnPath(text, path, 0, 0, textPaint); + } + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathTileFillPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathTileFillPage.cs new file mode 100644 index 000000000..aeec343ab --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PathTileFillPage.cs @@ -0,0 +1,48 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class PathTileFillPage : ContentPage + { + SKPath tilePath = SKPath.ParseSvgPathData( + "M -20 -20 L 2 -20, 2 -40, 18 -40, 18 -20, 40 -20, " + + "40 -12, 20 -12, 20 12, 40 12, 40 40, 22 40, 22 20, " + + "-2 20, -2 40, -20 40, -20 8, -40 8, -40 -8, -20 -8 Z"); + + public PathTileFillPage() + { + Title = "Path Tile Fill"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint paint = new SKPaint()) + { + paint.Color = SKColors.Red; + + using (SKPathEffect pathEffect = + SKPathEffect.Create2DPath(SKMatrix.MakeScale(64, 64), tilePath)) + { + paint.PathEffect = pathEffect; + + canvas.DrawRoundRect( + new SKRect(50, 50, info.Width - 50, info.Height - 50), + 100, 100, paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PrettyAnalogClockPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PrettyAnalogClockPage.cs new file mode 100644 index 000000000..a4d7ef25d --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/PrettyAnalogClockPage.cs @@ -0,0 +1,139 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class PrettyAnalogClockPage : ContentPage + { + SKCanvasView canvasView; + bool pageIsActive; + + // Clock hands pointing straight up + SKPath hourHandPath = SKPath.ParseSvgPathData( + "M 0 -60 C 0 -30 20 -30 5 -20 L 5 0" + + "C 5 7.5 -5 7.5 -5 0 L -5 -20" + + "C -20 -30 0 -30 0 -60 Z"); + + SKPath minuteHandPath = SKPath.ParseSvgPathData( + "M 0 -80 C 0 -75 0 -70 2.5 -60 L 2.5 0" + + "C 2.5 5 -2.5 5 -2.5 0 L -2.5 -60" + + "C 0 -70 0 -75 0 -80 Z"); + + SKPath secondHandPath = SKPath.ParseSvgPathData( + "M 0 10 L 0 -80"); + + // SKPaint objects + SKPaint handStrokePaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 2, + StrokeCap = SKStrokeCap.Round + }; + + SKPaint handFillPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Gray + }; + + SKPaint minuteMarkPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 3, + StrokeCap = SKStrokeCap.Round, + PathEffect = SKPathEffect.CreateDash(new float[] { 0, 3 * 3.14159f }, 0) + }; + + SKPaint hourMarkPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = 6, + StrokeCap = SKStrokeCap.Round, + PathEffect = SKPathEffect.CreateDash(new float[] { 0, 15 * 3.14159f }, 0) + }; + + public PrettyAnalogClockPage() + { + Title = "Pretty Analog Clock"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Transform for 100-radius circle in center + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(Math.Min(info.Width / 200, info.Height / 200)); + + // Draw circles for hour and minute marks + SKRect rect = new SKRect(-90, -90, 90, 90); + canvas.DrawOval(rect, minuteMarkPaint); + canvas.DrawOval(rect, hourMarkPaint); + + // Get time + DateTime dateTime = DateTime.Now; + + // Draw hour hand + canvas.Save(); + canvas.RotateDegrees(30 * dateTime.Hour + dateTime.Minute / 2f); + canvas.DrawPath(hourHandPath, handStrokePaint); + canvas.DrawPath(hourHandPath, handFillPaint); + canvas.Restore(); + + // Draw minute hand + canvas.Save(); + canvas.RotateDegrees(6 * dateTime.Minute + dateTime.Second / 10f); + canvas.DrawPath(minuteHandPath, handStrokePaint); + canvas.DrawPath(minuteHandPath, handFillPaint); + canvas.Restore(); + + // Draw second hand + double t = dateTime.Millisecond / 1000.0; + + if (t < 0.5) + { + t = 0.5 * Easing.SpringIn.Ease(t / 0.5); + } + else + { + t = 0.5 * (1 + Easing.SpringOut.Ease((t - 0.5) / 0.5)); + } + + canvas.Save(); + canvas.RotateDegrees(6 * (dateTime.Second + (float)t)); + canvas.DrawPath(secondHandPath, handStrokePaint); + canvas.Restore(); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml new file mode 100644 index 000000000..1dde64a73 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml @@ -0,0 +1,13 @@ + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml.cs new file mode 100644 index 000000000..5dfe4e5e2 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/QuadraticCurvePage.xaml.cs @@ -0,0 +1,60 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class QuadraticCurvePage : InteractivePage +{ + public QuadraticCurvePage() + { + touchPoints = new TouchPoint[3]; + + for (int i = 0; i < 3; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(100 + 100 * i, + 100 + (i == 1 ? 300 : 0)) + }; + touchPoints[i] = touchPoint; + } + + InitializeComponent(); + baseCanvasView = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Draw path with quadratic Bezier + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.QuadTo(touchPoints[1].Center, + touchPoints[2].Center); + + canvas.DrawPath(path, strokePaint); + } + + // Draw tangent lines + canvas.DrawLine(touchPoints[0].Center.X, + touchPoints[0].Center.Y, + touchPoints[1].Center.X, + touchPoints[1].Center.Y, dottedStrokePaint); + + canvas.DrawLine(touchPoints[1].Center.X, + touchPoints[1].Center.Y, + touchPoints[2].Center.X, + touchPoints[2].Center.Y, dottedStrokePaint); + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionOperationsPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionOperationsPage.cs new file mode 100644 index 000000000..334becde4 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionOperationsPage.cs @@ -0,0 +1,100 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class RegionOperationsPage : ContentPage + { + SKPaint textPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Black, + TextSize = 40, + TextAlign = SKTextAlign.Center + }; + + SKPaint fillPaint = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Pink + }; + + public RegionOperationsPage() + { + Title = "Region Operations"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float x = 0; + float y = 0; + float width = info.Height > info.Width ? info.Width / 2 : info.Width / 3; + float height = info.Height > info.Width ? info.Height / 3 : info.Height / 2; + + foreach (SKRegionOperation regionOp in Enum.GetValues(typeof(SKRegionOperation))) + { + DisplayClipOp(canvas, new SKRect(x, y, x + width, y + height), regionOp); + + if ((x += width) >= info.Width) + { + x = 0; + y += height; + } + } + } + + void DisplayClipOp(SKCanvas canvas, SKRect rect, SKRegionOperation regionOp) + { + float textSize = textPaint.TextSize; + canvas.DrawText(regionOp.ToString(), rect.MidX, rect.Top + textSize, textPaint); + rect.Top += textSize; + + float radius = 0.9f * Math.Min(rect.Width / 3, rect.Height / 2); + float xCenter = rect.MidX; + float yCenter = rect.MidY; + + SKRectI recti = new SKRectI((int)rect.Left, (int)rect.Top, + (int)rect.Right, (int)rect.Bottom); + + using (SKRegion wholeRectRegion = new SKRegion()) + { + wholeRectRegion.SetRect(recti); + + using (SKRegion region1 = new SKRegion(wholeRectRegion)) + using (SKRegion region2 = new SKRegion(wholeRectRegion)) + { + using (SKPath path1 = new SKPath()) + { + path1.AddCircle(xCenter - radius / 2, yCenter, radius); + region1.SetPath(path1); + } + + using (SKPath path2 = new SKPath()) + { + path2.AddCircle(xCenter + radius / 2, yCenter, radius); + region2.SetPath(path2); + } + + region1.Op(region2, regionOp); + + canvas.Save(); + canvas.ClipRegion(region1); + canvas.DrawPaint(fillPaint); + canvas.Restore(); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionPaintPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionPaintPage.cs new file mode 100644 index 000000000..8664c9528 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RegionPaintPage.cs @@ -0,0 +1,66 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class RegionPaintPage : ContentPage + { + public RegionPaintPage() + { + Title = "Region Paint"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + int radius = 10; + + // Create circular path + using (SKPath circlePath = new SKPath()) + { + circlePath.AddCircle(0, 0, radius); + + // Create circular region + using (SKRegion circleRegion = new SKRegion()) + { + circleRegion.SetRect(new SKRectI(-radius, -radius, radius, radius)); + circleRegion.SetPath(circlePath); + + // Set transform to move it to center and scale up + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(Math.Min(info.Width / 2, info.Height / 2) / radius); + + // Fill region + using (SKPaint fillPaint = new SKPaint()) + { + fillPaint.Style = SKPaintStyle.Fill; + fillPaint.Color = SKColors.Orange; + + canvas.DrawRegion(circleRegion, fillPaint); + } + + // Stroke path for comparison + using (SKPaint strokePaint = new SKPaint()) + { + strokePaint.Style = SKPaintStyle.Stroke; + strokePaint.Color = SKColors.Blue; + strokePaint.StrokeWidth = 0.1f; + + canvas.DrawPath(circlePath, strokePaint); + } + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RoundedHeptagonPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RoundedHeptagonPage.cs new file mode 100644 index 000000000..20292a965 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/RoundedHeptagonPage.cs @@ -0,0 +1,83 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class RoundedHeptagonPage : ContentPage + { + public RoundedHeptagonPage() + { + Title = "Rounded Heptagon"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + float cornerRadius = 100; + int numVertices = 7; + float radius = 0.45f * Math.Min(info.Width, info.Height); + + SKPoint[] vertices = new SKPoint[numVertices]; + SKPoint[] midPoints = new SKPoint[numVertices]; + + double vertexAngle = -0.5f * Math.PI; // straight up + + // Coordinates of the vertices of the polygon + for (int vertex = 0; vertex < numVertices; vertex++) + { + vertices[vertex] = new SKPoint(radius * (float)Math.Cos(vertexAngle), + radius * (float)Math.Sin(vertexAngle)); + vertexAngle += 2 * Math.PI / numVertices; + } + + // Coordinates of the midpoints of the sides connecting the vertices + for (int vertex = 0; vertex < numVertices; vertex++) + { + int prevVertex = (vertex + numVertices - 1) % numVertices; + midPoints[vertex] = new SKPoint((vertices[prevVertex].X + vertices[vertex].X) / 2, + (vertices[prevVertex].Y + vertices[vertex].Y) / 2); + } + + // Create the path + using (SKPath path = new SKPath()) + { + // Begin at the first midpoint + path.MoveTo(midPoints[0]); + + for (int vertex = 0; vertex < numVertices; vertex++) + { + SKPoint nextMidPoint = midPoints[(vertex + 1) % numVertices]; + + // Draws a line from the current point, and then the arc + path.ArcTo(vertices[vertex], nextMidPoint, cornerRadius); + + // Connect the arc with the next midpoint + path.LineTo(nextMidPoint); + } + path.Close(); + + // Render the path in the center of the screen + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.Color = SKColors.Blue; + paint.StrokeWidth = 10; + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.DrawPath(path, paint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/SquaringTheCirclePage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/SquaringTheCirclePage.cs new file mode 100644 index 000000000..f8a3257d1 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/SquaringTheCirclePage.cs @@ -0,0 +1,109 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class SquaringTheCirclePage : ContentPage + { + SKCanvasView canvasView; + bool pageIsActive; + + SKPoint[,] points = + { + { new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() }, + { new SKPoint( 55, 100), new SKPoint( 62.5f, 62.5f), new SKPoint() }, + { new SKPoint( 100, 55), new SKPoint( 62.5f, 62.5f), new SKPoint() }, + { new SKPoint( 100, 0), new SKPoint( 125, 0), new SKPoint() }, + { new SKPoint( 100, -55), new SKPoint( 62.5f, -62.5f), new SKPoint() }, + { new SKPoint( 55, -100), new SKPoint( 62.5f, -62.5f), new SKPoint() }, + { new SKPoint( 0, -100), new SKPoint( 0, -125), new SKPoint() }, + { new SKPoint( -55, -100), new SKPoint(-62.5f, -62.5f), new SKPoint() }, + { new SKPoint(-100, -55), new SKPoint(-62.5f, -62.5f), new SKPoint() }, + { new SKPoint(-100, 0), new SKPoint( -125, 0), new SKPoint() }, + { new SKPoint(-100, 55), new SKPoint(-62.5f, 62.5f), new SKPoint() }, + { new SKPoint( -55, 100), new SKPoint(-62.5f, 62.5f), new SKPoint() }, + { new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() } + }; + + SKPaint blueStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Blue, + StrokeWidth = 10 + }; + + SKPaint cyanFill = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Cyan + }; + + public SquaringTheCirclePage() + { + Title = "Squaring the Circle"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + canvas.Translate(info.Width / 2, info.Height / 2); + canvas.Scale(Math.Min(info.Width / 300, info.Height / 300)); + + // Interpolate + TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); + float t = (float)(timeSpan.TotalSeconds % 3 / 3); // 0 to 1 every 3 seconds + t = (1 + (float)Math.Sin(2 * Math.PI * t)) / 2; // 0 to 1 to 0 sinusoidally + + for (int i = 0; i < 13; i++) + { + points[i, 2] = new SKPoint( + (1 - t) * points[i, 0].X + t * points[i, 1].X, + (1 - t) * points[i, 0].Y + t * points[i, 1].Y); + } + + // Create the path and draw it + using (SKPath path = new SKPath()) + { + path.MoveTo(points[0, 2]); + + for (int i = 1; i < 13; i += 3) + { + path.CubicTo(points[i, 2], points[i + 1, 2], points[i + 2, 2]); + } + path.Close(); + + canvas.DrawPath(path, cyanFill); + canvas.DrawPath(path, blueStroke); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml new file mode 100644 index 000000000..afbdb2b3a --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml @@ -0,0 +1,27 @@ + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml.cs new file mode 100644 index 000000000..9b9e523bf --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TangentArcPage.xaml.cs @@ -0,0 +1,93 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Curves; + +public partial class TangentArcPage : InteractivePage +{ + public TangentArcPage() + { + touchPoints = new TouchPoint[3]; + + for (int i = 0; i < 3; i++) + { + TouchPoint touchPoint = new TouchPoint + { + Center = new SKPoint(i == 0 ? 100 : 300, + i != 2 ? 100 : 300) + }; + touchPoints[i] = touchPoint; + } + + InitializeComponent(); + + baseCanvasView = canvasView; + radiusSlider.Value = 100; + } + + void sliderValueChanged(object sender, ValueChangedEventArgs args) + { + if (canvasView != null) + { + canvasView.InvalidateSurface(); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Draw the two lines that meet at an angle + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.LineTo(touchPoints[1].Center); + path.LineTo(touchPoints[2].Center); + canvas.DrawPath(path, dottedStrokePaint); + } + + // Draw the circle that the arc wraps around + float radius = (float)radiusSlider.Value; + + SKPoint v1 = Normalize(touchPoints[0].Center - touchPoints[1].Center); + SKPoint v2 = Normalize(touchPoints[2].Center - touchPoints[1].Center); + + double dotProduct = v1.X * v2.X + v1.Y * v2.Y; + double angleBetween = Math.Acos(dotProduct); + float hypotenuse = radius / (float)Math.Sin(angleBetween / 2); + SKPoint vMid = Normalize(new SKPoint((v1.X + v2.X) / 2, (v1.Y + v2.Y) / 2)); + SKPoint center = new SKPoint(touchPoints[1].Center.X + vMid.X * hypotenuse, + touchPoints[1].Center.Y + vMid.Y * hypotenuse); + + canvas.DrawCircle(center.X, center.Y, radius, this.strokePaint); + + // Draw the tangent arc + using (SKPath path = new SKPath()) + { + path.MoveTo(touchPoints[0].Center); + path.ArcTo(touchPoints[1].Center, touchPoints[2].Center, radius); + canvas.DrawPath(path, redStrokePaint); + } + + foreach (TouchPoint touchPoint in touchPoints) + { + touchPoint.Paint(canvas); + } + } + + // Vector methods + SKPoint Normalize(SKPoint v) + { + float magnitude = Magnitude(v); + return new SKPoint(v.X / magnitude, v.Y / magnitude); + } + + float Magnitude(SKPoint v) + { + return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y); + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml new file mode 100644 index 000000000..474637921 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml @@ -0,0 +1,12 @@ + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml.cs new file mode 100644 index 000000000..00c8f002c --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TapToOutlineThePathPage.xaml.cs @@ -0,0 +1,78 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves; + +public partial class TapToOutlineThePathPage : ContentPage +{ + bool outlineThePath = false; + + SKPaint redThickStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Red, + StrokeWidth = 100 + }; + + SKPaint redThinStroke = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Red, + StrokeWidth = 20 + }; + + SKPaint blueFill = new SKPaint + { + Style = SKPaintStyle.Fill, + Color = SKColors.Blue + }; + + public TapToOutlineThePathPage() + { + InitializeComponent(); + } + + void OnTouch(object sender, SKTouchEventArgs e) + { + if (e.ActionType == SKTouchAction.Pressed) + { + outlineThePath ^= true; + (sender as SKCanvasView).InvalidateSurface(); + } + + e.Handled = true; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath circlePath = new SKPath()) + { + circlePath.AddCircle(info.Width / 2, info.Height / 2, + Math.Min(info.Width / 2, info.Height / 2) - + redThickStroke.StrokeWidth); + + if (!outlineThePath) + { + canvas.DrawPath(circlePath, blueFill); + canvas.DrawPath(circlePath, redThickStroke); + } + else + { + using (SKPath outlinePath = new SKPath()) + { + redThickStroke.GetFillPath(circlePath, outlinePath); + + canvas.DrawPath(outlinePath, blueFill); + canvas.DrawPath(outlinePath, redThinStroke); + } + } + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TextPathEffectPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TextPathEffectPage.cs new file mode 100644 index 000000000..63b2f86c8 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/TextPathEffectPage.cs @@ -0,0 +1,71 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class TextPathEffectPage : ContentPage + { + const string character = "@"; + const float littleSize = 50; + + SKPathEffect pathEffect; + + SKPaint textPathPaint = new SKPaint + { + TextSize = littleSize + }; + + SKPaint textPaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + Color = SKColors.Black + }; + + public TextPathEffectPage() + { + Title = "Text Path Effect"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + + // Get the bounds of textPathPaint + SKRect textPathPaintBounds = new SKRect(); + textPathPaint.MeasureText(character, ref textPathPaintBounds); + + // Create textPath centered around (0, 0) + SKPath textPath = textPathPaint.GetTextPath(character, + -textPathPaintBounds.MidX, + -textPathPaintBounds.MidY); + // Create the path effect + pathEffect = SKPathEffect.Create1DPath(textPath, littleSize, 0, + SKPath1DPathEffectStyle.Translate); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Set textPaint TextSize based on screen size + textPaint.TextSize = Math.Min(info.Width, info.Height); + + // Do not measure the text with PathEffect set! + SKRect textBounds = new SKRect(); + textPaint.MeasureText(character, ref textBounds); + + // Coordinates to center text on screen + float xText = info.Width / 2 - textBounds.MidX; + float yText = info.Height / 2 - textBounds.MidY; + + // Set the PathEffect property and display text + textPaint.PathEffect = pathEffect; + canvas.DrawText(character, xText, yText, textPaint); + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/UnicycleHalfPipePage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/UnicycleHalfPipePage.cs new file mode 100644 index 000000000..687e106fe --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Curves/UnicycleHalfPipePage.cs @@ -0,0 +1,94 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Curves +{ + public class UnicycleHalfPipePage : ContentPage + { + SKCanvasView canvasView; + bool pageIsActive; + + SKPaint strokePaint = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 3, + Color = SKColors.Black + }; + + SKPath unicyclePath = SKPath.ParseSvgPathData( + "M 0 0" + + "A 25 25 0 0 0 0 -50" + + "A 25 25 0 0 0 0 0 Z" + + "M 0 -25 L 0 -100" + + "A 15 15 0 0 0 0 -130" + + "A 15 15 0 0 0 0 -100 Z" + + "M -25 -85 L 25 -85"); + + public UnicycleHalfPipePage() + { + Title = "Unicycle Half-Pipe"; + + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + pageIsActive = true; + + Dispatcher.StartTimer(TimeSpan.FromSeconds(1f / 60), () => + { + canvasView.InvalidateSurface(); + return pageIsActive; + }); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + pageIsActive = false; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPath pipePath = new SKPath()) + { + pipePath.MoveTo(50, 50); + pipePath.CubicTo(0, 1.25f * info.Height, + info.Width - 0, 1.25f * info.Height, + info.Width - 50, 50); + + canvas.DrawPath(pipePath, strokePaint); + + using (SKPathMeasure pathMeasure = new SKPathMeasure(pipePath)) + { + float length = pathMeasure.Length; + + // Animate t from 0 to 1 every three seconds + TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks); + float t = (float)(timeSpan.TotalSeconds % 5 / 5); + + // t from 0 to 1 to 0 but slower at beginning and end + t = (float)((1 - Math.Cos(t * 2 * Math.PI)) / 2); + + SKMatrix matrix; + pathMeasure.GetMatrix(t * length, out matrix, + SKPathMeasureMatrixFlags.GetPositionAndTangent); + + canvas.SetMatrix(matrix); + canvas.DrawPath(unicyclePath, strokePaint); + } + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AlgorithmicBrickWallPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AlgorithmicBrickWallPage.cs new file mode 100644 index 000000000..e25a7f775 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AlgorithmicBrickWallPage.cs @@ -0,0 +1,82 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Effects +{ + public class AlgorithmicBrickWallPage : ContentPage + { + static AlgorithmicBrickWallPage() + { + const int brickWidth = 64; + const int brickHeight = 24; + const int morterThickness = 6; + const int bitmapWidth = brickWidth + morterThickness; + const int bitmapHeight = 2 * (brickHeight + morterThickness); + + SKBitmap bitmap = new SKBitmap(bitmapWidth, bitmapHeight); + + using (SKCanvas canvas = new SKCanvas(bitmap)) + using (SKPaint brickPaint = new SKPaint()) + { + brickPaint.Color = new SKColor(0xB2, 0x22, 0x22); + + canvas.Clear(new SKColor(0xF0, 0xEA, 0xD6)); + canvas.DrawRect(new SKRect(morterThickness / 2, + morterThickness / 2, + morterThickness / 2 + brickWidth, + morterThickness / 2 + brickHeight), + brickPaint); + + int ySecondBrick = 3 * morterThickness / 2 + brickHeight; + + canvas.DrawRect(new SKRect(0, + ySecondBrick, + bitmapWidth / 2 - morterThickness / 2, + ySecondBrick + brickHeight), + brickPaint); + + canvas.DrawRect(new SKRect(bitmapWidth / 2 + morterThickness / 2, + ySecondBrick, + bitmapWidth, + ySecondBrick + brickHeight), + brickPaint); + } + + // Save as public property for other programs + BrickWallTile = bitmap; + } + + public static SKBitmap BrickWallTile { private set; get; } + + public AlgorithmicBrickWallPage() + { + Title = "Algorithmic Brick Wall"; + + // Create SKCanvasView + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint paint = new SKPaint()) + { + // Create bitmap tiling + paint.Shader = SKShader.CreateBitmap(BrickWallTile, + SKShaderTileMode.Repeat, + SKShaderTileMode.Repeat); + // Draw background + canvas.DrawRect(info.Rect, paint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AnimatedBitmapTilePage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AnimatedBitmapTilePage.cs new file mode 100644 index 000000000..35607fdf5 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/AnimatedBitmapTilePage.cs @@ -0,0 +1,95 @@ +using System.Diagnostics; +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Effects +{ + public class AnimatedBitmapTilePage : ContentPage + { + const int SIZE = 64; + + SKCanvasView canvasView; + SKBitmap bitmap = new SKBitmap(SIZE, SIZE); + float angle; + + // For animation + bool isAnimating; + Stopwatch stopwatch = new Stopwatch(); + + public AnimatedBitmapTilePage() + { + Title = "Animated Bitmap Tile"; + + // Initialize bitmap prior to animation + DrawBitmap(); + + // Create SKCanvasView + canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + protected override void OnAppearing() + { + base.OnAppearing(); + + isAnimating = true; + stopwatch.Start(); + Dispatcher.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + + stopwatch.Stop(); + isAnimating = false; + } + + bool OnTimerTick() + { + const int duration = 10; // seconds + angle = (float)(360f * (stopwatch.Elapsed.TotalSeconds % duration) / duration); + DrawBitmap(); + canvasView.InvalidateSurface(); + + return isAnimating; + } + + void DrawBitmap() + { + using (SKCanvas canvas = new SKCanvas(bitmap)) + using (SKPaint paint = new SKPaint()) + { + paint.Style = SKPaintStyle.Stroke; + paint.Color = SKColors.Blue; + paint.StrokeWidth = SIZE / 8; + + canvas.Clear(); + canvas.Translate(SIZE / 2, SIZE / 2); + canvas.RotateDegrees(angle); + canvas.DrawLine(-SIZE, -SIZE, SIZE, SIZE, paint); + canvas.DrawLine(-SIZE, SIZE, SIZE, -SIZE, paint); + } + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint paint = new SKPaint()) + { + paint.Shader = SKShader.CreateBitmap(bitmap, + SKShaderTileMode.Mirror, + SKShaderTileMode.Mirror); + canvas.DrawRect(info.Rect, paint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml new file mode 100644 index 000000000..20467763a --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml.cs new file mode 100644 index 000000000..49bd2b244 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BitmapTileFlipModesPage.xaml.cs @@ -0,0 +1,56 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; + +namespace SkiaSharpDemos.Effects; + +public partial class BitmapTileFlipModesPage : ContentPage +{ + SKBitmap bitmap; + + public BitmapTileFlipModesPage() + { + InitializeComponent(); + + SKBitmap origBitmap = BitmapExtensions.LoadBitmapResource( + GetType(), "SkiaSharpDemos.Media.seatedmonkey.jpg"); + + // Define cropping rect + SKRectI cropRect = new SKRectI(5, 27, 296, 260); + + // Get the cropped bitmap + SKBitmap croppedBitmap = new SKBitmap(cropRect.Width, cropRect.Height); + origBitmap.ExtractSubset(croppedBitmap, cropRect); + + // Resize to half the width and height + SKImageInfo info = new SKImageInfo(cropRect.Width / 2, cropRect.Height / 2); + bitmap = croppedBitmap.Resize(info, SKFilterQuality.Low); + } + + void OnPickerSelectedIndexChanged(object sender, EventArgs args) + { + canvasView.InvalidateSurface(); + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + // Get tile modes from Pickers + SKShaderTileMode xTileMode = + (SKShaderTileMode)(xModePicker.SelectedIndex == -1 ? + 0 : xModePicker.SelectedItem); + SKShaderTileMode yTileMode = + (SKShaderTileMode)(yModePicker.SelectedIndex == -1 ? + 0 : yModePicker.SelectedItem); + + using (SKPaint paint = new SKPaint()) + { + paint.Shader = SKShader.CreateBitmap(bitmap, xTileMode, yTileMode); + canvas.DrawRect(info.Rect, paint); + } + } +} \ No newline at end of file diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlueBananaPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlueBananaPage.cs new file mode 100644 index 000000000..7e1680828 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlueBananaPage.cs @@ -0,0 +1,66 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Effects +{ + public class BlueBananaPage : ContentPage + { + SKBitmap bitmap = BitmapExtensions.LoadBitmapResource( + typeof(BlueBananaPage), + "SkiaSharpDemos.Media.banana.jpg"); + + SKBitmap blueBananaBitmap; + + public BlueBananaPage() + { + Title = "Blue Banana"; + + // Load banana matte bitmap (black on transparent) + SKBitmap matteBitmap = BitmapExtensions.LoadBitmapResource( + typeof(BlueBananaPage), + "SkiaSharpDemos.Media.bananamatte.png"); + + // Create a bitmap with a solid blue banana and transparent otherwise + blueBananaBitmap = new SKBitmap(matteBitmap.Width, matteBitmap.Height); + + using (SKCanvas canvas = new SKCanvas(blueBananaBitmap)) + { + canvas.Clear(); + canvas.DrawBitmap(matteBitmap, new SKPoint(0, 0)); + + using (SKPaint paint = new SKPaint()) + { + paint.Color = SKColors.Blue; + paint.BlendMode = SKBlendMode.SrcIn; + canvas.DrawPaint(paint); + } + } + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform); + + using (SKPaint paint = new SKPaint()) + { + paint.BlendMode = SKBlendMode.Color; + canvas.DrawBitmap(blueBananaBitmap, + info.Rect, + BitmapStretch.Uniform, + paint: paint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlurryReflectionPage.cs b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlurryReflectionPage.cs new file mode 100644 index 000000000..8c619b1bf --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BlurryReflectionPage.cs @@ -0,0 +1,74 @@ +using SkiaSharp; +using SkiaSharp.Views.Maui; +using SkiaSharp.Views.Maui.Controls; + +namespace SkiaSharpDemos.Effects +{ + public class BlurryReflectionPage : ContentPage + { + const string TEXT = "Reflection"; + + public BlurryReflectionPage() + { + Title = "Blurry Reflection"; + + SKCanvasView canvasView = new SKCanvasView(); + canvasView.PaintSurface += OnCanvasViewPaintSurface; + Content = canvasView; + } + + void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args) + { + SKImageInfo info = args.Info; + SKSurface surface = args.Surface; + SKCanvas canvas = surface.Canvas; + + canvas.Clear(); + + using (SKPaint paint = new SKPaint()) + { + // Set text color to blue + paint.Color = SKColors.Blue; + + // Set text size to fill 90% of width + paint.TextSize = 100; + float width = paint.MeasureText(TEXT); + float scale = 0.9f * info.Width / width; + paint.TextSize *= scale; + + // Get text bounds + SKRect textBounds = new SKRect(); + paint.MeasureText(TEXT, ref textBounds); + + // Calculate offsets to position text above center + float xText = info.Width / 2 - textBounds.MidX; + float yText = info.Height / 2; + + // Draw unreflected text + canvas.DrawText(TEXT, xText, yText, paint); + + // Shift textBounds to match displayed text + textBounds.Offset(xText, yText); + + // Use those offsets to create a gradient for the reflected text + paint.Shader = SKShader.CreateLinearGradient( + new SKPoint(0, textBounds.Top), + new SKPoint(0, textBounds.Bottom), + new SKColor[] { paint.Color.WithAlpha(0), + paint.Color.WithAlpha(0x80) }, + null, + SKShaderTileMode.Clamp); + + // Create a blur mask filter + paint.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, paint.TextSize / 36); + + // Scale the canvas to flip upside-down around the vertical center + canvas.Scale(1, -1, 0, yText); + + // Draw reflected text + canvas.DrawText(TEXT, xText, yText, paint); + } + } + } +} + diff --git a/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BrickWallCompositingPage.xaml b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BrickWallCompositingPage.xaml new file mode 100644 index 000000000..9ab9db7c2 --- /dev/null +++ b/8.0/SkiaSharp/SkiaSharpDemos/SkiaSharpDemos/Effects/BrickWallCompositingPage.xaml @@ -0,0 +1,17 @@ + + + + +