4using System.Threading.Tasks;
7using SkiaSharp.Views.Gtk;
17 #region Fields and Properties
18 private readonly SemaphoreSlim _renderSemaphore =
new SemaphoreSlim(1, 1);
22 private bool _disposed;
25 private SKImage _currentRenderedImage;
26 private PointD _lastOrigin;
27 private double _lastResolution;
28 private ZCT _lastCoordinate;
29 private int _lastWidth;
30 private int _lastHeight;
31 private volatile int _renderStateVersion;
34 private OpenSlideGTK.OpenSlideBase _openSlideSource;
35 private SlideBase _slideSource;
36 private bool _isOpenSlide;
37 private bool _hasSource;
39 public int MaxCacheSizeMB {
get;
set; } = 512;
40 public int TileSize {
get;
set; } = 256;
41 public SKSamplingOptions Sampling {
get;
set; } = SKSamplingOptions.Default;
42 public bool EnablePrefetch {
get;
set; } =
true;
45 public bool IsRendering {
get;
private set; }
46 public bool HasValidImage => _currentRenderedImage !=
null;
60 private void InitializeDefaults()
62 _lastOrigin =
new PointD(0, 0);
63 _lastResolution = 1.0;
64 _lastCoordinate =
new ZCT(0, 0, 0);
71 #region Source Management
76 public void SetSource(OpenSlideGTK.OpenSlideBase openSlideSource)
78 if (openSlideSource ==
null)
79 throw new ArgumentNullException(nameof(openSlideSource));
83 _openSlideSource = openSlideSource;
87 InitializeStitchingPipeline();
95 if (slideSource ==
null)
96 throw new ArgumentNullException(nameof(slideSource));
100 _slideSource = slideSource;
101 _isOpenSlide =
false;
104 InitializeStitchingPipeline();
110 private void InitializeStitchingPipeline()
112 _stitchingPipeline?.Dispose();
120 _stitchingPipeline =
new SkiaStitchingPipeline(_slideSource, MaxCacheSizeMB);
124 _stitchingPipeline.TileSize = TileSize;
126 _stitchingPipeline.EnablePrefetch = EnablePrefetch;
132 private void ClearCurrentSource()
134 Interlocked.Increment(ref _renderStateVersion);
135 _currentRenderedImage?.Dispose();
136 _currentRenderedImage =
null;
138 _stitchingPipeline?.Dispose();
139 _stitchingPipeline =
null;
150 Interlocked.Increment(ref _renderStateVersion);
155 #region Async View Update
167 if (_disposed || !_hasSource || _stitchingPipeline ==
null)
172 if (!_renderSemaphore.Wait(0))
180 int renderVersion = _renderStateVersion;
187 _lastOrigin = origin;
188 _lastResolution = resolution;
189 _lastCoordinate = coordinate;
191 _lastHeight = height;
193 await RenderFullQualityAsync(origin, resolution, coordinate, width, height, renderVersion);
197 Console.WriteLine($
"Error during view update: {ex.Message}");
202 _renderSemaphore.Release();
207 Gtk.Application.Invoke((s, a) => App.viewer?.RequestDeferredRender());
214 private async Task RenderLowResPreviewAsync(
224 double previewResolution = resolution * 4.0;
225 int previewWidth = width / 2;
226 int previewHeight = height / 2;
249 Console.WriteLine($
"Preview render error: {ex.Message}");
256 private async Task RenderFullQualityAsync(
262 int renderVersion = 0)
275 if (renderVersion != _renderStateVersion)
277 stitchedImage?.Dispose();
281 if (stitchedImage !=
null)
283 _currentRenderedImage?.Dispose();
284 _currentRenderedImage = stitchedImage;
292 Console.WriteLine($
"Full quality render error: {ex.Message}");
298 #region Synchronous Rendering Methods
306 ZCT coordinate,
double resolution,
int pxwidth,
int pxheight)
308 if (!_hasSource || _stitchingPipeline ==
null)
311 return _stitchingPipeline.
StitchRegion(region, level, coordinate, resolution, pxwidth, pxheight);
319 if (!_hasSource || _stitchingPipeline ==
null)
333 #region Image Access for Rendering
342 return _currentRenderedImage;
349 public void DrawToCanvas(SKCanvas canvas,
int canvasWidth,
int canvasHeight)
359 using (var paint =
new SKPaint
362 BlendMode = SKBlendMode.SrcOver
366 var destRect =
new SKRect(0, 0, canvasWidth, canvasHeight);
367 canvas.DrawImage(image, destRect, paint);
372 Console.WriteLine($
"Error drawing to canvas: {ex.Message}");
378 #region Update Management
383 private bool NeedsUpdate(
390 const double EPSILON = 0.0001;
392 return Math.Abs(origin.X - _lastOrigin.X) > EPSILON ||
393 Math.Abs(origin.Y - _lastOrigin.Y) > EPSILON ||
394 Math.Abs(resolution - _lastResolution) > EPSILON ||
395 width != _lastWidth ||
396 height != _lastHeight ||
397 coordinate.Z != _lastCoordinate.Z ||
398 coordinate.C != _lastCoordinate.C ||
399 coordinate.T != _lastCoordinate.T;
405 private bool ShouldRenderPreview(
double resolution)
408 return resolution < 1.0;
414 private void RequestRedraw()
416 Gtk.Application.Invoke((sender, args) =>
418 App.viewer.RequestDeferredRender();
424 #region Cache Management
442 IsEnabled = _stitchingPipeline !=
null,
443 MaxSizeMB = MaxCacheSizeMB,
450 public bool IsEnabled {
get;
set; }
451 public int MaxSizeMB {
get;
set; }
452 public int TileSize {
get;
set; }
457 #region Configuration Updates
463 int? maxCacheSizeMB =
null,
464 int? tileSize =
null,
465 SKSamplingOptions? sampling =
null,
466 bool? enablePrefetch =
null,
467 bool? enableProgressiveRendering =
null)
469 bool needsReinit =
false;
471 if (maxCacheSizeMB.HasValue && maxCacheSizeMB.Value != MaxCacheSizeMB)
473 MaxCacheSizeMB = maxCacheSizeMB.Value;
477 if (tileSize.HasValue && tileSize.Value != TileSize)
479 TileSize = tileSize.Value;
484 if (needsReinit && _hasSource)
486 InitializeStitchingPipeline();
494 public void Dispose()
500 _currentRenderedImage?.Dispose();
501 _stitchingPipeline?.Dispose();
502 _currentRenderedImage =
null;
503 _stitchingPipeline =
null;
Manages slide rendering by coordinating between the SkiaStitchingPipeline and SlideGLArea Handles asy...
SKSlideRenderer(SKDrawingArea sk)
Initialize renderer for a specific GLArea widget.
async Task UpdateViewAsync(PointD origin, double resolution, ZCT coordinate, int width=600, int height=400)
Update the rendered view asynchronously - main entry point for rendering.
CacheStatistics GetCacheStatistics()
Get cache statistics for monitoring.
void InvalidateRenderState()
Marks any in-flight render as stale so the next viewport update can replace it without the old result...
void SetSource(OpenSlideGTK.OpenSlideBase openSlideSource)
Set OpenSlide source and initialize pipeline.
void ClearCache()
Clear the tile cache.
SKImage RenderRegion(RectangleD region, int level, ZCT coordinate, double resolution, int pxwidth, int pxheight)
Render a specific region synchronously (for exports, small areas)
void DrawToCanvas(SKCanvas canvas, int canvasWidth, int canvasHeight)
Draw the current image to a canvas with proper scaling Called during the Skia render callback from Sl...
void SetSource(SlideBase slideSource)
Set BioLib slide source and initialize pipeline.
void UpdateConfiguration(int? maxCacheSizeMB=null, int? tileSize=null, SKSamplingOptions? sampling=null, bool? enablePrefetch=null, bool? enableProgressiveRendering=null)
Update renderer configuration and reinitialize if needed.
SKImage RenderCurrentViewSync()
Render current viewport synchronously (blocking)
bool LastRenderSkipped
True if the last UpdateViewAsync call was skipped because a render was already in progress.
SKImage GetCurrentImage()
Get the current image for rendering - called by GLArea during draw Returns the best available image (...
SkiaSharp-based tile stitching pipeline for compositing large pyramidal images.
void ClearCache()
Clear entire cache.
SKImage StitchRegion(RectangleD region, int level, ZCT coordinate, double resolution, int pxwidth, int pxheight)
Stitch a specific region synchronously (for smaller areas)
async Task< SKImage > StitchViewportAsync(PointD origin, double resolution, ZCT coordinate, int viewportWidth=600, int viewportHeight=400)
Stitch tiles for a given viewport asynchronously.