| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407 |
- using System;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Linq;
- using System.Threading;
- using Avalonia.Media;
- using Avalonia.Platform;
- using Avalonia.Rendering;
- using Avalonia.Rendering.SceneGraph;
- using Avalonia.Rendering.Utilities;
- using Avalonia.Utilities;
- using Avalonia.Media.Imaging;
- using Avalonia.Skia.Helpers;
- using SkiaSharp;
- using ISceneBrush = Avalonia.Media.ISceneBrush;
- namespace Avalonia.Skia
- {
- /// <summary>
- /// Skia based drawing context.
- /// </summary>
- internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
- {
- private IDisposable?[]? _disposables;
- private readonly Vector _dpi;
- private readonly Stack<PaintWrapper> _maskStack = new();
- private readonly Stack<double> _opacityStack = new();
- private readonly Stack<BitmapBlendingMode> _blendingModeStack = new();
- private readonly Matrix? _postTransform;
- private double _currentOpacity = 1.0f;
- private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
- private readonly bool _canTextUseLcdRendering;
- private Matrix _currentTransform;
- private bool _disposed;
- private GRContext? _grContext;
- public GRContext? GrContext => _grContext;
- private readonly ISkiaGpu? _gpu;
- private readonly SKPaint _strokePaint = SKPaintCache.Shared.Get();
- private readonly SKPaint _fillPaint = SKPaintCache.Shared.Get();
- private readonly SKPaint _boxShadowPaint = SKPaintCache.Shared.Get();
- private static SKShader? s_acrylicNoiseShader;
- private readonly ISkiaGpuRenderSession? _session;
- private bool _leased;
- private bool _useOpacitySaveLayer;
- /// <summary>
- /// Context create info.
- /// </summary>
- public struct CreateInfo
- {
- /// <summary>
- /// Canvas to draw to.
- /// </summary>
- public SKCanvas? Canvas;
- /// <summary>
- /// Surface to draw to.
- /// </summary>
- public SKSurface? Surface;
- /// <summary>
- /// Dpi of drawings.
- /// </summary>
- public Vector Dpi;
-
- /// <summary>
- /// Render text without Lcd rendering.
- /// </summary>
- public bool DisableTextLcdRendering;
- /// <summary>
- /// GPU-accelerated context (optional)
- /// </summary>
- public GRContext? GrContext;
- /// <summary>
- /// Skia GPU provider context (optional)
- /// </summary>
- public ISkiaGpu? Gpu;
- public ISkiaGpuRenderSession? CurrentSession;
- }
- private class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature
- {
- private readonly DrawingContextImpl _context;
- public SkiaLeaseFeature(DrawingContextImpl context)
- {
- _context = context;
- }
- public ISkiaSharpApiLease Lease()
- {
- _context.CheckLease();
- return new ApiLease(_context);
- }
- private class ApiLease : ISkiaSharpApiLease
- {
- private readonly DrawingContextImpl _context;
- private readonly SKMatrix _revertTransform;
- private bool _isDisposed;
- public ApiLease(DrawingContextImpl context)
- {
- _revertTransform = context.Canvas.TotalMatrix;
- _context = context;
- _context._leased = true;
- }
- public SKCanvas SkCanvas => _context.Canvas;
- public GRContext? GrContext => _context.GrContext;
- public SKSurface? SkSurface => _context.Surface;
- public double CurrentOpacity => _context._currentOpacity;
-
- public void Dispose()
- {
- if (!_isDisposed)
- {
- _context.Canvas.SetMatrix(_revertTransform);
- _context._leased = false;
- _isDisposed = true;
- }
- }
- }
- }
-
- /// <summary>
- /// Create new drawing context.
- /// </summary>
- /// <param name="createInfo">Create info.</param>
- /// <param name="disposables">Array of elements to dispose after drawing has finished.</param>
- public DrawingContextImpl(CreateInfo createInfo, params IDisposable?[]? disposables)
- {
- Canvas = createInfo.Canvas ?? createInfo.Surface?.Canvas
- ?? throw new ArgumentException("Invalid create info - no Canvas provided", nameof(createInfo));
- _dpi = createInfo.Dpi;
- _disposables = disposables;
- _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering;
- _grContext = createInfo.GrContext;
- _gpu = createInfo.Gpu;
- if (_grContext != null)
- Monitor.Enter(_grContext);
- Surface = createInfo.Surface;
- _session = createInfo.CurrentSession;
- if (!_dpi.NearlyEquals(SkiaPlatform.DefaultDpi))
- {
- _postTransform =
- Matrix.CreateScale(_dpi.X / SkiaPlatform.DefaultDpi.X, _dpi.Y / SkiaPlatform.DefaultDpi.Y);
- }
- Transform = Matrix.Identity;
- var options = AvaloniaLocator.Current.GetService<SkiaOptions>();
- if(options != null)
- {
- _useOpacitySaveLayer = options.UseOpacitySaveLayer;
- }
- }
-
- /// <summary>
- /// Skia canvas.
- /// </summary>
- public SKCanvas Canvas { get; }
- public SKSurface? Surface { get; }
- private void CheckLease()
- {
- if (_leased)
- throw new InvalidOperationException("The underlying graphics API is currently leased");
- }
-
- /// <inheritdoc />
- public void Clear(Color color)
- {
- CheckLease();
- Canvas.Clear(color.ToSKColor());
- }
- /// <inheritdoc />
- public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
- {
- CheckLease();
- var drawableImage = (IDrawableBitmapImpl)source.Item;
- var s = sourceRect.ToSKRect();
- var d = destRect.ToSKRect();
- var paint = SKPaintCache.Shared.Get();
- paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity)));
- paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality();
- paint.BlendMode = _currentBlendingMode.ToSKBlendMode();
- drawableImage.Draw(this, s, d, paint);
- SKPaintCache.Shared.ReturnReset(paint);
- }
- /// <inheritdoc />
- public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
- {
- CheckLease();
- PushOpacityMask(opacityMask, opacityMaskRect);
- DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
- PopOpacityMask();
- }
- /// <inheritdoc />
- public void DrawLine(IPen? pen, Point p1, Point p2)
- {
- CheckLease();
- if (pen is not null
- && TryCreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))) is { } stroke)
- {
- using (stroke)
- {
- Canvas.DrawLine((float)p1.X, (float)p1.Y, (float)p2.X, (float)p2.Y, stroke.Paint);
- }
- }
- }
- /// <inheritdoc />
- public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
- {
- CheckLease();
- var impl = (GeometryImpl) geometry;
- var size = geometry.Bounds.Size;
- if (brush is not null && impl.FillPath != null)
- {
- using (var fill = CreatePaint(_fillPaint, brush, size))
- {
- Canvas.DrawPath(impl.FillPath, fill.Paint);
- }
- }
- if (pen is not null
- && impl.StrokePath != null
- && TryCreatePaint(_strokePaint, pen, size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
- {
- using (stroke)
- {
- Canvas.DrawPath(impl.StrokePath, stroke.Paint);
- }
- }
- }
- private struct BoxShadowFilter : IDisposable
- {
- public readonly SKPaint Paint;
- private readonly SKImageFilter? _filter;
- public readonly SKClipOperation ClipOperation;
- private BoxShadowFilter(SKPaint paint, SKImageFilter? filter, SKClipOperation clipOperation)
- {
- Paint = paint;
- _filter = filter;
- ClipOperation = clipOperation;
- }
- private static float SkBlurRadiusToSigma(double radius) {
- if (radius <= 0)
- return 0.0f;
- return 0.288675f * (float)radius + 0.5f;
- }
- public static BoxShadowFilter Create(SKPaint paint, BoxShadow shadow, double opacity)
- {
- var ac = shadow.Color;
- var filter = SKImageFilter.CreateBlur(SkBlurRadiusToSigma(shadow.Blur), SkBlurRadiusToSigma(shadow.Blur));
- var color = new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity));
- paint.Reset();
- paint.IsAntialias = true;
- paint.Color = color;
- paint.ImageFilter = filter;
- var clipOperation = shadow.IsInset ? SKClipOperation.Intersect : SKClipOperation.Difference;
- return new BoxShadowFilter(paint, filter, clipOperation);
- }
- public void Dispose()
- {
- Paint?.Reset();
- _filter?.Dispose();
- }
- }
- private static SKRect AreaCastingShadowInHole(
- SKRect hole_rect,
- float shadow_blur,
- float shadow_spread,
- float offsetX, float offsetY)
- {
- // Adapted from Chromium
- var bounds = hole_rect;
- bounds.Inflate(shadow_blur, shadow_blur);
- if (shadow_spread < 0)
- bounds.Inflate(-shadow_spread, -shadow_spread);
- var offset_bounds = bounds;
- offset_bounds.Offset(-offsetX, -offsetY);
- bounds.Union(offset_bounds);
- return bounds;
- }
- /// <inheritdoc />
- public void DrawRectangle(IExperimentalAcrylicMaterial? material, RoundedRect rect)
- {
- if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
- return;
- CheckLease();
-
- var rc = rect.Rect.ToSKRect();
- SKRoundRect? skRoundRect = null;
- if (rect.IsRounded)
- {
- skRoundRect = SKRoundRectCache.Shared.Get();
- skRoundRect.SetRectRadii(rc,
- new[]
- {
- rect.RadiiTopLeft.ToSKPoint(),
- rect.RadiiTopRight.ToSKPoint(),
- rect.RadiiBottomRight.ToSKPoint(),
- rect.RadiiBottomLeft.ToSKPoint(),
- });
- }
- if (material != null)
- {
- using (var paint = CreateAcrylicPaint(_fillPaint, material))
- {
- if (skRoundRect is not null)
- {
- Canvas.DrawRoundRect(skRoundRect, paint.Paint);
- SKRoundRectCache.Shared.Return(skRoundRect);
- }
- else
- {
- Canvas.DrawRect(rc, paint.Paint);
- }
- }
- }
- }
- /// <inheritdoc />
- public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
- {
- if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0)
- return;
- CheckLease();
- // Arbitrary chosen values
- // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect
- if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192)
- boxShadows = default;
- var rc = rect.Rect.ToSKRect();
- var isRounded = rect.IsRounded;
- var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows);
- SKRoundRect? skRoundRect = null;
- if (needRoundRect)
- {
- skRoundRect = SKRoundRectCache.Shared.GetAndSetRadii(rc, rect);
- }
- foreach (var boxShadow in boxShadows)
- {
- if (!boxShadow.IsDefault && !boxShadow.IsInset)
- {
- using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
- {
- var spread = (float)boxShadow.Spread;
- if (boxShadow.IsInset)
- spread = -spread;
- Canvas.Save();
- if (isRounded)
- {
- var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii);
- if (spread != 0)
- shadowRect.Inflate(spread, spread);
- Canvas.ClipRoundRect(skRoundRect,
- shadow.ClipOperation, true);
-
- var oldTransform = Transform;
- Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
- Canvas.DrawRoundRect(shadowRect, shadow.Paint);
- Transform = oldTransform;
- SKRoundRectCache.Shared.Return(shadowRect);
- }
- else
- {
- var shadowRect = rc;
- if (spread != 0)
- shadowRect.Inflate(spread, spread);
- Canvas.ClipRect(rc, shadow.ClipOperation);
- var oldTransform = Transform;
- Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
- Canvas.DrawRect(shadowRect, shadow.Paint);
- Transform = oldTransform;
- }
- Canvas.Restore();
- }
- }
- }
- if (brush != null)
- {
- using (var fill = CreatePaint(_fillPaint, brush, rect.Rect.Size))
- {
- if (isRounded)
- {
- Canvas.DrawRoundRect(skRoundRect, fill.Paint);
- }
- else
- {
- Canvas.DrawRect(rc, fill.Paint);
- }
- }
- }
- foreach (var boxShadow in boxShadows)
- {
- if (!boxShadow.IsDefault && boxShadow.IsInset)
- {
- using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _useOpacitySaveLayer ? 1 : _currentOpacity))
- {
- var spread = (float)boxShadow.Spread;
- var offsetX = (float)boxShadow.OffsetX;
- var offsetY = (float)boxShadow.OffsetY;
- var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY);
- Canvas.Save();
- var shadowRect = SKRoundRectCache.Shared.GetAndSetRadii(skRoundRect!.Rect, skRoundRect.Radii);
- if (spread != 0)
- shadowRect.Deflate(spread, spread);
- Canvas.ClipRoundRect(skRoundRect,
- shadow.ClipOperation, true);
-
- var oldTransform = Transform;
- Transform = oldTransform * Matrix.CreateTranslation(boxShadow.OffsetX, boxShadow.OffsetY);
- using (var outerRRect = new SKRoundRect(outerRect))
- Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint);
- Transform = oldTransform;
- Canvas.Restore();
- SKRoundRectCache.Shared.Return(shadowRect);
- }
- }
- }
- if (pen is not null
- && TryCreatePaint(_strokePaint, pen, rect.Rect.Size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
- {
- using (stroke)
- {
- if (isRounded)
- {
- Canvas.DrawRoundRect(skRoundRect, stroke.Paint);
- }
- else
- {
- Canvas.DrawRect(rc, stroke.Paint);
- }
- }
- }
- if (skRoundRect is not null)
- SKRoundRectCache.Shared.Return(skRoundRect);
- }
- /// <inheritdoc />
- public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
- {
- if (rect.Height <= 0 || rect.Width <= 0)
- return;
- CheckLease();
-
- var rc = rect.ToSKRect();
- if (brush != null)
- {
- using (var fill = CreatePaint(_fillPaint, brush, rect.Size))
- {
- Canvas.DrawOval(rc, fill.Paint);
- }
- }
- if (pen is not null
- && TryCreatePaint(_strokePaint, pen, rect.Size.Inflate(new Thickness(pen.Thickness / 2))) is { } stroke)
- {
- using (stroke)
- {
- Canvas.DrawOval(rc, stroke.Paint);
- }
- }
- }
-
- /// <inheritdoc />
- public void DrawGlyphRun(IBrush? foreground, IRef<IGlyphRunImpl> glyphRun)
- {
- CheckLease();
- if (foreground is null)
- {
- return;
- }
- using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size))
- {
- var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
- Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.Item.BaselineOrigin.X,
- (float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint);
- }
- }
- /// <inheritdoc />
- public IDrawingContextLayerImpl CreateLayer(Size size)
- {
- CheckLease();
- return CreateRenderTarget(size, true);
- }
- /// <inheritdoc />
- public void PushClip(Rect clip)
- {
- CheckLease();
- Canvas.Save();
- Canvas.ClipRect(clip.ToSKRect());
- }
- public void PushClip(RoundedRect clip)
- {
- CheckLease();
- Canvas.Save();
- // Get the rounded rectangle
- var rc = clip.Rect.ToSKRect();
- // Get a round rect from the cache.
- var roundRect = SKRoundRectCache.Shared.Get();
- roundRect.SetRectRadii(rc,
- new[]
- {
- clip.RadiiTopLeft.ToSKPoint(), clip.RadiiTopRight.ToSKPoint(),
- clip.RadiiBottomRight.ToSKPoint(), clip.RadiiBottomLeft.ToSKPoint(),
- });
- Canvas.ClipRoundRect(roundRect, antialias:true);
- // Should not need to reset as SetRectRadii overrides the values.
- SKRoundRectCache.Shared.Return(roundRect);
- }
- /// <inheritdoc />
- public void PopClip()
- {
- CheckLease();
- Canvas.Restore();
- }
- /// <inheritdoc />
- public void PushOpacity(double opacity, Rect bounds)
- {
- CheckLease();
- if(_useOpacitySaveLayer)
- {
- var rect = bounds.ToSKRect();
- Canvas.SaveLayer(rect, new SKPaint { ColorF = new SKColorF(0, 0, 0, (float)opacity)});
- }
- else
- {
- _opacityStack.Push(_currentOpacity);
- _currentOpacity *= opacity;
- }
- }
- /// <inheritdoc />
- public void PopOpacity()
- {
- CheckLease();
- if(_useOpacitySaveLayer)
- {
- Canvas.Restore();
- }
- else
- {
- _currentOpacity = _opacityStack.Pop();
- }
- }
- /// <inheritdoc />
- public virtual void Dispose()
- {
- if(_disposed)
- return;
- CheckLease();
- try
- {
- // Return leased paints.
- SKPaintCache.Shared.ReturnReset(_strokePaint);
- SKPaintCache.Shared.ReturnReset(_fillPaint);
- SKPaintCache.Shared.ReturnReset(_boxShadowPaint);
- if (_grContext != null)
- {
- Monitor.Exit(_grContext);
- _grContext = null;
- }
- if (_disposables != null)
- {
- foreach (var disposable in _disposables)
- disposable?.Dispose();
- _disposables = null;
- }
- }
- finally
- {
- _disposed = true;
- }
- }
- /// <inheritdoc />
- public void PushGeometryClip(IGeometryImpl clip)
- {
- CheckLease();
- Canvas.Save();
- Canvas.ClipPath(((GeometryImpl)clip).FillPath, SKClipOperation.Intersect, true);
- }
- /// <inheritdoc />
- public void PopGeometryClip()
- {
- CheckLease();
- Canvas.Restore();
- }
- /// <inheritdoc />
- public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
- {
- CheckLease();
- _blendingModeStack.Push(_currentBlendingMode);
- _currentBlendingMode = blendingMode;
- }
- /// <inheritdoc />
- public void PopBitmapBlendMode()
- {
- CheckLease();
- _currentBlendingMode = _blendingModeStack.Pop();
- }
- public void Custom(ICustomDrawOperation custom)
- {
- CheckLease();
- custom.Render(this);
- }
- /// <inheritdoc />
- public void PushOpacityMask(IBrush mask, Rect bounds)
- {
- CheckLease();
- var paint = SKPaintCache.Shared.Get();
- Canvas.SaveLayer(bounds.ToSKRect(), paint);
- _maskStack.Push(CreatePaint(paint, mask, bounds.Size));
- }
- /// <inheritdoc />
- public void PopOpacityMask()
- {
- CheckLease();
- var paint = SKPaintCache.Shared.Get();
- paint.BlendMode = SKBlendMode.DstIn;
-
- Canvas.SaveLayer(paint);
- SKPaintCache.Shared.ReturnReset(paint);
- PaintWrapper paintWrapper;
- using (paintWrapper = _maskStack.Pop())
- {
- Canvas.DrawPaint(paintWrapper.Paint);
- }
- // Return the paint wrapper's paint less the reset since the paint is already reset in the Dispose method above.
- SKPaintCache.Shared.Return(paintWrapper.Paint);
- Canvas.Restore();
- Canvas.Restore();
- }
- /// <inheritdoc />
- public Matrix Transform
- {
- get { return _currentTransform; }
- set
- {
- CheckLease();
- if (_currentTransform == value)
- return;
- _currentTransform = value;
- var transform = value;
- if (_postTransform.HasValue)
- {
- transform *= _postTransform.Value;
- }
- Canvas.SetMatrix(transform.ToSKMatrix());
- }
- }
- public object? GetFeature(Type t)
- {
- if (t == typeof(ISkiaSharpApiLeaseFeature))
- return new SkiaLeaseFeature(this);
- return null;
- }
- /// <summary>
- /// Configure paint wrapper for using gradient brush.
- /// </summary>
- /// <param name="paintWrapper">Paint wrapper.</param>
- /// <param name="targetSize">Target size.</param>
- /// <param name="gradientBrush">Gradient brush.</param>
- private static void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
- {
- var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
- var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
- var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
- switch (gradientBrush)
- {
- case ILinearGradientBrush linearGradient:
- {
- var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
- var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
- // would be nice to cache these shaders possibly?
- if (linearGradient.Transform is null)
- {
- using (var shader =
- SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- else
- {
- var transformOrigin = linearGradient.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(transformOrigin);
- var transform = (-offset) * linearGradient.Transform.Value * (offset);
- using (var shader =
- SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- break;
- }
- case IRadialGradientBrush radialGradient:
- {
- var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
- var radius = (float)(radialGradient.Radius * targetSize.Width);
- var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
- if (origin.Equals(center))
- {
- // when the origin is the same as the center the Skia RadialGradient acts the same as D2D
- if (radialGradient.Transform is null)
- {
- using (var shader =
- SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- else
- {
- var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(transformOrigin);
- var transform = (-offset) * radialGradient.Transform.Value * (offset);
-
- using (var shader =
- SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, transform.ToSKMatrix()))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- }
- else
- {
- // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D
- // reverse the order of the stops to match D2D
- var reversedColors = new SKColor[stopColors.Length];
- Array.Copy(stopColors, reversedColors, stopColors.Length);
- Array.Reverse(reversedColors);
- // and then reverse the reference point of the stops
- var reversedStops = new float[stopOffsets.Length];
- for (var i = 0; i < stopOffsets.Length; i++)
- {
- reversedStops[i] = stopOffsets[i];
- if (reversedStops[i] > 0 && reversedStops[i] < 1)
- {
- reversedStops[i] = Math.Abs(1 - stopOffsets[i]);
- }
- }
-
- // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
- if (radialGradient.Transform is null)
- {
- using (var shader = SKShader.CreateCompose(
- SKShader.CreateColor(reversedColors[0]),
- SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
- ))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- else
- {
-
- var transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(transformOrigin);
- var transform = (-offset) * radialGradient.Transform.Value * (offset);
-
- using (var shader = SKShader.CreateCompose(
- SKShader.CreateColor(reversedColors[0]),
- SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, transform.ToSKMatrix())
- ))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- }
- break;
- }
- case IConicGradientBrush conicGradient:
- {
- var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
- // Skia's default is that angle 0 is from the right hand side of the center point
- // but we are matching CSS where the vertical point above the center is 0.
- var angle = (float)(conicGradient.Angle - 90);
- var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
- if (conicGradient.Transform is { })
- {
-
- var transformOrigin = conicGradient.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(transformOrigin);
- var transform = (-offset) * conicGradient.Transform.Value * (offset);
- rotation = rotation.PreConcat(transform.ToSKMatrix());
- }
- using (var shader =
- SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
- {
- paintWrapper.Paint.Shader = shader;
- }
- break;
- }
- }
- }
- /// <summary>
- /// Configure paint wrapper for using tile brush.
- /// </summary>
- /// <param name="paintWrapper">Paint wrapper.</param>
- /// <param name="targetSize">Target size.</param>
- /// <param name="tileBrush">Tile brush to use.</param>
- /// <param name="tileBrushImage">Tile brush image.</param>
- private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
- {
- var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSizeWithDpi(_dpi), targetSize);
- var intermediate = CreateRenderTarget(calc.IntermediateSize, false);
- paintWrapper.AddDisposable(intermediate);
- using (var context = intermediate.CreateDrawingContext())
- {
- var sourceRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(96));
- var targetRect = new Rect(tileBrushImage.PixelSize.ToSizeWithDpi(_dpi));
- context.Clear(Colors.Transparent);
- context.PushClip(calc.IntermediateClip);
- context.Transform = calc.IntermediateTransform;
- context.DrawBitmap(
- RefCountable.CreateUnownedNotClonable(tileBrushImage),
- 1,
- sourceRect,
- targetRect,
- tileBrush.BitmapInterpolationMode);
- context.PopClip();
- }
- var tileTransform =
- tileBrush.TileMode != TileMode.None
- ? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y)
- : SKMatrix.CreateIdentity();
- SKShaderTileMode tileX =
- tileBrush.TileMode == TileMode.None
- ? SKShaderTileMode.Clamp
- : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY
- ? SKShaderTileMode.Mirror
- : SKShaderTileMode.Repeat;
- SKShaderTileMode tileY =
- tileBrush.TileMode == TileMode.None
- ? SKShaderTileMode.Clamp
- : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY
- ? SKShaderTileMode.Mirror
- : SKShaderTileMode.Repeat;
- var image = intermediate.SnapshotImage();
- paintWrapper.AddDisposable(image);
- var paintTransform = default(SKMatrix);
- SKMatrix.Concat(
- ref paintTransform,
- tileTransform,
- SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
- if (tileBrush.Transform is { })
- {
- var origin = tileBrush.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(origin);
- var transform = (-offset) * tileBrush.Transform.Value * (offset);
- paintTransform = paintTransform.PreConcat(transform.ToSKMatrix());
- }
- using (var shader = image.ToShader(tileX, tileY, paintTransform))
- {
- paintWrapper.Paint.Shader = shader;
- }
- }
- private void ConfigureSceneBrushContent(ref PaintWrapper paintWrapper, ISceneBrushContent content,
- Size targetSize)
- {
- if(content.UseScalableRasterization)
- ConfigureSceneBrushContentWithPicture(ref paintWrapper, content, targetSize);
- else
- ConfigureSceneBrushContentWithSurface(ref paintWrapper, content, targetSize);
- }
-
- private void ConfigureSceneBrushContentWithSurface(ref PaintWrapper paintWrapper, ISceneBrushContent content,
- Size targetSize)
- {
- var rect = content.Rect;
- var intermediateSize = rect.Size;
- if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
- {
- using var intermediate = CreateRenderTarget(intermediateSize, false);
- using (var ctx = intermediate.CreateDrawingContext())
- {
- ctx.Clear(Colors.Transparent);
- content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
- }
- ConfigureTileBrush(ref paintWrapper, targetSize, content.Brush, intermediate);
- }
- }
-
- private void ConfigureSceneBrushContentWithPicture(ref PaintWrapper paintWrapper, ISceneBrushContent content,
- Size targetSize)
- {
- var rect = content.Rect;
- var contentSize = rect.Size;
- if (contentSize.Width <= 0 || contentSize.Height <= 0)
- {
- paintWrapper.Paint.Color = SKColor.Empty;
- return;
- }
-
- var tileBrush = content.Brush;
- var transform = rect.TopLeft == default ? Matrix.Identity : Matrix.CreateTranslation(-rect.X, -rect.Y);
- var calc = new TileBrushCalculator(tileBrush, contentSize, targetSize);
- transform *= calc.IntermediateTransform;
-
- using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
- using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
- {
- ctx.PushClip(calc.IntermediateClip);
- content.Render(ctx, transform);
- ctx.PopClip();
- }
- using var picture = pictureTarget.GetPicture();
- var paintTransform =
- tileBrush.TileMode != TileMode.None
- ? SKMatrix.CreateTranslation(-(float)calc.DestinationRect.X, -(float)calc.DestinationRect.Y)
- : SKMatrix.CreateIdentity();
- SKShaderTileMode tileX =
- tileBrush.TileMode == TileMode.None
- ? SKShaderTileMode.Clamp
- : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY
- ? SKShaderTileMode.Mirror
- : SKShaderTileMode.Repeat;
- SKShaderTileMode tileY =
- tileBrush.TileMode == TileMode.None
- ? SKShaderTileMode.Clamp
- : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY
- ? SKShaderTileMode.Mirror
- : SKShaderTileMode.Repeat;
- paintTransform = SKMatrix.Concat(paintTransform,
- SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
-
- if (tileBrush.Transform is { })
- {
- var origin = tileBrush.TransformOrigin.ToPixels(targetSize);
- var offset = Matrix.CreateTranslation(origin);
- var brushTransform = (-offset) * tileBrush.Transform.Value * (offset);
- paintTransform = paintTransform.PreConcat(brushTransform.ToSKMatrix());
- }
- using (var shader = picture.ToShader(tileX, tileY, paintTransform,
- new SKRect(0, 0, picture.CullRect.Width, picture.CullRect.Height)))
- {
- paintWrapper.Paint.FilterQuality = SKFilterQuality.None;
- paintWrapper.Paint.Shader = shader;
- }
- }
- private static SKColorFilter CreateAlphaColorFilter(double opacity)
- {
- if (opacity > 1)
- opacity = 1;
- var c = new byte[256];
- var a = new byte[256];
- for (var i = 0; i < 256; i++)
- {
- c[i] = (byte)i;
- a[i] = (byte)(i * opacity);
- }
- return SKColorFilter.CreateTable(a, c, c, c);
- }
- private static byte Blend(byte leftColor, byte leftAlpha, byte rightColor, byte rightAlpha)
- {
- var ca = leftColor / 255d;
- var aa = leftAlpha / 255d;
- var cb = rightColor / 255d;
- var ab = rightAlpha / 255d;
- var r = (ca * aa + cb * ab * (1 - aa)) / (aa + ab * (1 - aa));
- return (byte)(r * 255);
- }
- private static Color Blend(Color left, Color right)
- {
- var aa = left.A / 255d;
- var ab = right.A / 255d;
- return new Color(
- (byte)((aa + ab * (1 - aa)) * 255),
- Blend(left.R, left.A, right.R, right.A),
- Blend(left.G, left.A, right.G, right.A),
- Blend(left.B, left.A, right.B, right.A)
- );
- }
- internal PaintWrapper CreateAcrylicPaint (SKPaint paint, IExperimentalAcrylicMaterial material)
- {
- var paintWrapper = new PaintWrapper(paint);
- paint.IsAntialias = true;
- var tintOpacity =
- material.BackgroundSource == AcrylicBackgroundSource.Digger ?
- material.TintOpacity : 1;
- const double noiseOpcity = 0.0225;
- var tintColor = material.TintColor;
- var tint = new SKColor(tintColor.R, tintColor.G, tintColor.B, tintColor.A);
- if (s_acrylicNoiseShader == null)
- {
- using (var stream = typeof(DrawingContextImpl).Assembly.GetManifestResourceStream("Avalonia.Skia.Assets.NoiseAsset_256X256_PNG.png"))
- using (var bitmap = SKBitmap.Decode(stream))
- {
- s_acrylicNoiseShader = SKShader.CreateBitmap(bitmap, SKShaderTileMode.Repeat, SKShaderTileMode.Repeat)
- .WithColorFilter(CreateAlphaColorFilter(noiseOpcity));
- }
- }
- using (var backdrop = SKShader.CreateColor(new SKColor(material.MaterialColor.R, material.MaterialColor.G, material.MaterialColor.B, material.MaterialColor.A)))
- using (var tintShader = SKShader.CreateColor(tint))
- using (var effectiveTint = SKShader.CreateCompose(backdrop, tintShader))
- using (var compose = SKShader.CreateCompose(effectiveTint, s_acrylicNoiseShader))
- {
- paint.Shader = compose;
- if (material.BackgroundSource == AcrylicBackgroundSource.Digger)
- {
- paint.BlendMode = SKBlendMode.Src;
- }
- return paintWrapper;
- }
- }
- /// <summary>
- /// Creates paint wrapper for given brush.
- /// </summary>
- /// <param name="paint">The paint to wrap.</param>
- /// <param name="brush">Source brush.</param>
- /// <param name="targetSize">Target size.</param>
- /// <returns>Paint wrapper for given brush.</returns>
- internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize)
- {
- var paintWrapper = new PaintWrapper(paint);
- paint.IsAntialias = true;
- double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity);
- if (brush is ISolidColorBrush solid)
- {
- paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte) (solid.Color.A * opacity));
- return paintWrapper;
- }
- paint.Color = new SKColor(255, 255, 255, (byte) (255 * opacity));
- if (brush is IGradientBrush gradient)
- {
- ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
- return paintWrapper;
- }
- var tileBrush = brush as ITileBrush;
- var tileBrushImage = default(IDrawableBitmapImpl);
- if (brush is ISceneBrush sceneBrush)
- {
- using (var content = sceneBrush.CreateContent())
- {
- if (content != null)
- {
- ConfigureSceneBrushContent(ref paintWrapper, content, targetSize);
- return paintWrapper;
- }
- else
- paint.Color = default;
- }
- }
- else if (brush is ISceneBrushContent sceneBrushContent)
- {
- ConfigureSceneBrushContent(ref paintWrapper, sceneBrushContent, targetSize);
- return paintWrapper;
- }
- else
- {
- tileBrushImage = (tileBrush as IImageBrush)?.Source?.PlatformImpl.Item as IDrawableBitmapImpl;
- }
- if (tileBrush != null && tileBrushImage != null)
- {
- ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
- }
- else
- {
- paint.Color = new SKColor(255, 255, 255, 0);
- }
- return paintWrapper;
- }
- /// <summary>
- /// Creates paint wrapper for given pen.
- /// </summary>
- /// <param name="paint">The paint to wrap.</param>
- /// <param name="pen">Source pen.</param>
- /// <param name="targetSize">Target size.</param>
- /// <returns></returns>
- private PaintWrapper? TryCreatePaint(SKPaint paint, IPen pen, Size targetSize)
- {
- // In Skia 0 thickness means - use hairline rendering
- // and for us it means - there is nothing rendered.
- if (pen.Brush is not { } brush || pen.Thickness == 0d)
- {
- return null;
- }
- var rv = CreatePaint(paint, brush, targetSize);
- paint.IsStroke = true;
- paint.StrokeWidth = (float) pen.Thickness;
- // Need to modify dashes due to Skia modifying their lengths
- // https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots
- // TODO: Still something is off, dashes are now present, but don't look the same as D2D ones.
- switch (pen.LineCap)
- {
- case PenLineCap.Round:
- paint.StrokeCap = SKStrokeCap.Round;
- break;
- case PenLineCap.Square:
- paint.StrokeCap = SKStrokeCap.Square;
- break;
- default:
- paint.StrokeCap = SKStrokeCap.Butt;
- break;
- }
- switch (pen.LineJoin)
- {
- case PenLineJoin.Miter:
- paint.StrokeJoin = SKStrokeJoin.Miter;
- break;
- case PenLineJoin.Round:
- paint.StrokeJoin = SKStrokeJoin.Round;
- break;
- default:
- paint.StrokeJoin = SKStrokeJoin.Bevel;
- break;
- }
- paint.StrokeMiter = (float) pen.MiterLimit;
- if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)
- {
- var srcDashes = pen.DashStyle.Dashes;
- var count = srcDashes.Count % 2 == 0 ? srcDashes.Count : srcDashes.Count * 2;
- var dashesArray = new float[count];
- for (var i = 0; i < count; ++i)
- {
- dashesArray[i] = (float) srcDashes[i % srcDashes.Count] * paint.StrokeWidth;
- }
- var offset = (float)(pen.DashStyle.Offset * pen.Thickness);
- var pe = SKPathEffect.CreateDash(dashesArray, offset);
- paint.PathEffect = pe;
- rv.AddDisposable(pe);
- }
- return rv;
- }
- /// <summary>
- /// Create new render target compatible with this drawing context.
- /// </summary>
- /// <param name="size">The size of the render target in DIPs.</param>
- /// <param name="isLayer">Whether the render target is being created for a layer.</param>
- /// <param name="format">Pixel format.</param>
- /// <returns></returns>
- private SurfaceRenderTarget CreateRenderTarget(Size size, bool isLayer, PixelFormat? format = null)
- {
- var pixelSize = PixelSize.FromSizeWithDpi(size, _dpi);
- var createInfo = new SurfaceRenderTarget.CreateInfo
- {
- Width = pixelSize.Width,
- Height = pixelSize.Height,
- Dpi = _dpi,
- Format = format,
- DisableTextLcdRendering = !_canTextUseLcdRendering,
- GrContext = _grContext,
- Gpu = _gpu,
- Session = _session,
- DisableManualFbo = !isLayer,
- };
- return new SurfaceRenderTarget(createInfo);
- }
- /// <summary>
- /// Skia cached paint state.
- /// </summary>
- private readonly struct PaintState : IDisposable
- {
- private readonly SKColor _color;
- private readonly SKShader _shader;
- private readonly SKPaint _paint;
-
- public PaintState(SKPaint paint, SKColor color, SKShader shader)
- {
- _paint = paint;
- _color = color;
- _shader = shader;
- }
- /// <inheritdoc />
- public void Dispose()
- {
- _paint.Color = _color;
- _paint.Shader = _shader;
- }
- }
- /// <summary>
- /// Skia paint wrapper.
- /// </summary>
- internal struct PaintWrapper : IDisposable
- {
- //We are saving memory allocations there
- public readonly SKPaint Paint;
- private IDisposable? _disposable1;
- private IDisposable? _disposable2;
- private IDisposable? _disposable3;
- public PaintWrapper(SKPaint paint)
- {
- Paint = paint;
- _disposable1 = null;
- _disposable2 = null;
- _disposable3 = null;
- }
- public IDisposable ApplyTo(SKPaint paint)
- {
- var state = new PaintState(paint, paint.Color, paint.Shader);
- paint.Color = Paint.Color;
- paint.Shader = Paint.Shader;
- return state;
- }
- /// <summary>
- /// Add new disposable to a wrapper.
- /// </summary>
- /// <param name="disposable">Disposable to add.</param>
- public void AddDisposable(IDisposable disposable)
- {
- if (_disposable1 == null)
- {
- _disposable1 = disposable;
- }
- else if (_disposable2 == null)
- {
- _disposable2 = disposable;
- }
- else if (_disposable3 == null)
- {
- _disposable3 = disposable;
- }
- else
- {
- Debug.Assert(false);
- // ReSharper disable once HeuristicUnreachableCode
- throw new InvalidOperationException(
- "PaintWrapper disposable object limit reached. You need to add extra struct fields to support more disposables.");
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Paint?.Reset();
- _disposable1?.Dispose();
- _disposable2?.Dispose();
- _disposable3?.Dispose();
- }
- }
- }
- }
|