Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to render SKCanvas with DrawingContext? [Question] #2492

Closed
ChaseLewis opened this issue May 4, 2019 · 23 comments
Closed

How to render SKCanvas with DrawingContext? [Question] #2492

ChaseLewis opened this issue May 4, 2019 · 23 comments

Comments

@ChaseLewis
Copy link

ChaseLewis commented May 4, 2019

I'm interested in trying to make a drawing app with Avalonia. Pretty impressed with modest offerings so far. To do so I'd like to manage the layers and SKCanvas myself since functionality at that level seems overall easier to use for what i'm trying to do. I can manage the bitmap layers, bitmaps, etc myself. Redrawing

I found #2371 however, I don't 100% understand how to convert the SKCanvas or SKBitmap object into something Avalonia can consume for drawing. Anyone able to explain how one would consume operations from SkiaSharp and present them in a user control? I assume I have to draw the underlying SKBitmap in the UserControl.Render method, but not sure how to do so.

@kekekeks
Copy link
Member

kekekeks commented May 4, 2019

If you want to have "SKBitmap-backed" control, you can use WriteableBitmap class and convert locked framebuffer to SkBitmap which you can get SkCanvas for later.

#2371 allows you to add a node directly to Avalonia's scene graph. Scene graph nodes are rendered on a separate thread.
If the drawing platform is Skia, you can get the SkCanvas instance used to render the whole window surface (actually, it might be one of the layers). That SkCanvas might be backed with a GPU surface, so there is no direct access to SkBitmap there.

For a drawing app, WriteableBitmap is recommended.

@ChaseLewis
Copy link
Author

ChaseLewis commented May 4, 2019

So i don't mind using WriteableBitmap, but unsure how to write to it. Is there an abstracted api that can easily allow drawing operations on it similar to SkiaCanvas? Or am I just doing something wrong?

Below is my first attempt based on casting to SKBitmap, but I get the exception below.

System.InvalidCastException: 'Unable to cast object of type 'BitmapFramebuffer' to type 'SkiaSharp.SKBitmap'.'

My assumption is the backend is something other than Skia or it is on the GPU and won't cast neatly to a SKBitmap. Unsure how to proceed.

using System;
using System.IO;
using SkiaSharp;
using Avalonia.Skia;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Avalonia.Controls
{
    public class DrawingCanvas : UserControl
    {
        private SKPaint SKBrush;
        private WriteableBitmap RenderTarget;
        public List<SKImage> Layers = new List<SKImage>();

        private bool IsDrawing = false;

        public override void EndInit()
        {
     
            SKBrush = new SKPaint();
            SKBrush.Color = new SKColor(0, 0, 0);
            RenderTarget = new WriteableBitmap(new PixelSize((int)Width, (int)Height), new Vector(96,96), PixelFormat.Rgba8888);

            using (var lockedBitmap = RenderTarget.Lock())
            {
                SKBitmap bitmap = (SKBitmap)lockedBitmap;
                using (SKCanvas canvas = new SKCanvas(bitmap))
                {
                    canvas.Clear(new SKColor(255, 255, 255));
                }
            }

            PointerPressed += DrawingCanvas_PointerPressed;
            PointerMoved += DrawingCanvas_PointerMoved;
            PointerLeave += DrawingCanvas_PointerLeave;
            PointerReleased += DrawingCanvas_PointerReleased;
            base.EndInit();
        }

        private void DrawingCanvas_PointerReleased(object sender, Input.PointerReleasedEventArgs e)
        {
            IsDrawing = false;
            e.Handled = true;
        }

        private void DrawingCanvas_PointerLeave(object sender, Input.PointerEventArgs e)
        {
            IsDrawing = false;
            e.Handled = true;
        }

        private void DrawingCanvas_PointerMoved(object sender, Input.PointerEventArgs e)
        {
            if(IsDrawing)
            {
                DrawCurrentBrush(e);
            }
        }

        private void DrawCurrentBrush(Input.PointerEventArgs e)
        {

            using (var lockedBitmap = RenderTarget.Lock())
            {
                SKBitmap bitmap = (SKBitmap)lockedBitmap;
                using (SKCanvas canvas = new SKCanvas(bitmap))
                {

                    canvas.DrawCircle(e.GetPosition(this).ToSKPoint(), 5, SKBrush);
                }
            }

        }

        private void DrawingCanvas_PointerPressed(object sender, Input.PointerPressedEventArgs e)
        {
            IsDrawing = true;
            DrawCurrentBrush(e);
            e.Handled = true;
        }

        public async Task<bool> SaveImage(string path)
        {
            try
            {
                using(var lockedBitmap = RenderTarget.Lock())
                using (FileStream fileStream = File.OpenWrite(path))
                {
                    await SKImage.FromBitmap((SKBitmap)lockedBitmap)
                        .Encode(SKEncodedImageFormat.Png, 100)
                        .AsStream()
                        .CopyToAsync(fileStream);
                }
            }
            catch(Exception ex)
            {
                return false;
            }

            return true;
        }

        public override void Render(DrawingContext context)
        {
            //This should only redraw the 'selected' portion
            context.DrawImage(RenderTarget, 1.0,
                new Rect(0, 0, RenderTarget.PixelSize.Width, RenderTarget.PixelSize.Height),
                new Rect(0, 0, Width, Height)
                );
            //context.DrawImage(RenderTarget, 1.0, new Rect(0, 0, RenderTarget.Width, RenderTarget.Height), new Rect(0, 0,Width,Height));
        }
    }
}

Edit: Looking into the source it appears this is intentionally done like this. The BitmapFramebuffer class is a private class that wraps the SKBitmap when you Lock it. BitmapFramebuffer even if public abstracts away the SKBitmap class by making the SKBitmap member private also. Recompiling and making these members public could allow for interop but I don't think that is necessarily reliable if the backend shifts away from Skia. I'm not familiar enough with the project to know if that is a valid concern since it is in beta.

@kekekeks
Copy link
Member

kekekeks commented May 4, 2019

You can create a new skia surface from ILockedFramebuffer by passing the data pointer and other provided bitmap parameters to surface creation function:

var info =  new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height,
                framebuffer.Format.ToSkColorType(),SKAlphaType.Premul);
using(var surface =  SKSurface.Create(info, framebuffer.Address, framebuffer.RowBytes))
{
     var canvas = surface.Canvas;
}

@ChaseLewis
Copy link
Author

Cool, I have it working with the below code. 1 thing is I turned on FPS and drawing the line it drops down to 15-40 fps when drawing. This FPS drop results in a noticeable lag to the line when drawing. Is there a better way to do the drawing that comes to mind? It seems like that is pretty hefty performance penalty for such a simple operation. An HTML Canvas performs better in comparison.

namespace Avalonia.Controls
{
    public class DrawingCanvas : UserControl
    {
        private SKPaint SKBrush;
        private WriteableBitmap RenderTarget;
        private SKSurface CurrentSurface;

        private bool IsDrawing = false;
        private SKPoint? LastPoint = null;
        //Should do the drawing of the image in another thread potentially

        public override void EndInit()
        {
     
            SKBrush = new SKPaint();
            SKBrush.Color = new SKColor(0, 0, 0);
            SKBrush.Shader = SKShader.CreateColor(SKBrush.Color);
            RenderTarget = new WriteableBitmap(new PixelSize((int)Width, (int)Height), new Vector(96,96), PixelFormat.Rgba8888);
           
            using (var lockedBitmap = RenderTarget.Lock())
            {
                SKImageInfo info = new SKImageInfo(lockedBitmap.Size.Width, lockedBitmap.Size.Height, lockedBitmap.Format.ToSkColorType());

                CurrentSurface = SKSurface.Create(info, lockedBitmap.Address, lockedBitmap.RowBytes);
                CurrentSurface.Canvas.Clear(new SKColor(255, 255, 255));
            }

            PointerPressed += DrawingCanvas_PointerPressed;
            PointerMoved += DrawingCanvas_PointerMoved;
            PointerLeave += DrawingCanvas_PointerLeave;
            PointerReleased += DrawingCanvas_PointerReleased;
            base.EndInit();
        }

        private void DrawingCanvas_PointerReleased(object sender, Input.PointerReleasedEventArgs e)
        {
            IsDrawing = false;
            e.Handled = true;
        }

        private void DrawingCanvas_PointerLeave(object sender, Input.PointerEventArgs e)
        {
            IsDrawing = false;
            e.Handled = true;
        }

        private void DrawingCanvas_PointerMoved(object sender, Input.PointerEventArgs e)
        {
            if(IsDrawing)
            {
                DrawCurrentBrush(e);
                e.Handled = true;
            }
        }

        private SKPoint Interpolate(SKPoint p0, SKPoint p1, float t)
        {
            float omt = 1.0f - t;
            return new SKPoint(p0.X * omt + p1.X * t, p0.Y * omt + p1.Y*t);
        }


        private void DrawCurrentBrush(Input.PointerEventArgs e)
        {
            SKPoint currentPoint = e.GetPosition(this).ToSKPoint();
            if (LastPoint == null)
            {
                CurrentSurface.Canvas.DrawCircle(currentPoint, 5, SKBrush);
            }
            else
            {
                float length = (LastPoint.Value - currentPoint).Length;
                float stepLength = 2.5f/length;
                for(float t = 0;t < 1.0f;t += stepLength)
                {
                    CurrentSurface.Canvas.DrawCircle(Interpolate(LastPoint.Value, currentPoint, t), 5, SKBrush);
                }
                CurrentSurface.Canvas.DrawCircle(currentPoint, 5, SKBrush);
            }

            LastPoint = currentPoint;
            InvalidateVisual();
        }

        private void DrawingCanvas_PointerPressed(object sender, Input.PointerPressedEventArgs e)
        {
            IsDrawing = true;
            LastPoint = null;
            DrawCurrentBrush(e);
            e.Handled = true;
        }

        public async Task<bool> SaveImage(string path)
        {
            try
            {
                using (var lockedBitmap = RenderTarget.Lock())
                using (FileStream fileStream = File.OpenWrite(path))
                {
                    SKImageInfo info = new SKImageInfo(lockedBitmap.Size.Width, lockedBitmap.Size.Height, lockedBitmap.Format.ToSkColorType());

                    await SKImage.FromPixels(info,lockedBitmap.Address,lockedBitmap.RowBytes)
                        .Encode(SKEncodedImageFormat.Png, 100)
                        .AsStream()
                        .CopyToAsync(fileStream);
                }
            }
            catch(Exception ex)
            {
                return false;
            }

            return true;
        }

        public override void Render(DrawingContext context)
        {
            context.DrawImage(RenderTarget, 1.0,
                new Rect(0, 0, RenderTarget.PixelSize.Width, RenderTarget.PixelSize.Height),
                new Rect(0, 0, Width, Height)
                );
        }
    }
}

@Gillibald
Copy link
Contributor

In my opinion we should change SurfaceRenderTarget to expose the underlying SKCanvas. That should yield decent performance. Using WritableBitmap is always a performance hit.

@wieslawsoltes
Copy link
Collaborator

I have better performance when using SkiaSharp then when using DrawingContext.

@ChaseLewis This is my app, its a bit complex but its drawing app and is using SkiaSharp
https://github.com/wieslawsoltes/Draw2D

@ChaseLewis
Copy link
Author

Thanks @wieslawsoltes going to take a second to understand everything your doing here. Seems your using a custom renderer and then grabbing the SKCanvas directly from that renderer so you never have to write to WriteableBitmap.

I did get 47-50 FPS though by adding the use GPU lines to my Program.cs that I saw in your app. Still not perfect as I'd expect it to handle 59-60 FPS range pretty well with such a simple operation but that is fairly passable for the second.

        // Avalonia configuration, don't remove; also used by visual designer.
        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .With(new Win32PlatformOptions { AllowEglInitialization = true })
                .With(new X11PlatformOptions { UseGpu = true, UseEGL = true })
                .With(new AvaloniaNativePlatformOptions { UseGpu = true })
                .LogToDebug();

Even at 60 FPS drawing apps often have issues with keeping up completely for a smooth experience. Microsoft did a study a long time ago and found it took nearly 125 FPS before users couldn't tell any lag when drawing on an electronic surface. I'm just trying to make it a passable experience. DrawingCanvas type controls are pretty common in most frameworks and all the stuff seems to exist under the hood already if the SKCanvas of the render target was exposed.

@Gillibald
Copy link
Contributor

I prefer the RenderTargetBitmap approach because it doesn't effect the whole scene. If you expose the window's surface there is a lot that can go wrong.

@kekekeks
Copy link
Member

kekekeks commented May 6, 2019

I did get 47-50 FPS though by adding the use GPU lines to my Program.cs that I saw in your app.

You need to also install https://www.nuget.org/packages/Avalonia.Angle.Windows.Natives/ to actually enable GPU acceleration.

"Custom drawing operations" is probably the fastest thing you can use right now, but it ticks on the render thread.

It should be possible to obtain hw-accelerated SkCanvas from RenderTargetBitmap by casting the drawing context to ISkiaDrawingContextImpl.

@Gillibald

In my opinion we should change SurfaceRenderTarget to expose the underlying SKCanvas.

It's already possible to get SkCanvas from any skia-backed drawing context.

@Gillibald
Copy link
Contributor

Gillibald commented May 6, 2019

I know that you can get the SKCanvas from drawing context I just dont't like the fact that you can ruin the whole rendering of a window when you do something wrong. For example, you can draw outside of the actual control with that approach.

using(var bitmap = new RenderTargetBitmap())
{
    var renderTarget = bitmap.PlatformImpl as SurfaceRenderTarget;
    var canvas = renderTarget.Canvas;
}

@kekekeks
Copy link
Member

kekekeks commented May 6, 2019

You can already do this:

var bitmap = new RenderTargetBitmap()
using(var ctx = bitmap.CreateDrawingContext(null))
{
    var canvas = ((ISkiaDrawingContextImpl)renderTarget).SkCanvas;
}

@Gillibald
Copy link
Contributor

Yes true. But this is not limited to a DrawingContext that comes from RenderTargetBitmap and in my opinion, it should be that way. If you want to use the window's SKCanvas you should create your window with a special surface that exposes that canvas. That way it is always clear where the canvas comes from. No side effects etc.

@ChaseLewis
Copy link
Author

So the performance using the RenderTargetBitmap is FAAAR better. Doesn't drop even a single frame. One note though is you need to get 0.8.1 cli build to get access to the ISkiaDrawingContextImpl. It is not available in the currently stable 0.8.0 version. That took me a second to figure out.

@ChaseLewis ChaseLewis reopened this May 7, 2019
@ChaseLewis
Copy link
Author

Example code for anyone that it may help

using System;
using System.IO;
using SkiaSharp;
using Avalonia.Skia;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using System.Threading.Tasks;
using System.Collections.Generic;
using DrawingApp.DrawingTools;


namespace Avalonia.Controls
{
    public class Layer
    {
        public SKImage Image { get; set; }
        public SKSurface Surface { get; set; }
    }

    public class DrawingCanvas : UserControl
    {
        //This is where we keep the bitmaps that comprise individual layers
        //we use to composite what we present to the user
        private Layer UILayer;
        private int ActiveLayer;
        private Layer CachedActiveLayer;
        private List<Layer> ImageLayers;

        //Our render target we compile everything to and present to the user
        private RenderTargetBitmap RenderTarget;
        private ISkiaDrawingContextImpl SkiaContext;

        //Reference to the currently active drawing tool
        public IDrawingTool Tool { get; set; }
        //Should have a OnToolChange Event

        public override void EndInit()
        {
            SKPaint SKBrush = new SKPaint();
            SKBrush.IsAntialias = true;
            SKBrush.Color = new SKColor(0, 0, 0);
            SKBrush.Shader = SKShader.CreateColor(SKBrush.Color);
            RenderTarget = new RenderTargetBitmap(new PixelSize((int)Width, (int)Height), new Vector(96, 96));

            var context = RenderTarget.CreateDrawingContext(null);
            SkiaContext = (context as ISkiaDrawingContextImpl);
            SkiaContext.SkCanvas.Clear(new SKColor(255, 255, 255));

            Tool = new SquareBrush(5.0f, SKBrush);

            PointerPressed += DrawingCanvas_PointerPressed;
            PointerMoved += DrawingCanvas_PointerMoved;
            PointerLeave += DrawingCanvas_PointerLeave;
            PointerReleased += DrawingCanvas_PointerReleased;
            PointerEnter += DrawingCanvas_PointerEnter;
            base.EndInit();
        }

        

        private DrawingEvent PointerToDrawing(Input.PointerEventArgs e)
        {
            return new DrawingEvent()
            {
                 Handled = e.Handled,
                 InputModifiers = e.InputModifiers,
                 //This position should be capped at the bounds of the canvas
                 Position = e.GetPosition(this).ToSKPoint()
            };
        }

        private void DrawingCanvas_PointerEnter(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerEnter(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerReleased(object sender, Input.PointerReleasedEventArgs e)
        {
            if(Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerRelease(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerLeave(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerLeave(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerMoved(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerMove(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerPressed(object sender, Input.PointerPressedEventArgs e)
        {
            if (Tool != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                //Tool.OnPointerPress(CurrentSurface.Canvas, de);
                Tool.OnPointerPress(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        public Task<bool> SaveAsync(string path)
        {
            return Task.Run(() =>
            {
                try
                {
                    RenderTarget.Save(path);
                }
                catch(Exception)
                {
                    return false;
                }

                return true;
            });
        }

        public override void Render(DrawingContext context)
        {
            context.DrawImage(RenderTarget, 1.0,
                new Rect(0, 0, RenderTarget.PixelSize.Width, RenderTarget.PixelSize.Height),
                new Rect(0, 0, Width, Height)
                );
        }
    }
}

@yatli
Copy link
Contributor

yatli commented May 7, 2019

FYI in 0.8 you can also get SKCanvas with ((DrawingContextImpl)tgtBitmap).Canvas

@wieslawsoltes
Copy link
Collaborator

Example code for anyone that it may help

using System;
using System.IO;
using SkiaSharp;
using Avalonia.Skia;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using System.Threading.Tasks;
using System.Collections.Generic;
using DrawingApp.DrawingTools;


namespace Avalonia.Controls
{
    public class Layer
    {
        public SKImage Image { get; set; }
        public SKSurface Surface { get; set; }
    }

    public class DrawingCanvas : UserControl
    {
        //This is where we keep the bitmaps that comprise individual layers
        //we use to composite what we present to the user
        private Layer UILayer;
        private int ActiveLayer;
        private Layer CachedActiveLayer;
        private List<Layer> ImageLayers;

        //Our render target we compile everything to and present to the user
        private RenderTargetBitmap RenderTarget;
        private ISkiaDrawingContextImpl SkiaContext;

        //Reference to the currently active drawing tool
        public IDrawingTool Tool { get; set; }
        //Should have a OnToolChange Event

        public override void EndInit()
        {
            SKPaint SKBrush = new SKPaint();
            SKBrush.IsAntialias = true;
            SKBrush.Color = new SKColor(0, 0, 0);
            SKBrush.Shader = SKShader.CreateColor(SKBrush.Color);
            RenderTarget = new RenderTargetBitmap(new PixelSize((int)Width, (int)Height), new Vector(96, 96));

            var context = RenderTarget.CreateDrawingContext(null);
            SkiaContext = (context as ISkiaDrawingContextImpl);
            SkiaContext.SkCanvas.Clear(new SKColor(255, 255, 255));

            Tool = new SquareBrush(5.0f, SKBrush);

            PointerPressed += DrawingCanvas_PointerPressed;
            PointerMoved += DrawingCanvas_PointerMoved;
            PointerLeave += DrawingCanvas_PointerLeave;
            PointerReleased += DrawingCanvas_PointerReleased;
            PointerEnter += DrawingCanvas_PointerEnter;
            base.EndInit();
        }

        

        private DrawingEvent PointerToDrawing(Input.PointerEventArgs e)
        {
            return new DrawingEvent()
            {
                 Handled = e.Handled,
                 InputModifiers = e.InputModifiers,
                 //This position should be capped at the bounds of the canvas
                 Position = e.GetPosition(this).ToSKPoint()
            };
        }

        private void DrawingCanvas_PointerEnter(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerEnter(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerReleased(object sender, Input.PointerReleasedEventArgs e)
        {
            if(Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerRelease(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerLeave(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerLeave(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerMoved(object sender, Input.PointerEventArgs e)
        {
            if (Tool != null && SkiaContext != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                Tool.OnPointerMove(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        private void DrawingCanvas_PointerPressed(object sender, Input.PointerPressedEventArgs e)
        {
            if (Tool != null)
            {
                DrawingEvent de = PointerToDrawing(e);
                //Tool.OnPointerPress(CurrentSurface.Canvas, de);
                Tool.OnPointerPress(SkiaContext.SkCanvas, de);
                e.Handled = de.Handled;

                if (de.Handled)
                    InvalidateVisual();
            }
        }

        public Task<bool> SaveAsync(string path)
        {
            return Task.Run(() =>
            {
                try
                {
                    RenderTarget.Save(path);
                }
                catch(Exception)
                {
                    return false;
                }

                return true;
            });
        }

        public override void Render(DrawingContext context)
        {
            context.DrawImage(RenderTarget, 1.0,
                new Rect(0, 0, RenderTarget.PixelSize.Width, RenderTarget.PixelSize.Height),
                new Rect(0, 0, Width, Height)
                );
        }
    }
}

This is so much faster then using custom drawing with skia.

@wieslawsoltes
Copy link
Collaborator

        SKBrush.Color = new SKColor(0, 0, 0);
        SKBrush.Shader = SKShader.CreateColor(SKBrush.Color);

@ChaseLewis Why do you use SKShader here instead of SKColor?

@ChaseLewis
Copy link
Author

ChaseLewis commented May 18, 2019

@weislawsoltes
The color assignment probably isn't necessary with the shader assignment. Im still new to SkiaSharp so some of that code is just me being a newbie.

The thought of using shaders gives the possibility of having individual tools have custom shaders eventually. The shader interop has more potential for being flexible with the ITool paradigm I'm shooting for. Eventually I'd like effect layers and such. Imagine a tool when you draw that looks like it's unveiling a texture even though you don't have any masking, blur tool, etc. Kinda future proofing for some ideas I had more than anything.

@wieslawsoltes
Copy link
Collaborator

wieslawsoltes commented May 18, 2019

@ChaseLewis Thanks for the reply 😄

The shaders are are indeed nice https://docs.microsoft.com/en-us/dotnet/api/skiasharp.skshader?view=skiasharp-1.68.0

@Shadowblitz16
Copy link

I know that you can get the SKCanvas from drawing context I just dont't like the fact that you can ruin the whole rendering of a window when you do something wrong. For example, you can draw outside of the actual control with that approach.

using(var bitmap = new RenderTargetBitmap())
{
    var renderTarget = bitmap.PlatformImpl as SurfaceRenderTarget;
    var canvas = renderTarget.Canvas;
}

this doesn't work anymore

@maxkatz6
Copy link
Member

@Shadowblitz16 you need ICustomDrawOperation API

@Shadowblitz16
Copy link

@Shadowblitz16 you need ICustomDrawOperation API

Why is the imaging api so convoluted?
That doesn't tell me much.
Unless you mean casting the renderTarget.Canvas to a ICustomDrawOperation?

@Gillibald
Copy link
Contributor

Gillibald commented Jul 17, 2024

You need to call Custom on the DrawingContext with your ICustomDrawingOperation implementation. A custom drawing operation is required because actual drawing isn't happening immediately. Therefore you don't get access to the actual SKCanvas otherwise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants