/// <summary> /// Resizes the Mask Surface to the given size /// </summary> /// <param name="surfaceLock">The object to lock to prevent multiple threads /// from accessing the surface at the same time.</param> /// <param name="surface">CompositionDrawingSurface</param> /// <param name="size">New size of the Mask Surface</param> public void ResizeDrawingSurface(object surfaceLock, CompositionDrawingSurface surface, Size size) { // Cannot resize to Size.Empty. Will throw exception! if (size.IsEmpty) { return; } // Ensuring that the size contains positive values size.Width = Math.Max(0, size.Width); size.Height = Math.Max(0, size.Height); // // Since multiple threads could be trying to get access to the device/surface // at the same time, we need to do any device/surface work under a lock. // lock (surfaceLock) { CanvasComposition.Resize(surface, size); } }
public override async Task Draw(CompositionGraphicsDevice device, Object drawingLock, CompositionDrawingSurface surface, Size size) { var canvasDevice = CanvasComposition.GetCanvasDevice(device); using (var canvasBitmap = await CanvasBitmap.LoadAsync(canvasDevice, _uri)) { var bitmapSize = canvasBitmap.Size; // // Because the drawing is done asynchronously and multiple threads could // be trying to get access to the device/surface at the same time, we need // to do any device/surface work under a lock. // lock (drawingLock) { Size surfaceSize = size; if (surface.Size != size || surface.Size == new Size(0, 0)) { // Resize the surface to the size of the image CanvasComposition.Resize(surface, bitmapSize); surfaceSize = bitmapSize; } // Allow the app to process the bitmap if requested if (_handler != null) { _handler(surface, canvasBitmap, device); } else { // Draw the image to the surface using (var session = CanvasComposition.CreateDrawingSession(surface)) { session.Clear(Windows.UI.Color.FromArgb(0, 0, 0, 0)); session.DrawImage(canvasBitmap, new Rect(0, 0, surfaceSize.Width, surfaceSize.Height), new Rect(0, 0, bitmapSize.Width, bitmapSize.Height)); } } } } }
/// <summary> /// Renders the CanvasBitmap on the CompositionDrawingSurface based on the given options. /// </summary> /// <param name="surfaceLock">The object to lock to prevent multiple threads /// from accessing the surface at the same time.</param> /// <param name="surface">CompositionDrawingSurface on which the CanvasBitmap has to be rendered.</param> /// <param name="canvasBitmap">CanvasBitmap created by loading the image from the Uri</param> /// <param name="options">Describes the image's resize and alignment options in the allocated space.</param> private static void RenderBitmap(object surfaceLock, CompositionDrawingSurface surface, CanvasBitmap canvasBitmap, CompositionSurfaceImageOptions options) { var surfaceSize = surface.Size; // If the canvasBitmap is null, then just fill the surface with the SurfaceBackgroundColor if (canvasBitmap == null) { // No need to render if the width and/or height of the surface is zero if (surfaceSize.IsEmpty || surfaceSize.Width.IsZero() || surfaceSize.Height.IsZero()) { return; } // // Since multiple threads could be trying to get access to the device/surface // at the same time, we need to do any device/surface work under a lock. // lock (surfaceLock) { using (var session = CanvasComposition.CreateDrawingSession(surface)) { // Clear the surface with the SurfaceBackgroundColor session.Clear(options.SurfaceBackgroundColor); } // No need to proceed further return; } } // // Since multiple threads could be trying to get access to the device/surface // at the same time, we need to do any device/surface work under a lock. // lock (surfaceLock) { // Is AutoResize Enabled? if (options.AutoResize) { // If AutoResize is allowed and the canvasBitmap size and surface size are // not matching then resize the surface to match the canvasBitmap size. // // NOTE: HorizontalAlignment, Vertical Alignment and Stretch will be // handled by the CompositionSurfaceBrush created using this surface. // if (canvasBitmap.Size != surfaceSize) { // Resize the surface CanvasComposition.Resize(surface, canvasBitmap.Size); surfaceSize = canvasBitmap.Size; } // No need to render if the width and/or height of the surface is zero if (surfaceSize.IsEmpty || surface.Size.Width.IsZero() || surface.Size.Height.IsZero()) { return; } // Draw the image to the surface using (var session = CanvasComposition.CreateDrawingSession(surface)) { // Render the image session.DrawImage(canvasBitmap, // CanvasBitmap new Rect(0, 0, surfaceSize.Width, surfaceSize.Height), // Target Rectangle new Rect(0, 0, canvasBitmap.Size.Width, canvasBitmap.Size.Height), // Source Rectangle options.Opacity, // Opacity options.Interpolation); // Interpolation } } else { // No need to render if the width and/or height of the surface is zero if (surfaceSize.IsEmpty || surface.Size.Width.IsZero() || surface.Size.Height.IsZero()) { return; } var bitmapSize = canvasBitmap.Size; var sourceWidth = bitmapSize.Width; var sourceHeight = bitmapSize.Height; var ratio = sourceWidth / sourceHeight; var targetWidth = 0d; var targetHeight = 0d; var left = 0d; var top = 0d; // Stretch Mode switch (options.Stretch) { case Stretch.None: targetWidth = sourceWidth; targetHeight = sourceHeight; break; case Stretch.Fill: targetWidth = surfaceSize.Width; targetHeight = surfaceSize.Height; break; case Stretch.Uniform: // If width is greater than height if (ratio > 1.0) { targetHeight = Math.Min(surfaceSize.Width / ratio, surfaceSize.Height); targetWidth = targetHeight * ratio; } else { targetWidth = Math.Min(surfaceSize.Height * ratio, surfaceSize.Width); targetHeight = targetWidth / ratio; } break; case Stretch.UniformToFill: // If width is greater than height if (ratio > 1.0) { targetHeight = Math.Max(surfaceSize.Width / ratio, surfaceSize.Height); targetWidth = targetHeight * ratio; } else { targetWidth = Math.Max(surfaceSize.Height * ratio, surfaceSize.Width); targetHeight = targetWidth / ratio; } break; } // Horizontal Alignment switch (options.HorizontalAlignment) { case AlignmentX.Left: left = 0; break; case AlignmentX.Center: left = (surfaceSize.Width - targetWidth) / 2.0; break; case AlignmentX.Right: left = surfaceSize.Width - targetWidth; break; } // Vertical Alignment switch (options.VerticalAlignment) { case AlignmentY.Top: top = 0; break; case AlignmentY.Center: top = (surfaceSize.Height - targetHeight) / 2.0; break; case AlignmentY.Bottom: top = surfaceSize.Height - targetHeight; break; } // Draw the image to the surface using (var session = CanvasComposition.CreateDrawingSession(surface)) { // Clear the surface with the SurfaceBackgroundColor session.Clear(options.SurfaceBackgroundColor); // Render the image session.DrawImage(canvasBitmap, // CanvasBitmap new Rect(left, top, targetWidth, targetHeight), // Target Rectangle new Rect(0, 0, sourceWidth, sourceHeight), // Source Rectangle options.Opacity, // Opacity options.Interpolation); // Interpolation } } } }
private static async Task <CompositionSurfaceBrush> LoadWin2DSurfaceBrushFromImageAsync( [NotNull] Compositor compositor, [NotNull] CanvasControl canvas, [NotNull] Uri uri, int timeThreshold = 1000, bool reload = false) { TaskCompletionSource <CompositionSurfaceBrush> tcs = new TaskCompletionSource <CompositionSurfaceBrush>(); async Task <CompositionSurfaceBrush> LoadImageAsync(bool shouldThrow) { // Load the image - this will only succeed when there's an available Win2D device try { using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(canvas, uri)) { // Get the device and the target surface CompositionGraphicsDevice device = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvas.Device); CompositionDrawingSurface surface = device.CreateDrawingSurface(default(Size), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); // Calculate the surface size Size size = bitmap.Size; CanvasComposition.Resize(surface, size); // Draw the image on the surface and get the resulting brush using (CanvasDrawingSession session = CanvasComposition.CreateDrawingSession(surface)) { session.Clear(Color.FromArgb(0, 0, 0, 0)); session.DrawImage(bitmap, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, size.Width, size.Height)); CompositionSurfaceBrush brush = surface.Compositor.CreateSurfaceBrush(surface); return(brush); } } } catch when(!shouldThrow) { // Win2D error, just ignore and continue return(null); } } async void Canvas_CreateResources(CanvasControl sender, CanvasCreateResourcesEventArgs args) { // Cancel previous actions args.GetTrackedAction()?.Cancel(); // Load the image and notify the canvas Task <CompositionSurfaceBrush> task = LoadImageAsync(false); IAsyncAction action = task.AsAsyncAction(); try { args.TrackAsyncAction(action); CompositionSurfaceBrush brush = await task; action.Cancel(); tcs.TrySetResult(brush); } catch (COMException) { // Somehow another action was still being tracked tcs.TrySetResult(null); } } // Lock the semaphore and check the cache first await Win2DSemaphore.WaitAsync(); if (!reload && SurfacesCache.TryGetValue(uri.ToString(), out CompositionSurfaceBrush cached)) { Win2DSemaphore.Release(); return(cached); } // Load the image canvas.CreateResources += Canvas_CreateResources; try { // This will throw and the canvas will re-initialize the Win2D device if needed await LoadImageAsync(true); } catch (ArgumentException) { // Just ignore here } catch { // Win2D messed up big time tcs.TrySetResult(null); } await Task.WhenAny(tcs.Task, Task.Delay(timeThreshold).ContinueWith(t => tcs.TrySetResult(null))); canvas.CreateResources -= Canvas_CreateResources; CompositionSurfaceBrush instance = tcs.Task.Result; String key = uri.ToString(); if (instance != null && !SurfacesCache.ContainsKey(key)) { SurfacesCache.Add(key, instance); } Win2DSemaphore.Release(); return(instance); }