Skip to content

Commit 3baca9b

Browse files
mattleibowCopilot
andcommitted
Add DeepZoom static preview services, tests, and samples
Extract the core DeepZoom rendering system from PR #378: - DeepZoom collection/image source parsers (DZC/DZI XML) - Tile cache, scheduler, and HTTP/file fetchers - Controller (orchestrates loading), viewport (centered-fit geometry), renderer (draws tiles) - 436 unit tests (animation tests excluded — no dependency on gestures/animations) - Blazor sample page (Deep Zoom Preview) with testgrid DZI static asset - MAUI sample page (Deep Zoom Preview) with testgrid DZI app-package asset - Documentation: deep-zoom.md, deep-zoom-blazor.md, deep-zoom-maui.md - Solution file updated to include DeepZoom test project - DeepZoom added to MAUI ExtendedDemos and Blazor NavMenu No gestures, animations, or custom controls included. This is a minimal static preview system for gigapixel images. Images render centered-fit (aspect ratio preserved, no cropping/stretching). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4f16bb8 commit 3baca9b

2,798 files changed

Lines changed: 9744 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

SkiaSharp.Extended.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Maui.
2121
EndProject
2222
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharpDemo.Blazor", "samples\SkiaSharpDemo.Blazor\SkiaSharpDemo.Blazor.csproj", "{B7E4C45C-5CAB-444E-B2D3-294151544256}"
2323
EndProject
24+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.DeepZoom.Tests", "tests\SkiaSharp.Extended.DeepZoom.Tests\SkiaSharp.Extended.DeepZoom.Tests.csproj", "{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}"
25+
EndProject
2426
Global
2527
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2628
Debug|Any CPU = Debug|Any CPU
@@ -53,6 +55,10 @@ Global
5355
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.Build.0 = Debug|Any CPU
5456
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.ActiveCfg = Release|Any CPU
5557
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.Build.0 = Release|Any CPU
58+
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
59+
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
60+
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
61+
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|Any CPU.Build.0 = Release|Any CPU
5662
EndGlobalSection
5763
GlobalSection(SolutionProperties) = preSolution
5864
HideSolutionNode = FALSE
@@ -64,6 +70,7 @@ Global
6470
{2C67033A-2C49-4146-B942-9CDD2E0BA412} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
6571
{4B4EC78C-33B5-456D-BD7D-4358D16272F4} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
6672
{B7E4C45C-5CAB-444E-B2D3-294151544256} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
73+
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
6774
EndGlobalSection
6875
GlobalSection(ExtensibilityGlobals) = postSolution
6976
SolutionGuid = {08D78153-5DD7-4C52-A348-46AA448B2CFC}

docs/docs/deep-zoom-blazor.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Deep Zoom for Blazor
2+
3+
Use `SKDeepZoomController` with a plain `SKCanvasView` to render Deep Zoom images in Blazor WebAssembly. There is no custom component — the page wires the services directly to the canvas.
4+
5+
## Quick Start
6+
7+
```razor
8+
@page "/deepzoom"
9+
@implements IDisposable
10+
@inject HttpClient Http
11+
@using SkiaSharp.Extended.DeepZoom
12+
13+
<SKCanvasView @ref="_canvas"
14+
OnPaintSurface="OnPaintSurface"
15+
style="width: 100%; height: 600px; border: 1px solid #ccc;" />
16+
17+
@code {
18+
private SKCanvasView? _canvas;
19+
private SKDeepZoomController? _controller;
20+
21+
protected override async Task OnAfterRenderAsync(bool firstRender)
22+
{
23+
if (!firstRender) return;
24+
25+
_controller = new SKDeepZoomController();
26+
_controller.InvalidateRequired += OnInvalidateRequired;
27+
28+
var xml = await Http.GetStringAsync("deepzoom/image.dzi");
29+
var baseUrl = new Uri(Http.BaseAddress!, "deepzoom/image_files/").ToString();
30+
var tileSource = SKDeepZoomImageSource.Parse(xml, baseUrl);
31+
32+
_controller.Load(tileSource, new SKDeepZoomHttpTileFetcher(new HttpClient()));
33+
34+
await InvokeAsync(StateHasChanged);
35+
}
36+
37+
private void OnPaintSurface(SKPaintSurfaceEventArgs e)
38+
{
39+
if (_controller == null) return;
40+
41+
_controller.SetControlSize(e.Info.Width, e.Info.Height);
42+
_controller.Update();
43+
_controller.Render(e.Surface.Canvas);
44+
}
45+
46+
private void OnInvalidateRequired(object? sender, EventArgs e)
47+
=> InvokeAsync(() => _canvas?.Invalidate());
48+
49+
public void Dispose()
50+
{
51+
if (_controller != null)
52+
{
53+
_controller.InvalidateRequired -= OnInvalidateRequired;
54+
_controller.Dispose();
55+
}
56+
}
57+
}
58+
```
59+
60+
## Serving Tile Assets
61+
62+
Place `.dzi` and tile folder under `wwwroot`. In the project file, reference them as content:
63+
64+
```xml
65+
<ItemGroup>
66+
<Content Include="wwwroot\deepzoom\image.dzi" />
67+
<Content Include="wwwroot\deepzoom\image_files\**" />
68+
</ItemGroup>
69+
```
70+
71+
The `SKDeepZoomHttpTileFetcher` fetches each tile URL via `HttpClient`; tile URLs are constructed automatically from the base URL you supply to `SKDeepZoomImageSource.Parse`.
72+
73+
## Rendering Behaviour
74+
75+
- **Fit and center**: On load the controller calls `FitToView()` so the full image is visible and centered in the canvas. Neither cropping nor distortion occurs.
76+
- **Tile resolution**: `Update()` selects the pyramid level whose tile size best matches the physical pixel dimensions of the canvas. Only visible tiles are requested.
77+
- **Tile blending**: While high-resolution tiles are in-flight, parent-level tiles are upscaled and drawn as placeholders.
78+
79+
## Related
80+
81+
- [Deep Zoom overview](deep-zoom.md) — architecture, services, and API reference
82+
- [Deep Zoom for MAUI](deep-zoom-maui.md) — .NET MAUI integration
83+

docs/docs/deep-zoom-maui.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Deep Zoom for MAUI
2+
3+
Use `SKDeepZoomController` with a plain `SKCanvasView` to render Deep Zoom images in .NET MAUI. There is no custom control — you wire the services directly to the canvas, which keeps the integration minimal and transparent.
4+
5+
## Quick Start
6+
7+
### XAML
8+
9+
Add a `SKCanvasView` to your page:
10+
11+
```xml
12+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
13+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
14+
xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
15+
x:Class="MyApp.DeepZoomPage"
16+
Title="Deep Zoom">
17+
18+
<skia:SKCanvasView x:Name="canvas" PaintSurface="OnPaintSurface" />
19+
20+
</ContentPage>
21+
```
22+
23+
### Code-Behind
24+
25+
```csharp
26+
using SkiaSharp;
27+
using SkiaSharp.Extended.DeepZoom;
28+
using SkiaSharp.Views.Maui;
29+
30+
public partial class DeepZoomPage : ContentPage
31+
{
32+
private readonly SKDeepZoomController _controller = new();
33+
34+
public DeepZoomPage()
35+
{
36+
InitializeComponent();
37+
_controller.InvalidateRequired += (_, _) => canvas.InvalidateSurface();
38+
}
39+
40+
protected override void OnAppearing()
41+
{
42+
base.OnAppearing();
43+
LoadAsync();
44+
}
45+
46+
protected override void OnDisappearing()
47+
{
48+
base.OnDisappearing();
49+
_controller.Dispose();
50+
}
51+
52+
private async void LoadAsync()
53+
{
54+
// Load a .dzi bundled as a MAUI app-package asset
55+
using var stream = await FileSystem.OpenAppPackageFileAsync("image.dzi");
56+
using var reader = new StreamReader(stream);
57+
var xml = await reader.ReadToEndAsync();
58+
59+
var tileSource = SKDeepZoomImageSource.Parse(xml, "image_files/");
60+
_controller.Load(tileSource, new AppPackageFetcher());
61+
canvas.InvalidateSurface();
62+
}
63+
64+
private void OnPaintSurface(object? sender, SKPaintSurfaceEventArgs e)
65+
{
66+
_controller.SetControlSize(e.Info.Width, e.Info.Height);
67+
_controller.Update();
68+
_controller.Render(e.Surface.Canvas);
69+
}
70+
}
71+
```
72+
73+
## App-Package Tile Fetcher
74+
75+
When tiles are bundled as MAUI assets, implement `ISKDeepZoomTileFetcher` to read them via `FileSystem.OpenAppPackageFileAsync`:
76+
77+
```csharp
78+
public sealed class AppPackageFetcher : ISKDeepZoomTileFetcher
79+
{
80+
public async Task<SKBitmap?> FetchTileAsync(string url, CancellationToken ct = default)
81+
{
82+
try
83+
{
84+
using var stream = await FileSystem.OpenAppPackageFileAsync(url);
85+
return SKBitmap.Decode(stream);
86+
}
87+
catch
88+
{
89+
return null;
90+
}
91+
}
92+
93+
public void Dispose() { }
94+
}
95+
```
96+
97+
Include the `.dzi` and tile folder in the project file:
98+
99+
```xml
100+
<ItemGroup>
101+
<MauiAsset Include="Assets\image.dzi" LogicalName="image.dzi" />
102+
<MauiAsset Include="Assets\image_files\**" LogicalName="image_files/%(RecursiveDir)%(Filename)%(Extension)" />
103+
</ItemGroup>
104+
```
105+
106+
## HTTP Tile Fetcher
107+
108+
For remote images, use the built-in `SKDeepZoomHttpTileFetcher`:
109+
110+
```csharp
111+
var httpClient = new HttpClient();
112+
var fetcher = new SKDeepZoomHttpTileFetcher(httpClient);
113+
114+
var xml = await httpClient.GetStringAsync("https://example.com/image.dzi");
115+
var tileSource = SKDeepZoomImageSource.Parse(xml, "https://example.com/image_files/");
116+
117+
_controller.Load(tileSource, fetcher);
118+
```
119+
120+
## Rendering Behaviour
121+
122+
- **Fit and center**: On load the controller fits the full image into the canvas with `FitToView()`. The image is centered horizontally and vertically; neither cropping nor distortion occurs.
123+
- **Tile resolution**: `Update()` selects the pyramid level that best matches the physical pixel size of the canvas. Only tiles visible in the current viewport are fetched.
124+
- **Tile blending**: While high-resolution tiles load, lower-resolution tiles from the parent level are upscaled and composited as a placeholder (LOD blending).
125+
126+
## Related
127+
128+
- [Deep Zoom overview](deep-zoom.md) — architecture, services, and API reference
129+
- [Deep Zoom for Blazor](deep-zoom-blazor.md) — Blazor WebAssembly integration

0 commit comments

Comments
 (0)