Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
3baca9b
Add DeepZoom static preview services, tests, and samples
mattleibow Mar 13, 2026
75513d6
fix: use GitHub raw URL for DZI, switch to SKGLView, refit on resize,…
mattleibow Mar 13, 2026
7f9a9af
Fix README diagram inaccuracies for TileRequest and SubImage.Source
mattleibow Mar 13, 2026
247ec2c
feat: responsive canvas, debug inspector, remote URL for MAUI
mattleibow Mar 13, 2026
8416a31
Fix JS resize listener leak and HttpClient disposal
mattleibow Mar 13, 2026
d50e518
Add basic pan and zoom to DeepZoom sample pages
mattleibow Mar 13, 2026
7f64176
Fix pan direction and MAUI cumulative pan bug
mattleibow Mar 13, 2026
e294da1
Fix zoom-out clamp: allow zooming past fit level
mattleibow Mar 14, 2026
018a54d
Fix test assertion to match new FitToView behaviour
mattleibow Mar 14, 2026
c77731b
Increase zoom to 50x, enhance inspector with level/tile size info
mattleibow Mar 14, 2026
6f1467c
Fix tilePxH formula and regenerate testgrid with 1px overlap
mattleibow Mar 14, 2026
6e32aec
Show max detail warning only when tiles are being upscaled
mattleibow Mar 14, 2026
76f7da8
Make DeepZoom collection URL use current git branch
mattleibow Mar 14, 2026
1aca82d
Fix git branch detection to correctly bake branch into assembly
mattleibow Mar 14, 2026
beaa1d9
Use embedded build-info.json config for branch detection
mattleibow Mar 14, 2026
81a212f
Fix AzDO PR branch detection, dispose JsonDocument, align build-info …
mattleibow Mar 14, 2026
bd25f9e
Revert branch detection: hardcode main URL in both samples
mattleibow Mar 14, 2026
4c2663e
Add URL text box to load custom DZC/DZI in sample pages
mattleibow Mar 14, 2026
72ae8ba
Add DZC warning and concurrent load guard
mattleibow Mar 14, 2026
b03c4ed
Add touch event support for pan on mobile in Blazor DeepZoom page
mattleibow Mar 14, 2026
3ab3445
Add touch-action: none CSS for reliable mobile pan
mattleibow Mar 14, 2026
53ae1ea
Add debug tile border visualization to DeepZoom inspector
mattleibow Mar 14, 2026
7425b1c
Improve testgrid visuals and debug tile overlay
mattleibow Mar 14, 2026
06374b8
Add logarithmic zoom slider and NativeZoom property
mattleibow Mar 15, 2026
aeda3de
Expand zoom range to 0.01-100x and fix slider flicker
mattleibow Mar 15, 2026
a8e10a3
Fix zoom label min-width to 7.5ch for 100.00x range
mattleibow Mar 15, 2026
2d97188
Add diagonal gradient overlay to testgrid and consolidate into PR #379
mattleibow Mar 15, 2026
025f9e3
Make debug tile borders darker and boost gradient overlay visibility
mattleibow Mar 15, 2026
74db6e9
Add pluggable ISKDeepZoomTileCache, DelayTileCache, and record structs
mattleibow Mar 15, 2026
6785d0f
Fix cache interface: add sync Put(), fix ToString, fix inspector capa…
mattleibow Mar 15, 2026
64779af
Remove stats overlay, rename cache, add browser storage cache, async …
mattleibow Mar 15, 2026
51cfe8c
Fix cache rebuild losing image, fix unbounded memIndex, update README
mattleibow Mar 15, 2026
abe9d36
Comprehensive DeepZoom documentation overhaul
mattleibow Mar 15, 2026
9907eb0
Fix MAUI doc pan example and document missing public types
mattleibow Mar 15, 2026
afaf151
Extract renderer interface, move tile borders to sample, expose LOD b…
mattleibow Mar 15, 2026
3b485ef
Fix tests: remove ShowTileBorders references (moved to sample layer)
mattleibow Mar 15, 2026
124036b
Fix LOD blending: missing tiles must show blank when disabled
mattleibow Mar 15, 2026
a9684c3
Fix LOD blending visibility: stop clearing cache on delay enable
mattleibow Mar 15, 2026
dc43e6a
Revert debug UI additions; expand LOD blending docs
mattleibow Mar 15, 2026
dba88f7
Delete DeepZoom README.md
mattleibow Mar 16, 2026
fd02f75
Add SKDeepZoomRectI/F, rename TileScheduler→TileLayout, move GetTileD…
mattleibow Mar 16, 2026
a974895
Extract SkiaSharp.Extended.Abstractions: ISKDeepZoomTile, refactored …
mattleibow Mar 16, 2026
9c4964c
Remove all backwards-compat shims from DeepZoom
mattleibow Mar 17, 2026
c92af22
Fix Blazor cache classes and rename remaining TileScheduler test methods
mattleibow Mar 17, 2026
daac956
Move DeepZoom core to Abstractions; add ISKDeepZoomTileDecoder
mattleibow Mar 17, 2026
aa56e4e
Fix global.json: use latestFeature to stay on SDK 10.x
mattleibow Mar 17, 2026
1b6c316
Remove testgrid resources (extracted to PR #382)
mattleibow Mar 17, 2026
0c19784
Move Abstractions DeepZoom files into DeepZoom/ subfolder
mattleibow Mar 17, 2026
4c5f597
Apply file-scoped namespaces and primary constructors; collapsible in…
mattleibow Mar 17, 2026
84795b4
Move Polyfills.cs to Abstractions project root
mattleibow Mar 17, 2026
dcabadb
Merge main into feature/deep-zoom-static-preview
mattleibow Mar 17, 2026
4fdaa2e
Remove .DeepZoom from namespaces: SkiaSharp.Extended.DeepZoom → SkiaS…
mattleibow Mar 17, 2026
c582379
Extract DeepZoom CSS to DeepZoom.razor.css
mattleibow Mar 17, 2026
583e754
Extract DeepZoomInspector Blazor component
mattleibow Mar 17, 2026
01e3e20
Move Blazor demo classes to Components/DeepZoom/
mattleibow Mar 17, 2026
f5b23ed
Switch from SKBitmap to SKImage for GPU-compatible tile rendering
mattleibow Mar 17, 2026
afce838
Update DeepZoom docs: new namespace, SKImage, architecture
mattleibow Mar 17, 2026
fc65620
Fix doc accuracy: use ISKDeepZoomTile in interface signatures and exa…
mattleibow Mar 17, 2026
f0e61fe
Rename DeepZoom → ImagePyramid across all files, types, tests, and docs
mattleibow Mar 17, 2026
0368f5f
Add ISKImagePyramidSource interface and SKImagePyramidIiifSource (III…
mattleibow Mar 17, 2026
3e5cfff
Fix canvas sizing, nav icon, home page, and docs reorganization
mattleibow Mar 17, 2026
357aead
Fix CSS isolation, rename dz- → ip-, fix layout flickering
mattleibow Mar 17, 2026
bd3dea1
Fix review issues: DebugBorderRenderer dispose, BrowserStorageTileCac…
mattleibow Mar 17, 2026
f123694
Fix 4 critical Abstractions issues from deep review
mattleibow Mar 17, 2026
e363cdb
Fix: tiles render in top-left corner after cache settings change in i…
mattleibow Mar 17, 2026
a23d8c0
Fix tile vanishing at canvas edges + add debug zoom overlay
mattleibow Mar 17, 2026
521d125
Fix debug zoom centering: translate to center before scaling
mattleibow Mar 17, 2026
cb0b109
Fix: remove extra tile row/column at bottom/right canvas edges
mattleibow Mar 17, 2026
88f5b88
Fix docs accuracy, sample URLs, and add IIIF examples dropdown
mattleibow Mar 17, 2026
a91eb32
Add testgrid/conceptcar examples to dropdown, fix TileBorderRenderer …
mattleibow Mar 17, 2026
8fc17f0
docs: fix broken links and add curated sample URLs for IIIF and DZI
mattleibow Mar 17, 2026
7a09e61
Update examples with verified IIIF/DZI sources; replace blank Bodleia…
mattleibow Mar 18, 2026
480f9e7
Fix IIIF tile seam flickering with pixel-snapping in GetTileDestRect
mattleibow Mar 18, 2026
bcdaf0c
Reset testgrid to main branch version
mattleibow Mar 18, 2026
6eef3e9
Refactor: replace SKImagePyramidRectI/RectF with generic Rect<T> and …
mattleibow Mar 18, 2026
8dc4114
docs: update code examples to use Rect<T>/Point<T> generic types
mattleibow Mar 18, 2026
02565fe
fix: address 7 multi-model code review findings
mattleibow Mar 18, 2026
4865ab3
fix: address remaining code review findings (samples, docs, tests)
mattleibow Mar 18, 2026
0b97ecb
Merge Abstractions into Extended, replace ISKImagePyramidTile with SK…
mattleibow Mar 19, 2026
b5322c3
Fix MAUI sample and update all docs to remove deleted tile/decoder types
mattleibow Mar 19, 2026
3d7b2cc
feat: add SKImagePyramidTile opaque type with raw bytes, SourceId, fi…
mattleibow Mar 20, 2026
5047873
fix: address 3 review issues in SKImagePyramidTile implementation
mattleibow Mar 20, 2026
82da437
refactor: reorganize ImagePyramid into Caching/Fetching/Sources subfo…
mattleibow Mar 20, 2026
4d8dd54
fix: expiry reset on source switch, DefaultExpiry property, SourceId …
mattleibow Mar 20, 2026
8e1a66c
refactor: move disk cache into ISKImagePyramidTileProvider, simplify …
mattleibow Mar 21, 2026
7c926b8
fix: delete dead fetcher files, throw on cancellation, update docs
mattleibow Mar 21, 2026
1144034
refactor: split ISKImagePyramidTileCache into pure sync render buffer
mattleibow Mar 21, 2026
08e95c9
Update ImagePyramid test files for new architecture
mattleibow Mar 21, 2026
5851f0c
Rework ImagePyramid fetching/caching into composable layers
mattleibow Mar 21, 2026
e091cdf
fix: provider lifecycle leaks, cancellation, null RawData; add 33 tes…
mattleibow Mar 21, 2026
5cc34b2
Add ImagePyramidView components for Blazor and MAUI samples
mattleibow Mar 21, 2026
29af1e7
Fix three ImagePyramidView issues from code review
mattleibow Mar 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions SkiaSharp.Extended.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,118 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.UI.Maui.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharpDemo.Blazor", "samples\SkiaSharpDemo.Blazor\SkiaSharpDemo.Blazor.csproj", "{B7E4C45C-5CAB-444E-B2D3-294151544256}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SkiaSharp.Extended.ImagePyramid.Tests", "tests\SkiaSharp.Extended.ImagePyramid.Tests\SkiaSharp.Extended.ImagePyramid.Tests.csproj", "{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SkiaSharp.Extended.UI.Blazor", "source\SkiaSharp.Extended.UI.Blazor\SkiaSharp.Extended.UI.Blazor.csproj", "{AB88B47A-1946-40E9-8418-0FD42D031690}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x64.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x64.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Debug|x86.Build.0 = Debug|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|Any CPU.Build.0 = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x64.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x64.Build.0 = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x86.ActiveCfg = Release|Any CPU
{FDA62359-1C0D-4661-8ACF-023EF7DAF2A0}.Release|x86.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x64.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x64.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x86.ActiveCfg = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Debug|x86.Build.0 = Debug|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|Any CPU.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x64.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x64.Build.0 = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x86.ActiveCfg = Release|Any CPU
{B5A95CCE-FF80-4ACA-AA49-F150C23C65D5}.Release|x86.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x64.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Debug|x86.Build.0 = Debug|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|Any CPU.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x64.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x64.Build.0 = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x86.ActiveCfg = Release|Any CPU
{2C0DAB3F-1246-4AE7-BFA5-E7F5DDD7E1C4}.Release|x86.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x64.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x64.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x86.ActiveCfg = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Debug|x86.Build.0 = Debug|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|Any CPU.Deploy.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x64.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x64.Build.0 = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x86.ActiveCfg = Release|Any CPU
{2C67033A-2C49-4146-B942-9CDD2E0BA412}.Release|x86.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x64.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x64.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x86.ActiveCfg = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Debug|x86.Build.0 = Debug|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|Any CPU.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x64.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x64.Build.0 = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x86.ActiveCfg = Release|Any CPU
{4B4EC78C-33B5-456D-BD7D-4358D16272F4}.Release|x86.Build.0 = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|x64.ActiveCfg = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|x64.Build.0 = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|x86.ActiveCfg = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Debug|x86.Build.0 = Debug|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|Any CPU.Build.0 = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|x64.ActiveCfg = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|x64.Build.0 = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|x86.ActiveCfg = Release|Any CPU
{B7E4C45C-5CAB-444E-B2D3-294151544256}.Release|x86.Build.0 = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|x64.ActiveCfg = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|x64.Build.0 = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|x86.ActiveCfg = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Debug|x86.Build.0 = Debug|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|Any CPU.Build.0 = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|x64.ActiveCfg = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|x64.Build.0 = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|x86.ActiveCfg = Release|Any CPU
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D}.Release|x86.Build.0 = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|x64.ActiveCfg = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|x64.Build.0 = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|x86.ActiveCfg = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Debug|x86.Build.0 = Debug|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|Any CPU.Build.0 = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|x64.ActiveCfg = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|x64.Build.0 = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|x86.ActiveCfg = Release|Any CPU
{AB88B47A-1946-40E9-8418-0FD42D031690}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -64,6 +144,8 @@ Global
{2C67033A-2C49-4146-B942-9CDD2E0BA412} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{4B4EC78C-33B5-456D-BD7D-4358D16272F4} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
{B7E4C45C-5CAB-444E-B2D3-294151544256} = {51B0C2C7-732B-4A5C-A4F2-55655D147866}
{C8E5D3F1-2A47-4B89-AD16-7F3E2C1B9A5D} = {5555F827-12DF-4D15-BF07-3A720FC2EF3F}
{AB88B47A-1946-40E9-8418-0FD42D031690} = {5DEC7961-7CE3-44D7-A7FC-6185BA2D37FE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08D78153-5DD7-4C52-A348-46AA448B2CFC}
Expand Down
165 changes: 165 additions & 0 deletions docs/docs/image-pyramid/blazor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Image Pyramid for Blazor

Use `SKImagePyramidController` with a plain `SKCanvasView` in Blazor WebAssembly to render Image Pyramid images. There is no custom component — the page wires the services directly to the canvas, giving you full control over layout, interaction, and lifecycle.

## Quick Start

```razor
@page "/deepzoom"
@implements IAsyncDisposable
@inject HttpClient Http
@using SkiaSharp.Extended

<SKCanvasView @ref="_canvas"
OnPaintSurface="OnPaintSurface"
style="width: 100%; height: 600px; touch-action: none;" />

@code {
private SKCanvasView? _canvas;
private SKImagePyramidController? _controller;
private readonly SKImagePyramidRenderer _renderer = new();

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;

_controller = new SKImagePyramidController();
_controller.InvalidateRequired += OnInvalidateRequired;

var xml = await Http.GetStringAsync("deepzoom/image.dzi");
var baseUrl = new Uri(Http.BaseAddress!, "deepzoom/image_files/").ToString();
var source = SKImagePyramidDziSource.Parse(xml, baseUrl);

_controller.Load(source, new SKTieredTileProvider(new SKHttpTileFetcher()));
}

private void OnPaintSurface(SKPaintSurfaceEventArgs e)
{
if (_controller == null) return;
_controller.SetControlSize(e.Info.Width, e.Info.Height);
_controller.Update();
_renderer.Canvas = e.Surface.Canvas;
_controller.Render(_renderer);
}

private void OnInvalidateRequired(object? sender, EventArgs e)
=> InvokeAsync(() => _canvas?.Invalidate());

public async ValueTask DisposeAsync()
{
if (_controller != null)
{
_controller.InvalidateRequired -= OnInvalidateRequired;
_controller.Dispose();
}
}
}
```

## Serving Tile Assets

Place `.dzi` and tile folder under `wwwroot`. In the project file, mark them as content:

```xml
<ItemGroup>
<Content Include="wwwroot\deepzoom\image.dzi" />
<Content Include="wwwroot\deepzoom\image_files\**" />
</ItemGroup>
```

The `SKTieredTileProvider` with `SKHttpTileFetcher` fetches each tile via `HttpClient`; tile URLs are constructed automatically from the base URL you pass to `SKImagePyramidDziSource.Parse`.

## Pan and Zoom

Wire mouse and touch events to the controller's navigation methods:

```razor
<SKCanvasView @ref="_canvas"
OnPaintSurface="OnPaintSurface"
@onmousedown="OnMouseDown"
@onmousemove="OnMouseMove"
@onmouseup="OnMouseUp"
@onwheel="OnWheel"
style="width: 100%; height: 600px; touch-action: none;" />

@code {
private bool _dragging;
private double _lastX, _lastY;

private void OnMouseDown(MouseEventArgs e)
{
_dragging = true;
_lastX = e.ClientX;
_lastY = e.ClientY;
}

private void OnMouseMove(MouseEventArgs e)
{
if (!_dragging || _controller == null) return;
_controller.Pan(e.ClientX - _lastX, e.ClientY - _lastY);
_lastX = e.ClientX;
_lastY = e.ClientY;
_canvas?.Invalidate();
}

private void OnMouseUp(MouseEventArgs e) => _dragging = false;

private void OnWheel(WheelEventArgs e)
{
if (_controller == null) return;
double factor = e.DeltaY < 0 ? 1.15 : 1.0 / 1.15;
_controller.ZoomAboutScreenPoint(factor, e.OffsetX, e.OffsetY);
_canvas?.Invalidate();
}
}
```

## Loading a DZC Collection

```csharp
var xml = await Http.GetStringAsync("collection.dzc");
var collection = SKImagePyramidDziCollectionSource.Parse(xml);
collection.TilesBaseUri = Http.BaseAddress!.ToString();
_controller!.Load(collection, new SKTieredTileProvider(new SKHttpTileFetcher()));
```

## Custom Provider

Pass a custom `ISKImagePyramidTileProvider` to the controller's `Load()` method for advanced scenarios:

```csharp
// With disk cache (persists across Blazor restarts via OPFS or service worker)
controller.Load(source, new SKTieredTileProvider(
new SKHttpTileFetcher(),
new SKDiskTileCacheStore("/cache/tiles", expiry: TimeSpan.FromDays(7))));
```

See the [Tile Providers](fetching.md) docs for implementing browser storage tiers, delay wrappers, and other custom strategies.

## Canvas Resize

When the Blazor page resizes, `SetControlSize` automatically picks up the new dimensions on the next paint. If you want to trigger a reset to fit the image after a resize:

```csharp
// In a JS interop resize callback:
[JSInvokable]
public void OnCanvasResized()
{
_controller?.ResetView();
InvokeAsync(() => _canvas?.Invalidate());
}
```

## Rendering Behaviour

- **Fit and center**: On load the controller calls `ResetView()` — the full image is visible, centered horizontally and vertically, with aspect ratio preserved. No cropping or distortion.
- **LOD blending**: While high-resolution tiles are in-flight, lower-resolution parent tiles are upscaled and composited as placeholders.
- **Idle detection**: `controller.IsIdle` is `true` when no tiles are loading. You can pause periodic repaints when the view is idle.

## Related

- [Image Pyramid overview](index.md)
- [Controller & Viewport](controller.md)
- [Tile Fetching](fetching.md)
- [Caching](caching.md)
- [Image Pyramid for MAUI](maui.md)
Loading
Loading