diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/MainLayout.razor.css b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/MainLayout.razor.css index ecf25e5b2e..7b1f5c62a5 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/MainLayout.razor.css +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/MainLayout.razor.css @@ -2,10 +2,15 @@ position: relative; display: flex; flex-direction: column; + height: 100vh; + overflow: hidden; } main { flex: 1; + display: flex; + flex-direction: column; + min-height: 0; } .sidebar { diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor index 548e2130c4..27f3e0d5fd 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor @@ -11,17 +11,17 @@ diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor.css b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor.css index 881d128a5f..e67566a11e 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor.css +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Layout/NavMenu.razor.css @@ -25,12 +25,12 @@ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); } -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +.bi-gpu-card-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-gpu-card' viewBox='0 0 16 16'%3E%3Cpath d='M4 8a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0m7.5-1.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3'/%3E%3Cpath d='M0 1.5A.5.5 0 0 1 .5 1h1a.5.5 0 0 1 .5.5V4h13.5a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-.5.5H2v2.5a.5.5 0 0 1-1 0V2H.5a.5.5 0 0 1-.5-.5m5.5 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M9 8a2.5 2.5 0 1 0 5 0 2.5 2.5 0 0 0-5 0'/%3E%3Cpath d='M3 12.5h3.5v1a.5.5 0 0 1-.5.5H3.5a.5.5 0 0 1-.5-.5zm4 0h4v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); } -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +.bi-pencil-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-pencil-fill' viewBox='0 0 16 16'%3E%3Cpath d='M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.5.5 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11z'/%3E%3C/svg%3E"); } .nav-item { diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor new file mode 100644 index 0000000000..2bf287e101 --- /dev/null +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor @@ -0,0 +1,159 @@ +@page "/drawing" + +@* Drawing Canvas Demo + Demonstrates SKCanvasView with pointer events, wheel delta for + brush size control, and basic stroke rendering. + Shows multi-stroke drawing with color picker and on-demand rendering + via manual Invalidate() (no render loop). *@ + +

Drawing Canvas

+ +

+ Draw with touch or mouse. Use the scroll wheel to change brush size. +

+ +
+ + + +
+ Brush: @(brushSize.ToString("F2"))px +
+ +
+ @foreach (var (name, color) in colors) + { +
+ } + +
+ +
+ +@code { + + SKCanvasView skiaView = null!; + float brushSize = 25f; + SKColor currentColor = SKColors.Black; + List strokes = []; + DrawingStroke? currentStroke = null; + SKPoint lastPoint; + + // The paint that is reused for drawing strokes. + readonly SKPaint strokePaint = new() + { + IsAntialias = true, + Style = SKPaintStyle.Stroke, + StrokeCap = SKStrokeCap.Round, + StrokeJoin = SKStrokeJoin.Round, + }; + + // The available brush colors with their CSS names. + static readonly (string Name, SKColor Color)[] colors = + { + ("black", SKColors.Black), + ("red", SKColors.Red), + ("blue", SKColors.Blue), + ("green", SKColors.Green), + ("orange", SKColors.Orange), + ("purple", SKColors.Purple), + }; + + void OnPaintSurface(SKPaintSurfaceEventArgs e) + { + var canvas = e.Surface.Canvas; + + // Clear to white before drawing the strokes. + canvas.Clear(SKColors.White); + + // Draw completed strokes. + foreach (var stroke in strokes) + { + strokePaint.Color = stroke.Color; + strokePaint.StrokeWidth = stroke.StrokeWidth; + canvas.DrawPath(stroke.Path, strokePaint); + } + + // Draw in-progress stroke. + if (currentStroke is not null) + { + strokePaint.Color = currentStroke.Color; + strokePaint.StrokeWidth = currentStroke.StrokeWidth; + canvas.DrawPath(currentStroke.Path, strokePaint); + } + + // Brush size indicator at last pointer position. + strokePaint.Color = SKColors.Gray; + strokePaint.StrokeWidth = 1; + canvas.DrawCircle(lastPoint, brushSize / 2f, strokePaint); + } + + void OnPointerDown(PointerEventArgs e) + { + if (!e.IsPrimary) + return; + lastPoint = new SKPoint((float)e.OffsetX, (float)e.OffsetY); + currentStroke = new DrawingStroke(new(), currentColor, brushSize); + currentStroke.Path.MoveTo(lastPoint); + skiaView.Invalidate(); + } + + void OnPointerMove(PointerEventArgs e) + { + if (!e.IsPrimary) + return; + lastPoint = new SKPoint((float)e.OffsetX, (float)e.OffsetY); + if (currentStroke is not null) + currentStroke.Path.LineTo(lastPoint); + skiaView.Invalidate(); + } + + void OnPointerUp(PointerEventArgs e) + { + if (!e.IsPrimary) + return; + if (currentStroke is not null) + { + strokes.Add(currentStroke); + currentStroke = null; + skiaView.Invalidate(); + } + } + + void OnWheel(WheelEventArgs e) + { + // DeltaY > 0 = scroll down = decrease brush size; DeltaY < 0 = scroll up = increase. + // Normalize ~100 CSS pixels per notch to ~1px brush change per notch. + brushSize -= (float)(e.DeltaY / 100.0); + brushSize = Math.Clamp(brushSize, 1f, 50f); + StateHasChanged(); + } + + void SetColor(SKColor color) + { + currentColor = color; + skiaView.Invalidate(); + StateHasChanged(); + } + + void ClearCanvas() + { + foreach (var stroke in strokes) + { + stroke.Path.Dispose(); + } + strokes.Clear(); + currentStroke?.Path.Dispose(); + currentStroke = null; + skiaView.Invalidate(); + StateHasChanged(); + } + + // Represents a single drawing stroke with its path, color, and width. + // Path is owned — callers must dispose when removing strokes. + record DrawingStroke(SKPath Path, SKColor Color, float StrokeWidth = 25); +} diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor.css b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor.css new file mode 100644 index 0000000000..f116fa8114 --- /dev/null +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Drawing.razor.css @@ -0,0 +1,47 @@ +.canvas-container { + position: relative; +} + +.overlay-info { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 1; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 8px; + padding: 6px 16px; + backdrop-filter: blur(4px); + color: white; + font-size: 0.85rem; + font-weight: 500; +} + +.overlay-toolbar { + position: absolute; + bottom: 16px; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 8px; + align-items: center; + z-index: 1; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 0, 0, 0.4); + border-radius: 8px; + padding: 8px 16px; + backdrop-filter: blur(4px); +} + +.color-swatch { + width: 26px; + height: 26px; + border-radius: 50%; + cursor: pointer; + border: 2px solid transparent; +} + +.color-swatch.selected { + border-color: white; +} diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor index 25097e87bb..b9aa740f1d 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor @@ -1,86 +1,203 @@ -@page "/gpu" +@page "/gpu" -

GPU (WebGL) Canvas

+@* GPU Canvas Demo + Demonstrates SKGLView with EnableRenderLoop for continuous animation, + SKRuntimeEffect.BuildShader() for compiling SkSL shaders, and + touch interaction via uniforms. The entire visual is rendered in SkSL — + no C# draw calls. C# only handles touch input and passes uniforms. *@ -

The canvas below is using WebGL. See the great FPS!

+

GPU Canvas

-
-
-
+

+ Pure SkSL lava lamp — all rendering in the shader. + Tap or drag to add a blob. +

- +
-
+ + +
+ @($"{displayFps:F2}") fps
+
@code { + + // SkSL shader source: a "lava lamp" metaball effect. + // 6 colored blobs orbit on Lissajous curves. When the user touches, + // an extra white-hot blob appears at the touch position and merges + // with the others. All rendering is per-pixel in the shader. + const string sksl = @" + // Uniforms passed from C# each frame + uniform float iTime; // elapsed seconds + uniform float2 iResolution; // canvas size in pixels + uniform float2 iTouchPos; // touch position normalized 0..1 + uniform float iTouchActive; // 1.0 when touching, 0.0 otherwise + + half4 main(float2 fragCoord) { + // Convert pixel coords to normalized UV and aspect-corrected coords + float2 uv = fragCoord / iResolution; + float aspect = iResolution.x / iResolution.y; + float2 st = float2(uv.x * aspect, uv.y); + float t = iTime; + + // Metaball field: accumulate 1/r² contributions from each blob. + // 'weighted' tracks color contribution weighted by field strength. + float field = 0.0; + float3 weighted = float3(0.0); + + // Color palette for the 6 orbiting blobs + float3 colors[6]; + colors[0] = float3(1.0, 0.3, 0.4); // hot pink + colors[1] = float3(0.3, 0.7, 1.0); // sky blue + colors[2] = float3(1.0, 0.6, 0.1); // orange + colors[3] = float3(0.4, 1.0, 0.7); // mint + colors[4] = float3(0.7, 0.3, 1.0); // purple + colors[5] = float3(1.0, 0.9, 0.2); // yellow + + // Each blob orbits on a Lissajous curve with unique phase and speed + for (int i = 0; i < 6; i++) { + float fi = float(i); + float phase = fi * 1.047; // evenly spaced: 2*pi/6 + float speed = 0.3 + fi * 0.07; + + // Lissajous orbit center + float2 center = float2( + aspect * 0.5 + 0.4 * sin(t * speed + phase) * cos(t * speed * 0.6 + fi), + 0.5 + 0.4 * cos(t * speed * 0.8 + phase * 1.3) * sin(t * speed * 0.4 + fi * 0.7) + ); + + // Metaball field: strength falls off as 1/r², creating smooth merging + float2 d = st - center; + float r = length(d); + float strength = 0.030 / (r * r + 0.002); + field += strength; + weighted += colors[i] * strength; + } + + // Touch interaction: add an extra, larger blob at the touch position + if (iTouchActive > 0.5) { + float2 touchSt = float2(iTouchPos.x * aspect, iTouchPos.y); + float2 d = st - touchSt; + float r = length(d); + float strength = 0.050 / (r * r + 0.002); + field += strength; + // white-hot touch blob + weighted += float3(1.0, 0.95, 0.9) * strength; + } + + // Normalize accumulated color by field strength for smooth blending + float3 blobColor = weighted / max(field, 0.001); + + // Threshold the field into blob edges with smooth falloff + float edge = smoothstep(5.0, 8.0, field); + float innerGlow = smoothstep(8.0, 20.0, field) * 0.3; + + // Subtly animated dark background + float3 bg = float3(0.03, 0.02, 0.08); + bg += float3(0.02, 0.01, 0.03) * sin(t * 0.2 + uv.y * 3.0); + + // Outer glow: visible between the halo and solid edge + float halo = smoothstep(3.0, 5.0, field) * (1.0 - edge); + + // Composite: background + halo + solid blobs with inner glow + float3 result = bg; + result += blobColor * halo * 0.4; + result = mix(result, blobColor * (1.0 + innerGlow), edge); + + // Vignette: darken corners for depth + float2 vc = uv - 0.5; + float vignette = 1.0 - dot(vc, vc) * 0.8; + result *= vignette; + + return half4(clamp(result, 0.0, 1.0), 1.0); + } + "; + + // SKRuntimeShaderBuilder: compiled once via BuildShader(), then reused. + // Each frame we update uniforms and call Build() to get a new SKShader. + readonly SKRuntimeShaderBuilder builder = SKRuntimeEffect.BuildShader(sksl); + readonly long startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + + // The paint that is reused for drawing the shader quad. + readonly SKPaint shaderPaint = new(); + + // Touch state passed to the shader as uniforms. + float touchX = -1f, touchY = -1f; + float touchActive = 0f; + // FPS display value, updated ~4Hz. + double displayFps; + long lastUiUpdate; + + // Rolling average FPS calculation. int tickIndex = 0; long tickSum = 0; long[] tickList = new long[100]; long lastTick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + // Handle pointer down: start tracking touch position. + void OnPointerDown(PointerEventArgs e) + { + if (!e.IsPrimary) + return; + touchX = (float)e.OffsetX; + touchY = (float)e.OffsetY; + touchActive = 1f; + } + + // Handle pointer move: update touch position while pointer is pressed. + void OnPointerMove(PointerEventArgs e) + { + if (!e.IsPrimary || e.Buttons == 0) + return; + touchX = (float)e.OffsetX; + touchY = (float)e.OffsetY; + } + + // Handle pointer up: clear touch state. + void OnPointerUp(PointerEventArgs e) + { + if (!e.IsPrimary) + return; + touchActive = 0f; + } + + // Called every frame by SKGLView's render loop. void OnPaintSurface(SKPaintGLSurfaceEventArgs e) { - // the the canvas and properties var canvas = e.Surface.Canvas; - - // make sure the canvas is blank - canvas.Clear(SKColors.White); - - using var paint = new SKPaint - { - IsAntialias = true, - StrokeWidth = 5f, - StrokeCap = SKStrokeCap.Round - }; - using var font = new SKFont - { - Size = 24 - }; - - var surfaceSize = e.Info.Size; - var clockSize = Math.Min(surfaceSize.Width, surfaceSize.Height) * 0.4f; - var center = new SKPoint(surfaceSize.Width / 2f, surfaceSize.Height / 2f); - var now = DateTime.Now; - var fps = GetCurrentFPS(); - - // draw the fps counter - canvas.DrawText($"{fps:0.00}fps", surfaceSize.Width / 2, surfaceSize.Height - 10f, SKTextAlign.Center, font, paint); - - // draw the clock - canvas.RotateDegrees(-90f, center.X, center.Y); - - // hours - paint.StrokeWidth = 3f; - canvas.Save(); - canvas.Translate(center); - canvas.RotateDegrees(360f * (now.Hour / 12f)); - canvas.DrawLine(0, 0, clockSize * 0.4f, 0, paint); - canvas.Restore(); - - // minutes - paint.StrokeWidth = 2f; - canvas.Save(); - canvas.Translate(center); - canvas.RotateDegrees(360f * (now.Minute / 60f)); - canvas.DrawLine(0, 0, clockSize * 0.6f, 0, paint); - canvas.Restore(); - - // seconds - paint.StrokeWidth = 1f; - canvas.Save(); - canvas.Translate(center); - canvas.RotateDegrees(360f * ((now.Second * 1000f + now.Millisecond) / 1000f / 60f)); - canvas.DrawLine(0, 0, clockSize * 0.8f, 0, paint); - canvas.Restore(); - - // center - canvas.DrawCircle(center, 10f, paint); - - // border - paint.Style = SKPaintStyle.Stroke; - canvas.DrawCircle(center, clockSize * 0.9f, paint); + var width = e.Info.Width; + var height = e.Info.Height; + + // Update uniforms — no allocations, reuses the builder's internal storage. + var elapsed = (float)(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime) / 1000f; + builder.Uniforms["iTime"] = elapsed; + builder.Uniforms["iResolution"] = new float[] { width, height }; + builder.Uniforms["iTouchPos"] = new float[] { + touchActive > 0 ? touchX / width : -1f, + touchActive > 0 ? touchY / height : -1f + }; + builder.Uniforms["iTouchActive"] = touchActive; + + // Build() creates a shader from cached effect + current uniforms. + // Draw a full-screen quad to run the shader on every pixel. + using var shader = builder.Build(); + shaderPaint.Shader = shader; + canvas.DrawRect(0, 0, width, height, shaderPaint); + + displayFps = GetCurrentFPS(); + + // Throttle Blazor UI updates to ~4Hz for the FPS counter. + var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (now - lastUiUpdate > 250) + { + lastUiUpdate = now; + _ = InvokeAsync(StateHasChanged); + } } double GetCurrentFPS() @@ -88,14 +205,11 @@ var newTick = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var delta = newTick - lastTick; lastTick = newTick; - tickSum -= tickList[tickIndex]; tickSum += delta; tickList[tickIndex] = delta; - if (++tickIndex == tickList.Length) tickIndex = 0; - return 1000.0 / ((double)tickSum / tickList.Length); } } diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor.css b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor.css new file mode 100644 index 0000000000..ad7aa7b51e --- /dev/null +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/GPU.razor.css @@ -0,0 +1,19 @@ +.canvas-container { + position: relative; +} + +.overlay-stats { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 1; + background: rgba(255, 255, 255, 0.5); + border: 1px solid rgba(255, 255, 255, 0.6); + border-radius: 8px; + padding: 6px 16px; + backdrop-filter: blur(4px); + color: black; + font-size: 0.85rem; + font-weight: 500; +} diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Home.razor b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Home.razor index 2011a936c5..41caa361cd 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Home.razor +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/Pages/Home.razor @@ -1,44 +1,106 @@ -@page "/" +@page "/" + +@* Home Page + Demonstrates basic SKCanvasView usage with gradients, shapes, and text. + Shows SKShader.CreateRadialGradient, DrawCircle, DrawText, and SKFont. *@

SkiaSharp

-

SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on Google's Skia Graphics Library. It provides a comprehensive 2D API that can be used across mobile, server and desktop models to render images.

+

+ SkiaSharp is a cross-platform 2D graphics API for .NET platforms based on + Google's Skia Graphics Library, providing comprehensive 2D rendering + across mobile, server, and desktop. +

-
-
-
+
- + -
-
@code { + // The paint that is reused for drawing circles. + readonly SKPaint circlePaint = new() + { + IsAntialias = true, + Style = SKPaintStyle.Fill + }; + + // The circles with normalized positions/sizes and colors. + readonly (float xf, float yf, float rf, SKColor color)[] circles = + { + (0.2f, 0.3f, 0.10f, new SKColor(0xFF, 0x4D, 0x66, 0xCC)), // hot pink + (0.75f, 0.25f, 0.08f, new SKColor(0x4D, 0xB3, 0xFF, 0xCC)), // sky blue + (0.15f, 0.7f, 0.07f, new SKColor(0xFF, 0x99, 0x1A, 0xCC)), // orange + (0.8f, 0.7f, 0.12f, new SKColor(0x66, 0xFF, 0xB3, 0xCC)), // mint + (0.5f, 0.15f, 0.06f, new SKColor(0xB3, 0x4D, 0xFF, 0xCC)), // purple + (0.4f, 0.8f, 0.09f, new SKColor(0xFF, 0xE6, 0x33, 0xCC)), // yellow + }; + + // The paint for the background gradient, reused each frame. + readonly SKPaint bgPaint = new() + { + IsAntialias = true + }; + + // The paint for drawing the centered text. + readonly SKPaint textPaint = new() + { + Color = SKColors.White, + IsAntialias = true, + Style = SKPaintStyle.Fill + }; + + // The font for drawing the centered text. + readonly SKFont textFont = new() + { + Size = 48 + }; + void OnPaintSurface(SKPaintSurfaceEventArgs e) { - // the the canvas and properties var canvas = e.Surface.Canvas; + var width = e.Info.Width; + var height = e.Info.Height; + var center = new SKPoint(width / 2f, height / 2f); + var radius = Math.Max(width, height) / 2f; - // make sure the canvas is blank + // Clear to white before drawing the background gradient and circles. canvas.Clear(SKColors.White); - // decide what the text looks like - using var paint = new SKPaint - { - Color = SKColors.Black, - IsAntialias = true, - Style = SKPaintStyle.Fill - }; - using var font = new SKFont - { - Size = 24 - }; - - // draw some text - var coord = new SKPoint(e.Info.Width / 2, (e.Info.Height + font.Size) / 2); - canvas.DrawText("SkiaSharp", coord, SKTextAlign.Center, font, paint); - } + // Background radial gradient centered on the canvas, fading from blue to purple. + using var bgShader = SKShader.CreateRadialGradient( + center, + radius, + [ + new SKColor(0x44, 0x88, 0xFF), + new SKColor(0x88, 0x33, 0xCC) + ], + SKShaderTileMode.Clamp); + bgPaint.Shader = bgShader; + // Draw a full-screen rectangle with the background gradient shader. + canvas.DrawRect(0, 0, width, height, bgPaint); + + // Draw the circles with their specified colors, positions, and sizes. + foreach (var (xf, yf, rf, color) in circles) + { + circlePaint.Color = color; + canvas.DrawCircle( + xf * width, + yf * height, + rf * Math.Min(width, height), + circlePaint); + } + + // Draw centered "SkiaSharp" text + canvas.DrawText( + "SkiaSharp", + center.X, + center.Y + textFont.Size / 3f, + SKTextAlign.Center, + textFont, + textPaint); + } } diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/css/app.css b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/css/app.css index e9c2cb2d06..877f844090 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/css/app.css +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/css/app.css @@ -1,5 +1,12 @@ html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + height: 100%; + margin: 0; + overflow: hidden; +} + +#app { + height: 100%; } h1:focus { @@ -22,6 +29,12 @@ a, .btn-link { .content { padding-top: 1.1rem; + padding-bottom: 1rem; + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; + overflow: hidden; } .valid.modified:not([type=checkbox]) { @@ -105,9 +118,11 @@ code { .canvas-container { line-height: 1; + flex: 1; + min-height: 0; } .canvas-container canvas { width: 100%; - height: 300px; + height: 100%; } diff --git a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/index.html b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/index.html index 6394d03cf1..c576997971 100644 --- a/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/index.html +++ b/samples/Basic/BlazorWebAssembly/SkiaSharpSample/wwwroot/index.html @@ -5,8 +5,8 @@ SkiaSharpSample - - + +