Browse Source

Introduce advanced render options

pull/9584/head
Benedikt Stebner 3 years ago
parent
commit
0442c3b337
  1. 26
      src/Avalonia.Base/Media/DrawingContext.cs
  2. 10
      src/Avalonia.Base/Media/DrawingGroup.cs
  3. 4
      src/Avalonia.Base/Media/DrawingImage.cs
  4. 10
      src/Avalonia.Base/Media/EdgeMode.cs
  5. 42
      src/Avalonia.Base/Media/GlyphRun.cs
  6. 23
      src/Avalonia.Base/Media/GlyphRunMetrics.cs
  7. 4
      src/Avalonia.Base/Media/IImage.cs
  8. 8
      src/Avalonia.Base/Media/ITileBrush.cs
  9. 6
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  10. 6
      src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs
  11. 10
      src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs
  12. 4
      src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs
  13. 5
      src/Avalonia.Base/Media/ImmediateDrawingContext.cs
  14. 7
      src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs
  15. 11
      src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs
  16. 16
      src/Avalonia.Base/Media/PlatformDrawingContext.cs
  17. 127
      src/Avalonia.Base/Media/RenderOptions.cs
  18. 8
      src/Avalonia.Base/Media/TextDecoration.cs
  19. 11
      src/Avalonia.Base/Media/TextRenderingMode.cs
  20. 13
      src/Avalonia.Base/Media/TileBrush.cs
  21. 21
      src/Avalonia.Base/Platform/IDrawingContextImpl.cs
  22. 22
      src/Avalonia.Base/Platform/IGlyphRunBuffer.cs
  23. 19
      src/Avalonia.Base/Platform/IGlyphRunImpl.cs
  24. 3
      src/Avalonia.Base/Platform/IPlatformRenderInterface.cs
  25. 4
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  26. 36
      src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
  27. 21
      src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
  28. 2
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
  29. 7
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
  30. 18
      src/Avalonia.Base/Rendering/ImmediateRenderer.cs
  31. 68
      src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs
  32. 5
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  33. 21
      src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs
  34. 4
      src/Avalonia.Base/Visual.cs
  35. 1
      src/Avalonia.Base/composition-schema.xml
  36. 4
      src/Avalonia.Controls/Image.cs
  37. 13
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  38. 41
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  39. 118
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  40. 1
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  41. 65
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  42. 4
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  43. 54
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  44. 70
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  45. 93
      src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs
  46. 9
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  47. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  48. 11
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  49. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  50. 10
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  51. 3
      tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  52. 2
      tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
  53. 5
      tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs
  54. 21
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  55. 4
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  56. 23
      tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
  57. 44
      tests/Avalonia.RenderTests/Shapes/EllipseTests.cs
  58. 25
      tests/Avalonia.UnitTests/MockGlyphRun.cs
  59. 21
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  60. BIN
      tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Aliased.expected.png
  61. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Fill_NoTile.expected.png
  62. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Fill.expected.png
  63. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_UniformToFill.expected.png
  64. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_UniformToFill_NoTile.expected.png
  65. BIN
      tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Uniform_NoTile.expected.png
  66. BIN
      tests/TestFiles/Direct2D1/Media/ImageDrawing/ImageDrawing_Fill.expected.png
  67. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Fill_NoTile.expected.png
  68. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png
  69. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_UniformToFill_NoTile.expected.png
  70. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Uniform_NoTile.expected.png
  71. BIN
      tests/TestFiles/Direct2D1/Shapes/Ellipse/Should_Render_Circle_Aliased.expected.png
  72. BIN
      tests/TestFiles/Direct2D1/Shapes/Ellipse/Should_Render_Circle_Antialiased.expected.png
  73. BIN
      tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Aliased.expected.png
  74. BIN
      tests/TestFiles/Skia/Shapes/Ellipse/Should_Render_Circle_Aliased.expected.png
  75. BIN
      tests/TestFiles/Skia/Shapes/Ellipse/Should_Render_Circle_Antialiased.expected.png

26
src/Avalonia.Base/Media/DrawingContext.cs

@ -4,7 +4,6 @@ using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Media
{
@ -53,12 +52,10 @@ namespace Avalonia.Media
/// <param name="source">The image.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = default)
public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect)
{
_ = source ?? throw new ArgumentNullException(nameof(source));
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode);
source.Draw(this, sourceRect, destRect);
}
/// <summary>
@ -68,8 +65,7 @@ namespace Avalonia.Media
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
/// <summary>
/// Draws a line.
@ -286,8 +282,7 @@ namespace Avalonia.Media
Opacity,
Clip,
GeometryClip,
OpacityMask,
BitmapBlendMode
OpacityMask
}
public RestoreState(DrawingContext context, PushedStateType type)
@ -312,8 +307,6 @@ namespace Avalonia.Media
_context.PopGeometryClipCore();
else if (_type == PushedStateType.OpacityMask)
_context.PopOpacityMaskCore();
else if (_type == PushedStateType.BitmapBlendMode)
_context.PopBitmapBlendModeCore();
}
}
@ -394,16 +387,6 @@ namespace Avalonia.Media
}
protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds);
public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
PushBitmapBlendMode(blendingMode);
_states ??= StateStackPool.Get();
_states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode));
return new PushedState(this);
}
protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode);
/// <summary>
/// Pushes a matrix transformation.
/// </summary>
@ -431,7 +414,6 @@ namespace Avalonia.Media
protected abstract void PopGeometryClipCore();
protected abstract void PopOpacityCore();
protected abstract void PopOpacityMaskCore();
protected abstract void PopBitmapBlendModeCore();
protected abstract void PopTransformCore();
private static bool PenIsVisible(IPen? pen)

10
src/Avalonia.Base/Media/DrawingGroup.cs

@ -196,13 +196,7 @@ namespace Avalonia.Media
throw new NotImplementedException();
}
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode)
{
throw new NotImplementedException();
}
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
throw new NotImplementedException();
}
@ -321,8 +315,6 @@ namespace Avalonia.Media
protected override void PopOpacityMaskCore() => Pop();
protected override void PopBitmapBlendModeCore() => Pop();
protected override void PopTransformCore() => Pop();
/// <summary>

4
src/Avalonia.Base/Media/DrawingImage.cs

@ -1,6 +1,5 @@
using System;
using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Media
{
@ -43,8 +42,7 @@ namespace Avalonia.Media
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
Rect destRect)
{
var drawing = Drawing;

10
src/Avalonia.Base/Media/EdgeMode.cs

@ -0,0 +1,10 @@
namespace Avalonia.Media
{
public enum EdgeMode : byte
{
Unspecified,
Antialias,
Aliased
}
}

42
src/Avalonia.Base/Media/GlyphRun.cs

@ -153,7 +153,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets the conservative bounding box of the <see cref="GlyphRun"/>.
/// </summary>
public Rect Bounds => PlatformImpl.Item.Bounds;
public Rect Bounds => new Rect(new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height));
/// <summary>
///
@ -166,7 +166,7 @@ namespace Avalonia.Media
/// </summary>
public Point BaselineOrigin
{
get => PlatformImpl.Item.BaselineOrigin;
get => _baselineOrigin ?? new Point(0, Metrics.Baseline);
set => Set(ref _baselineOrigin, value);
}
@ -676,13 +676,17 @@ namespace Avalonia.Media
}
}
return new GlyphRunMetrics(
width,
trailingWhitespaceLength,
newLineLength,
firstCluster,
lastCluster
);
return new GlyphRunMetrics
{
Baseline = -GlyphTypeface.Metrics.Ascent * Scale,
Width = width,
WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace,
Height = height,
NewLineLength = newLineLength,
TrailingWhitespaceLength = trailingWhitespaceLength,
FirstCluster = firstCluster,
LastCluster = lastCluster
};
}
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
@ -820,10 +824,11 @@ namespace Avalonia.Media
private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
{
var platformImpl = s_renderInterface.CreateGlyphRun(
GlyphTypeface,
FontRenderingEmSize,
GlyphInfos,
_baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale));
GlyphTypeface,
FontRenderingEmSize,
GlyphInfos,
BaselineOrigin,
Bounds);
_platformImpl = RefCountable.Create(platformImpl);
@ -835,5 +840,16 @@ namespace Avalonia.Media
_platformImpl?.Dispose();
_platformImpl = null;
}
/// <summary>
/// Gets the intersections of specified upper and lower limit.
/// </summary>
/// <param name="lowerLimit">Upper limit.</param>
/// <param name="upperLimit">Lower limit.</param>
/// <returns></returns>
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
{
return PlatformImpl.Item.GetIntersections(lowerLimit, upperLimit);
}
}
}

23
src/Avalonia.Base/Media/GlyphRunMetrics.cs

@ -2,23 +2,20 @@
{
public readonly record struct GlyphRunMetrics
{
public GlyphRunMetrics(double width, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster)
{
Width = width;
TrailingWhitespaceLength = trailingWhitespaceLength;
NewLineLength = newLineLength;
FirstCluster = firstCluster;
LastCluster = lastCluster;
}
public double Baseline { get; init; }
public double Width { get; }
public double Width { get; init; }
public int TrailingWhitespaceLength { get; }
public double WidthIncludingTrailingWhitespace { get; init; }
public int NewLineLength { get; }
public double Height { get; init; }
public int FirstCluster { get; }
public int TrailingWhitespaceLength { get; init; }
public int LastCluster { get; }
public int NewLineLength { get; init; }
public int FirstCluster { get; init; }
public int LastCluster { get; init; }
}
}

4
src/Avalonia.Base/Media/IImage.cs

@ -18,11 +18,9 @@ namespace Avalonia.Media
/// <param name="context">The drawing context.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode);
Rect destRect);
}
}

8
src/Avalonia.Base/Media/ITileBrush.cs

@ -39,13 +39,5 @@ namespace Avalonia.Media
/// Gets the brush's tile mode.
/// </summary>
TileMode TileMode { get; }
/// <summary>
/// Gets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The bitmap interpolation mode.
/// </value>
BitmapInterpolationMode BitmapInterpolationMode { get; }
}
}

6
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -224,15 +224,13 @@ namespace Avalonia.Media.Imaging
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
Rect destRect)
{
context.DrawBitmap(
PlatformImpl,
1,
sourceRect,
destRect,
bitmapInterpolationMode);
destRect);
}
private static IPlatformRenderInterface GetFactory()

6
src/Avalonia.Base/Media/Imaging/BitmapBlendingMode.cs

@ -3,8 +3,10 @@ namespace Avalonia.Media.Imaging
/// <summary>
/// Controls the way the bitmaps are drawn together.
/// </summary>
public enum BitmapBlendingMode
public enum BitmapBlendingMode : byte
{
Unspecified,
/// <summary>
/// Source is placed over the destination.
/// </summary>
@ -52,6 +54,6 @@ namespace Avalonia.Media.Imaging
/// <summary>
/// Display the sum of the source image and destination image.
/// </summary>
Plus,
Plus
}
}

10
src/Avalonia.Base/Media/Imaging/BitmapInterpolationMode.cs

@ -3,12 +3,14 @@
/// <summary>
/// Controls the performance and quality of bitmap scaling.
/// </summary>
public enum BitmapInterpolationMode
public enum BitmapInterpolationMode : byte
{
Unspecified,
/// <summary>
/// Uses the default behavior of the underling render backend.
/// Disable interpolation.
/// </summary>
Default,
None,
/// <summary>
/// The best performance but worst image quality.
@ -18,7 +20,7 @@
/// <summary>
/// Good performance and decent image quality.
/// </summary>
MediumQuality,
MediumQuality,
/// <summary>
/// Highest quality but worst performance.

4
src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs

@ -83,12 +83,12 @@ namespace Avalonia.Media.Imaging
}
}
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)
{
if (Source is not IBitmap bmp)
return;
var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi);
Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode);
Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect);
}
}
}

5
src/Avalonia.Base/Media/ImmediateDrawingContext.cs

@ -80,11 +80,10 @@ namespace Avalonia.Media
/// <param name="source">The bitmap.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect)
{
_ = source ?? throw new ArgumentNullException(nameof(source));
PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode);
PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect);
}
/// <summary>

7
src/Avalonia.Base/Media/Immutable/ImmutableImageBrush.cs

@ -22,7 +22,6 @@ namespace Avalonia.Media.Immutable
/// How the source rectangle will be stretched to fill the destination rect.
/// </param>
/// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public ImmutableImageBrush(
IBitmap? source,
AlignmentX alignmentX = AlignmentX.Center,
@ -33,8 +32,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
TileMode tileMode = TileMode.None)
: base(
alignmentX,
alignmentY,
@ -44,8 +42,7 @@ namespace Avalonia.Media.Immutable
transformOrigin,
sourceRect ?? RelativeRect.Fill,
stretch,
tileMode,
bitmapInterpolationMode)
tileMode)
{
Source = source;
}

11
src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs

@ -21,7 +21,6 @@ namespace Avalonia.Media.Immutable
/// How the source rectangle will be stretched to fill the destination rect.
/// </param>
/// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
protected ImmutableTileBrush(
AlignmentX alignmentX,
AlignmentY alignmentY,
@ -31,8 +30,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin,
RelativeRect sourceRect,
Stretch stretch,
TileMode tileMode,
BitmapInterpolationMode bitmapInterpolationMode)
TileMode tileMode)
{
AlignmentX = alignmentX;
AlignmentY = alignmentY;
@ -43,7 +41,6 @@ namespace Avalonia.Media.Immutable
SourceRect = sourceRect;
Stretch = stretch;
TileMode = tileMode;
BitmapInterpolationMode = bitmapInterpolationMode;
}
/// <summary>
@ -60,8 +57,7 @@ namespace Avalonia.Media.Immutable
source.TransformOrigin,
source.SourceRect,
source.Stretch,
source.TileMode,
source.BitmapInterpolationMode)
source.TileMode)
{
}
@ -95,8 +91,5 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/>
public TileMode TileMode { get; }
/// <inheritdoc/>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
}
}

16
src/Avalonia.Base/Media/PlatformDrawingContext.cs

@ -25,6 +25,12 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
_ownsImpl = ownsImpl;
}
public RenderOptions RenderOptions
{
get => _impl.RenderOptions;
set => _impl.RenderOptions = value;
}
protected override void DrawLineCore(IPen pen, Point p1, Point p2) =>
_impl.DrawLine(pen, p1, p2);
@ -37,9 +43,8 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect) => _impl.DrawEllipse(brush, pen, rect);
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) =>
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode);
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect) =>
_impl.DrawBitmap(source, opacity, sourceRect, destRect);
public override void Custom(ICustomDrawOperation custom) =>
custom.Render(_impl);
@ -65,9 +70,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void PushOpacityMaskCore(IBrush mask, Rect bounds) =>
_impl.PushOpacityMask(mask, bounds);
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode) =>
_impl.PushBitmapBlendMode(blendingMode);
protected override void PushTransformCore(Matrix matrix)
{
_transforms ??= TransformStackPool.Get();
@ -84,8 +86,6 @@ internal sealed class PlatformDrawingContext : DrawingContext, IDrawingContextWi
protected override void PopOpacityMaskCore() => _impl.PopOpacityMask();
protected override void PopBitmapBlendModeCore() => _impl.PopBitmapBlendMode();
protected override void PopTransformCore() =>
_impl.Transform =
(_transforms ?? throw new ObjectDisposedException(nameof(PlatformDrawingContext))).Pop();

127
src/Avalonia.Base/Media/RenderOptions.cs

@ -1,36 +1,131 @@
using Avalonia.Media.Imaging;
namespace Avalonia.Media
{
public class RenderOptions
{
public readonly record struct RenderOptions
{
public BitmapInterpolationMode BitmapInterpolationMode { get; init; }
public EdgeMode EdgeMode { get; init; }
public TextRenderingMode TextRenderingMode { get; init; }
public BitmapBlendingMode BitmapBlendingMode { get; init; }
/// <summary>
/// Gets the value of the BitmapInterpolationMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static BitmapInterpolationMode GetBitmapInterpolationMode(Visual visual)
{
return visual.RenderOptions.BitmapInterpolationMode;
}
/// <summary>
/// Defines the <see cref="BitmapInterpolationMode"/> property.
/// Sets the value of the BitmapInterpolationMode attached property for a visual.
/// </summary>
public static readonly StyledProperty<BitmapInterpolationMode> BitmapInterpolationModeProperty =
AvaloniaProperty.RegisterAttached<RenderOptions, AvaloniaObject, BitmapInterpolationMode>(
"BitmapInterpolationMode",
BitmapInterpolationMode.MediumQuality,
inherits: true);
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetBitmapInterpolationMode(Visual visual, BitmapInterpolationMode value)
{
visual.RenderOptions = visual.RenderOptions with { BitmapInterpolationMode = value };
}
/// <summary>
/// Gets the value of the BitmapInterpolationMode attached property for a control.
/// Gets the value of the BitmapBlendingMode attached property for a visual.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static BitmapInterpolationMode GetBitmapInterpolationMode(AvaloniaObject element)
public static BitmapBlendingMode GetBitmapBlendingMode(Visual visual)
{
return element.GetValue(BitmapInterpolationModeProperty);
return visual.RenderOptions.BitmapBlendingMode;
}
/// <summary>
/// Sets the value of the BitmapInterpolationMode attached property for a control.
/// Sets the value of the BitmapBlendingMode attached property for a visual.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetBitmapInterpolationMode(AvaloniaObject element, BitmapInterpolationMode value)
public static void SetBitmapBlendingMode(Visual visual, BitmapBlendingMode value)
{
element.SetValue(BitmapInterpolationModeProperty, value);
visual.RenderOptions = visual.RenderOptions with { BitmapBlendingMode = value };
}
/// <summary>
/// Gets the value of the EdgeMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static EdgeMode GetEdgeMode(Visual visual)
{
return visual.RenderOptions.EdgeMode;
}
/// <summary>
/// Sets the value of the EdgeMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetEdgeMode(Visual visual, EdgeMode value)
{
visual.RenderOptions = visual.RenderOptions with { EdgeMode = value };
}
/// <summary>
/// Gets the value of the TextRenderingMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns>
public static TextRenderingMode GetTextRenderingMode(Visual visual)
{
return visual.RenderOptions.TextRenderingMode;
}
/// <summary>
/// Sets the value of the TextRenderingMode attached property for a visual.
/// </summary>
/// <param name="visual">The control.</param>
/// <param name="value">The left value.</param>
public static void SetTextRenderingMode(Visual visual, TextRenderingMode value)
{
visual.RenderOptions = visual.RenderOptions with { TextRenderingMode = value };
}
public RenderOptions MergeWith(RenderOptions other)
{
var bitmapInterpolationMode = BitmapInterpolationMode;
if (bitmapInterpolationMode == BitmapInterpolationMode.Unspecified)
{
bitmapInterpolationMode = other.BitmapInterpolationMode;
}
var edgeMode = EdgeMode;
if (edgeMode == EdgeMode.Unspecified)
{
edgeMode = other.EdgeMode;
}
var textRenderingMode = TextRenderingMode;
if (textRenderingMode == TextRenderingMode.Unspecified)
{
textRenderingMode = other.TextRenderingMode;
}
var bitmapBlendingMode = BitmapBlendingMode;
if (bitmapBlendingMode == BitmapBlendingMode.Unspecified)
{
bitmapBlendingMode = other.BitmapBlendingMode;
}
return new RenderOptions
{
BitmapInterpolationMode = bitmapInterpolationMode,
EdgeMode = edgeMode,
TextRenderingMode = textRenderingMode,
BitmapBlendingMode = bitmapBlendingMode
};
}
}
}

8
src/Avalonia.Base/Media/TextDecoration.cs

@ -1,10 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Collections.Pooled;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -218,7 +214,7 @@ namespace Avalonia.Media
{
var offsetY = glyphRun.BaselineOrigin.Y - origin.Y;
var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
var intersections = glyphRun.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY));
if (intersections.Count > 0)
{

11
src/Avalonia.Base/Media/TextRenderingMode.cs

@ -0,0 +1,11 @@
namespace Avalonia.Media
{
public enum TextRenderingMode : byte
{
Unspecified,
SubpixelAntialias,
Antialias,
Alias
}
}

13
src/Avalonia.Base/Media/TileBrush.cs

@ -83,7 +83,6 @@ namespace Avalonia.Media
SourceRectProperty,
StretchProperty,
TileModeProperty);
RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue<TileBrush>(BitmapInterpolationMode.Default);
}
/// <summary>
@ -140,17 +139,5 @@ namespace Avalonia.Media
get { return (TileMode)GetValue(TileModeProperty); }
set { SetValue(TileModeProperty, value); }
}
/// <summary>
/// Gets or sets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The bitmap interpolation mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode
{
get { return RenderOptions.GetBitmapInterpolationMode(this); }
set { RenderOptions.SetBitmapInterpolationMode(this, value); }
}
}
}

21
src/Avalonia.Base/Platform/IDrawingContextImpl.cs

@ -2,8 +2,8 @@ using System;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Platform
{
@ -13,6 +13,11 @@ namespace Avalonia.Platform
[Unstable]
public interface IDrawingContextImpl : IDisposable
{
/// <summary>
/// Gets or sets the current render options used to control the rendering behavior of drawing operations.
/// </summary>
RenderOptions RenderOptions { get; set; }
/// <summary>
/// Gets or sets the current transform of the drawing context.
/// </summary>
@ -31,8 +36,7 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
/// <summary>
/// Draws a bitmap image.
@ -157,17 +161,6 @@ namespace Avalonia.Platform
/// </summary>
void PopGeometryClip();
/// <summary>
/// Pushes a bitmap blending value.
/// </summary>
/// <param name="blendingMode">The bitmap blending mode.</param>
void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
/// <summary>
/// Pops the latest pushed bitmap blending value.
/// </summary>
void PopBitmapBlendMode();
/// <summary>
/// Adds a custom draw operation
/// </summary>

22
src/Avalonia.Base/Platform/IGlyphRunBuffer.cs

@ -1,22 +0,0 @@
using System;
using System.Drawing;
namespace Avalonia.Platform
{
public interface IGlyphRunBuffer
{
Span<ushort> GlyphIndices { get; }
IGlyphRunImpl Build();
}
public interface IHorizontalGlyphRunBuffer : IGlyphRunBuffer
{
Span<float> GlyphPositions { get; }
}
public interface IPositionedGlyphRunBuffer : IGlyphRunBuffer
{
Span<PointF> GlyphPositions { get; }
}
}

19
src/Avalonia.Base/Platform/IGlyphRunImpl.cs

@ -1,25 +1,36 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Platform
{
/// <summary>
/// Actual implementation of a glyph run that stores platform dependent resources.
/// An immutable platform representation of a <see cref="GlyphRun"/>.
/// </summary>
[Unstable]
public interface IGlyphRunImpl : IDisposable
public interface IGlyphRunImpl : IDisposable
{
/// <summary>
/// Gets the conservative bounding box of the glyph run./>.
/// Gets the <see cref="IGlyphTypeface"/> for the <see cref="IGlyphRunImpl"/>.
/// </summary>
Rect Bounds { get; }
IGlyphTypeface GlyphTypeface { get; }
/// <summary>
/// Gets the em size used for rendering the <see cref="IGlyphRunImpl"/>.
/// </summary>
double FontRenderingEmSize { get; }
/// <summary>
/// Gets the baseline origin of the glyph run./>.
/// </summary>
Point BaselineOrigin { get; }
/// <summary>
/// Gets the conservative bounding box of the glyph run./>.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// Gets the intersections of specified upper and lower limit.
/// </summary>

3
src/Avalonia.Base/Platform/IPlatformRenderInterface.cs

@ -169,8 +169,9 @@ namespace Avalonia.Platform
/// <param name="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="glyphInfos">The list of glyphs.</param>
/// <param name="baselineOrigin">The baseline origin of the run. Can be null.</param>
/// <param name="bounds">the conservative bounding box of the run</param>
/// <returns>An <see cref="IGlyphRunImpl"/>.</returns>
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin);
IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds);
/// <summary>
/// Creates a backend-specific object using a low-level API graphics context

4
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -260,6 +260,8 @@ public class CompositingRenderer : IRendererWithCompositor
if (!comp.Effect.EffectEquals(visual.Effect))
comp.Effect = visual.Effect?.ToImmutable();
comp.RenderOptions = visual.RenderOptions;
var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform)
@ -272,8 +274,6 @@ public class CompositingRenderer : IRendererWithCompositor
renderTransform *= (-offset) * visual.RenderTransform.Value * (offset);
}
comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
_recorder.BeginUpdate(comp.DrawList);

36
src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs

@ -78,15 +78,14 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
}
}
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
var next = NextDrawAs<ImageNode>();
if (next == null ||
!next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode))
!next.Item.Equals(Transform, source, opacity, sourceRect, destRect))
{
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode));
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
}
else
{
@ -227,20 +226,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
}
}
protected override void PopBitmapBlendModeCore()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationIndex;
}
}
protected override void PopOpacityCore()
{
var next = NextDrawAs<OpacityNode>();
@ -354,21 +339,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
_needsToPopOpacityMask.Push(needsToPop);
}
/// <inheritdoc/>
protected override void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationIndex;
}
}
private void Add<T>(T node) where T : class, IDrawOperation
{
if (_drawOperationIndex < _builder.Count)

21
src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs

@ -42,15 +42,20 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
set => _impl.Transform = (_transform = value) * PostTransform;
}
public RenderOptions RenderOptions
{
get => _impl.RenderOptions;
set => _impl.RenderOptions = value;
}
public void Clear(Color color)
{
_impl.Clear(color);
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
_impl.DrawBitmap(source, opacity, sourceRect, destRect, bitmapInterpolationMode);
_impl.DrawBitmap(source, opacity, sourceRect, destRect);
}
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
@ -133,16 +138,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.PopGeometryClip();
}
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_impl.PushBitmapBlendMode(blendingMode);
}
public void PopBitmapBlendMode()
{
_impl.PopBitmapBlendMode();
}
public void Custom(ICustomDrawOperation custom)
{
_impl.Custom(custom);

2
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs

@ -181,7 +181,7 @@ namespace Avalonia.Rendering.Composition.Server
else
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
new Rect(_layerSize),
new Rect(Size), BitmapInterpolationMode.LowQuality);
new Rect(Size));
if (DebugOverlays != RendererDebugOverlays.None)
{

7
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs

@ -66,6 +66,8 @@ namespace Avalonia.Rendering.Composition.Server
if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
canvas.RenderOptions = RenderOptions;
RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix
@ -122,6 +124,11 @@ namespace Avalonia.Rendering.Composition.Server
var wasVisible = IsVisibleInFrame;
if(Parent != null)
{
RenderOptions = RenderOptions.MergeWith(Parent.RenderOptions);
}
// Calculate new parent-relative transform
if (_combinedTransformDirty)
{

18
src/Avalonia.Base/Rendering/ImmediateRenderer.cs

@ -97,7 +97,18 @@ namespace Avalonia.Rendering
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransform(Matrix.Identity))
{
visual.Render(context);
var currentRenderOptions = default(RenderOptions);
var platformContext = context as PlatformDrawingContext;
if(platformContext != null)
{
currentRenderOptions = platformContext.RenderOptions;
platformContext.RenderOptions = visual.RenderOptions.MergeWith(platformContext.RenderOptions);
}
visual.Render(context);
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
@ -115,6 +126,11 @@ namespace Avalonia.Rendering
Render(context, child, childClipRect);
}
}
if(platformContext != null)
{
platformContext.RenderOptions = currentRenderOptions;
}
}
}
}

68
src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs

@ -1,68 +0,0 @@
using Avalonia.Platform;
using Avalonia.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an bitmap blending mode push or pop.
/// </summary>
internal class BitmapBlendModeNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> push.
/// </summary>
/// <param name="bitmapBlend">The <see cref="BitmapBlendingMode"/> to push.</param>
public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
{
BlendingMode = bitmapBlend;
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> pop.
/// </summary>
public BitmapBlendModeNode()
{
}
/// <inheritdoc/>
public Rect Bounds => default;
/// <summary>
/// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
/// </summary>
public BitmapBlendingMode? BlendingMode { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="blendingMode">the <see cref="BitmapBlendModeNode"/> how to compare</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (BlendingMode.HasValue)
{
context.PushBitmapBlendMode(BlendingMode.Value);
}
else
{
context.PopBitmapBlendMode();
}
}
public void Dispose()
{
}
}
}

5
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@ -1,5 +1,4 @@
using System;
using Avalonia.Media;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="glyphRun">The glyph run to draw.</param>
public GlyphRunNode(
Matrix transform,
IImmutableBrush foreground,
IImmutableBrush? foreground,
IRef<IGlyphRunImpl> glyphRun)
: base(glyphRun.Item.Bounds, transform, foreground)
{

21
src/Avalonia.Base/Rendering/SceneGraph/ImageNode.cs

@ -1,6 +1,5 @@
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph
{
@ -17,15 +16,13 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="opacity">The draw opacity.</param>
/// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
: base(destRect, transform)
{
Source = source.Clone();
Opacity = opacity;
SourceRect = sourceRect;
DestRect = destRect;
BitmapInterpolationMode = bitmapInterpolationMode;
SourceVersion = Source.Item.Version;
}
@ -53,14 +50,6 @@ namespace Avalonia.Rendering.SceneGraph
/// Gets the destination rect.
/// </summary>
public Rect DestRect { get; }
/// <summary>
/// Gets the bitmap interpolation mode.
/// </summary>
/// <value>
/// The scaling mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
/// <summary>
/// Determines if this draw operation equals another.
@ -70,27 +59,25 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <param name="sourceRect">The source rect of the other draw operation.</param>
/// <param name="destRect">The dest rect of the other draw operation.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public bool Equals(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
return transform == Transform &&
Equals(source.Item, Source.Item) &&
source.Item.Version == SourceVersion &&
opacity == Opacity &&
sourceRect == SourceRect &&
destRect == DestRect &&
bitmapInterpolationMode == BitmapInterpolationMode;
destRect == DestRect;
}
/// <inheritdoc/>
public override void Render(IDrawingContextImpl context)
{
context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode);
context.DrawBitmap(Source, Opacity, SourceRect, DestRect);
}
/// <inheritdoc/>

4
src/Avalonia.Base/Visual.cs

@ -318,7 +318,9 @@ namespace Avalonia
internal CompositionDrawListVisual? CompositionVisual { get; private set; }
internal CompositionVisual? ChildCompositionVisual { get; set; }
internal RenderOptions RenderOptions { get; set; }
public bool HasNonUniformZIndexChildren { get; private set; }
/// <summary>

1
src/Avalonia.Base/composition-schema.xml

@ -30,6 +30,7 @@
<Property Name="AdornerIsClipped" Type="bool" Internal="true" />
<Property Name="OpacityMaskBrush" Type="Avalonia.Media.IImmutableBrush?" Internal="true" />
<Property Name="Effect" Type="Avalonia.Media.IImmutableEffect?" Internal="true" />
<Property Name="RenderOptions" Type="Avalonia.Media.RenderOptions" />
</Object>
<Object Name="CompositionContainerVisual" Inherits="CompositionVisual"/>
<Object Name="CompositionSolidColorVisual" Inherits="CompositionContainerVisual">

4
src/Avalonia.Controls/Image.cs

@ -91,9 +91,7 @@ namespace Avalonia.Controls
Rect sourceRect = new Rect(sourceSize)
.CenterRect(new Rect(destRect.Size / scale));
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
context.DrawImage(source, sourceRect, destRect, interpolationMode);
context.DrawImage(source, sourceRect, destRect);
}
}

13
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
@ -125,7 +123,8 @@ namespace Avalonia.Headless
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin)
Point baselineOrigin,
Rect bounds)
{
return new HeadlessGlyphRunStub();
}
@ -136,6 +135,10 @@ namespace Avalonia.Headless
public Point BaselineOrigin => new Point(0, 8);
public IGlyphTypeface GlyphTypeface => new HeadlessGlyphTypefaceImpl();
public double FontRenderingEmSize => 12;
public void Dispose()
{
}
@ -372,6 +375,8 @@ namespace Avalonia.Headless
public Matrix Transform { get; set; }
public RenderOptions RenderOptions { get; set; }
public void Clear(Color color)
{
@ -454,7 +459,7 @@ namespace Avalonia.Headless
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
}

41
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -5,12 +5,10 @@ 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;
@ -27,10 +25,8 @@ namespace Avalonia.Skia
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;
@ -171,6 +167,8 @@ namespace Avalonia.Skia
public SKCanvas Canvas { get; }
public SKSurface? Surface { get; }
public RenderOptions RenderOptions { get; set; }
private void CheckLease()
{
if (_leased)
@ -185,7 +183,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
CheckLease();
var drawableImage = (IDrawableBitmapImpl)source.Item;
@ -194,8 +192,8 @@ namespace Avalonia.Skia
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();
paint.FilterQuality = RenderOptions.BitmapInterpolationMode.ToSKFilterQuality();
paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode();
drawableImage.Draw(this, s, d, paint);
SKPaintCache.Shared.ReturnReset(paint);
@ -206,7 +204,7 @@ namespace Avalonia.Skia
{
CheckLease();
PushOpacityMask(opacityMask, opacityMaskRect);
DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect);
PopOpacityMask();
}
@ -522,7 +520,9 @@ namespace Avalonia.Skia
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.Item.BaselineOrigin.X,
var textBlob = glyphRunImpl.GetTextBlob(RenderOptions);
Canvas.DrawText(textBlob, (float)glyphRun.Item.BaselineOrigin.X,
(float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint);
}
}
@ -652,21 +652,6 @@ namespace Avalonia.Skia
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();
@ -914,12 +899,14 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.RenderOptions = RenderOptions;
context.DrawBitmap(
RefCountable.CreateUnownedNotClonable(tileBrushImage),
1,
sourceRect,
targetRect,
tileBrush.BitmapInterpolationMode);
targetRect);
context.PopClip();
}
@ -1152,7 +1139,7 @@ namespace Avalonia.Skia
{
var paintWrapper = new PaintWrapper(paint);
paint.IsAntialias = true;
paint.IsAntialias = RenderOptions.EdgeMode != EdgeMode.Aliased;
double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity);

118
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using SkiaSharp;
@ -7,30 +9,122 @@ namespace Avalonia.Skia
{
internal class GlyphRunImpl : IGlyphRunImpl
{
public GlyphRunImpl(SKTextBlob textBlob, Size size, Point baselineOrigin)
private readonly GlyphTypefaceImpl _glyphTypefaceImpl;
private readonly ushort[] _glyphIndices;
private readonly SKPoint[] _glyphPositions;
private readonly Dictionary<SKFontEdging, SKTextBlob> _textBlobCache = new(1);
public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
TextBlob = textBlob ?? throw new ArgumentNullException(nameof(textBlob));
if (glyphTypeface == null)
{
throw new ArgumentNullException(nameof(glyphTypeface));
}
_glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
if (glyphInfos == null)
{
throw new ArgumentNullException(nameof(glyphInfos));
}
var count = glyphInfos.Count;
_glyphIndices = new ushort[count];
_glyphPositions = new SKPoint[count];
var currentX = 0.0;
for (int i = 0; i < count; i++)
{
var glyphInfo = glyphInfos[i];
var offset = glyphInfo.GlyphOffset;
_glyphIndices[i] = glyphInfo.GlyphIndex;
Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
_glyphPositions[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y);
currentX += glyphInfos[i].GlyphAdvance;
}
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin;
Bounds = bounds;
}
/// <summary>
/// Gets the text blob to draw.
/// </summary>
public SKTextBlob TextBlob { get; }
public IGlyphTypeface GlyphTypeface => _glyphTypefaceImpl;
public Rect Bounds { get; }
public double FontRenderingEmSize { get; }
public Point BaselineOrigin { get; }
public IReadOnlyList<float> GetIntersections(float upperBound, float lowerBound) =>
TextBlob.GetIntercepts(lowerBound, upperBound);
public Rect Bounds { get; }
public SKTextBlob GetTextBlob(RenderOptions renderOptions)
{
var edging = SKFontEdging.SubpixelAntialias;
switch (renderOptions.TextRenderingMode)
{
case TextRenderingMode.Alias:
edging = SKFontEdging.Alias;
break;
case TextRenderingMode.Antialias:
edging = SKFontEdging.Antialias;
break;
case TextRenderingMode.Unspecified:
edging = renderOptions.EdgeMode == EdgeMode.Aliased ? SKFontEdging.Alias : SKFontEdging.SubpixelAntialias;
break;
}
if (_textBlobCache.TryGetValue(edging, out var textBlob))
{
return textBlob;
}
void IDisposable.Dispose()
var font = SKFontCache.Shared.Get();
font.LinearMetrics = true;
font.Subpixel = edging == SKFontEdging.SubpixelAntialias;
font.Edging = edging;
font.Hinting = SKFontHinting.Full;
font.Size = (float)FontRenderingEmSize;
font.Typeface = _glyphTypefaceImpl.Typeface;
font.Embolden = (_glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0;
font.SkewX = (_glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0;
var builder = SKTextBlobBuilderCache.Shared.Get();
var runBuffer = builder.AllocatePositionedRun(font, _glyphIndices.Length);
runBuffer.SetPositions(_glyphPositions);
runBuffer.SetGlyphs(_glyphIndices);
SKFontCache.Shared.Return(font);
textBlob = builder.Build();
SKTextBlobBuilderCache.Shared.Return(builder);
_textBlobCache.Add(edging, textBlob);
return textBlob;
}
public void Dispose()
{
TextBlob.Dispose();
foreach (var pair in _textBlobCache)
{
pair.Value.Dispose();
}
}
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
{
var textBlob = GetTextBlob(default);
return textBlob.GetIntercepts(lowerLimit, upperLimit);
}
}
}

1
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -1,5 +1,4 @@
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Skia
{

65
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -5,8 +5,8 @@ using Avalonia.Media;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using SkiaSharp;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Skia
{
@ -209,67 +209,10 @@ namespace Avalonia.Skia
return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
}
public IGlyphRunImpl CreateGlyphRun(
IGlyphTypeface glyphTypeface,
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
if (glyphTypeface == null)
{
throw new ArgumentNullException(nameof(glyphTypeface));
}
if (glyphInfos == null)
{
throw new ArgumentNullException(nameof(glyphInfos));
}
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
var font = SKFontCache.Shared.Get();
font.LinearMetrics = true;
font.Subpixel = true;
font.Edging = SKFontEdging.SubpixelAntialias;
font.Hinting = SKFontHinting.Full;
font.Size = (float)fontRenderingEmSize;
font.Typeface = glyphTypefaceImpl.Typeface;
font.Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0;
font.SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0;
var builder = SKTextBlobBuilderCache.Shared.Get();
var count = glyphInfos.Count;
var runBuffer = builder.AllocatePositionedRun(font, count);
var glyphSpan = runBuffer.GetGlyphSpan();
var positionSpan = runBuffer.GetPositionSpan();
SKFontCache.Shared.Return(font);
var width = 0.0;
for (int i = 0; i < count; i++)
{
var glyphInfo = glyphInfos[i];
var offset = glyphInfo.GlyphOffset;
glyphSpan[i] = glyphInfo.GlyphIndex;
positionSpan[i] = new SKPoint((float)(width + offset.X), (float)offset.Y);
width += glyphInfo.GlyphAdvance;
}
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
var height = glyphTypeface.Metrics.LineSpacing * scale;
var skTextBlob = builder.Build();
SKTextBlobBuilderCache.Shared.Return(builder);
return new GlyphRunImpl(skTextBlob, new Size(width, height), baselineOrigin);
return new GlyphRunImpl(glyphTypeface, fontRenderingEmSize, glyphInfos, baselineOrigin, bounds);
}
}
}

4
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -19,7 +19,8 @@ namespace Avalonia.Skia
return SKFilterQuality.Medium;
case BitmapInterpolationMode.HighQuality:
return SKFilterQuality.High;
case BitmapInterpolationMode.Default:
case BitmapInterpolationMode.None:
case BitmapInterpolationMode.Unspecified:
return SKFilterQuality.None;
default:
throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null);
@ -30,6 +31,7 @@ namespace Avalonia.Skia
{
switch (blendingMode)
{
case BitmapBlendingMode.Unspecified:
case BitmapBlendingMode.SourceOver:
return SKBlendMode.SrcOver;
case BitmapBlendingMode.Source:

54
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -160,59 +160,9 @@ namespace Avalonia.Direct2D1
public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<IGeometryImpl> children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, IGeometryImpl g1, IGeometryImpl g2) => new CombinedGeometryImpl(combineMode, g1, g2);
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
var glyphCount = glyphInfos.Count;
var run = new SharpDX.DirectWrite.GlyphRun
{
FontFace = glyphTypefaceImpl.FontFace,
FontSize = (float)fontRenderingEmSize
};
var indices = new short[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
indices[i] = (short)glyphInfos[i].GlyphIndex;
}
run.Indices = indices;
run.Advances = new float[glyphCount];
var width = 0.0;
for (var i = 0; i < glyphCount; i++)
{
var advance = glyphInfos[i].GlyphAdvance;
width += advance;
run.Advances[i] = (float)advance;
}
run.Offsets = new GlyphOffset[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
var (x, y) = glyphInfos[i].GlyphOffset;
run.Offsets[i] = new GlyphOffset
{
AdvanceOffset = (float)x,
AscenderOffset = (float)y
};
}
var scale = fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight;
var height = glyphTypeface.Metrics.LineSpacing * scale;
return new GlyphRunImpl(run, new Size(width, height), baselineOrigin);
return new GlyphRunImpl(glyphTypeface, fontRenderingEmSize, glyphInfos, baselineOrigin, bounds);
}
class D2DApi : IPlatformRenderInterfaceContext

70
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
@ -76,6 +75,16 @@ namespace Avalonia.Direct2D1.Media
set => throw new NotSupportedException();
}
public RenderOptions RenderOptions
{
get => _renderOptions;
set
{
_renderOptions = value;
ApplyRenderOptions(value);
}
}
/// <inheritdoc/>
public void Clear(Color color)
{
@ -119,15 +128,14 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{
var interpolationMode = GetInterpolationMode(bitmapInterpolationMode);
var interpolationMode = GetInterpolationMode(RenderOptions.BitmapInterpolationMode);
// TODO: How to implement CompositeMode here?
_deviceContext.DrawBitmap(
d2d.Value,
destRect.ToSharpDX(),
@ -143,13 +151,14 @@ namespace Avalonia.Direct2D1.Media
switch (interpolationMode)
{
case BitmapInterpolationMode.LowQuality:
return InterpolationMode.NearestNeighbor;
case BitmapInterpolationMode.MediumQuality:
return InterpolationMode.Linear;
case BitmapInterpolationMode.MediumQuality:
return InterpolationMode.MultiSampleLinear;
case BitmapInterpolationMode.HighQuality:
return InterpolationMode.HighQualityCubic;
case BitmapInterpolationMode.Default:
return InterpolationMode.Linear;
case BitmapInterpolationMode.None:
case BitmapInterpolationMode.Unspecified:
return InterpolationMode.NearestNeighbor;
default:
throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null);
}
@ -158,15 +167,16 @@ namespace Avalonia.Direct2D1.Media
public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode)
{
switch (blendingMode)
{
{
case BitmapBlendingMode.SourceIn:
return CompositeMode.SourceIn;
case BitmapBlendingMode.SourceOut:
return CompositeMode.SourceOut;
case BitmapBlendingMode.Unspecified:
case BitmapBlendingMode.SourceOver:
return CompositeMode.SourceOver;
case BitmapBlendingMode.SourceAtop:
return CompositeMode.SourceAtop;
return CompositeMode.SourceAtop;
case BitmapBlendingMode.DestinationIn:
return CompositeMode.DestinationIn;
case BitmapBlendingMode.DestinationOut:
@ -193,8 +203,10 @@ namespace Avalonia.Direct2D1.Media
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
var interpolationMode = GetInterpolationMode(RenderOptions.BitmapInterpolationMode);
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var sourceBrush = new BitmapBrush1(_deviceContext, d2dSource.Value, new BitmapBrushProperties1 { InterpolationMode = interpolationMode }))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
@ -388,9 +400,11 @@ namespace Avalonia.Direct2D1.Media
{
using (var brush = CreateBrush(foreground, glyphRun.Item.Bounds.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item;
var immutableGlyphRun = (GlyphRunImpl)glyphRun.Item;
_renderTarget.DrawGlyphRun(glyphRun.Item.BaselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun,
var dxGlyphRun = immutableGlyphRun.GlyphRun;
_renderTarget.DrawGlyphRun(glyphRun.Item.BaselineOrigin.ToSharpDX(), dxGlyphRun,
brush.PlatformBrush, MeasuringMode.Natural);
}
}
@ -433,6 +447,8 @@ namespace Avalonia.Direct2D1.Media
readonly Stack<Layer> _layers = new Stack<Layer>();
private readonly Stack<Layer> _layerPool = new Stack<Layer>();
private RenderOptions _renderOptions;
/// <summary>
/// Pushes an opacity value.
/// </summary>
@ -546,7 +562,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl(
sceneBrushContent.Brush,
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
new D2DBitmapImpl(intermediate.Bitmap.QueryInterface<Bitmap1>()),
destinationSize);
}
@ -608,8 +624,28 @@ namespace Avalonia.Direct2D1.Media
{
PopLayer();
}
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
public object GetFeature(Type t) => null;
private void ApplyRenderOptions(RenderOptions renderOptions)
{
_deviceContext.AntialiasMode = renderOptions.EdgeMode != EdgeMode.Aliased ? AntialiasMode.PerPrimitive : AntialiasMode.Aliased;
switch (renderOptions.TextRenderingMode)
{
case TextRenderingMode.Unspecified:
_deviceContext.TextAntialiasMode = renderOptions.EdgeMode != EdgeMode.Aliased ? TextAntialiasMode.Default : TextAntialiasMode.Aliased;
break;
case TextRenderingMode.Alias:
_deviceContext.TextAntialiasMode = TextAntialiasMode.Aliased;
break;
case TextRenderingMode.Antialias:
_deviceContext.TextAntialiasMode = TextAntialiasMode.Grayscale;
break;
case TextRenderingMode.SubpixelAntialias:
_deviceContext.TextAntialiasMode = TextAntialiasMode.Cleartype;
break;
}
}
}
}

93
src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs

@ -1,31 +1,106 @@
using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using SharpDX.DirectWrite;
#nullable enable
namespace Avalonia.Direct2D1.Media
{
internal class GlyphRunImpl : IGlyphRunImpl
{
public GlyphRunImpl(GlyphRun glyphRun, Size size, Point baselineOrigin)
private readonly GlyphTypefaceImpl _glyphTypefaceImpl;
private readonly short[] _glyphIndices;
private readonly float[] _glyphAdvances;
private readonly GlyphOffset[] _glyphOffsets;
private SharpDX.DirectWrite.GlyphRun? _glyphRun;
public GlyphRunImpl(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
Bounds = new Rect(new Point(baselineOrigin.X, 0), size);
_glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin;
GlyphRun = glyphRun;
Bounds = bounds;
var glyphCount = glyphInfos.Count;
_glyphIndices = new short[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
_glyphIndices[i] = (short)glyphInfos[i].GlyphIndex;
}
_glyphAdvances = new float[glyphCount];
var width = 0.0;
for (var i = 0; i < glyphCount; i++)
{
var advance = glyphInfos[i].GlyphAdvance;
width += advance;
_glyphAdvances[i] = (float)advance;
}
_glyphOffsets = new GlyphOffset[glyphCount];
for (var i = 0; i < glyphCount; i++)
{
var (x, y) = glyphInfos[i].GlyphOffset;
_glyphOffsets[i] = new GlyphOffset
{
AdvanceOffset = (float)x,
AscenderOffset = (float)y
};
}
}
public SharpDX.DirectWrite.GlyphRun GlyphRun
{
get
{
if (_glyphRun != null)
{
return _glyphRun;
}
_glyphRun = new SharpDX.DirectWrite.GlyphRun
{
FontFace = _glyphTypefaceImpl.FontFace,
FontSize = (float)FontRenderingEmSize,
Advances = _glyphAdvances,
Indices = _glyphIndices,
Offsets = _glyphOffsets
};
return _glyphRun;
}
}
public Rect Bounds{ get; }
public IGlyphTypeface GlyphTypeface => _glyphTypefaceImpl;
public double FontRenderingEmSize { get; }
public Point BaselineOrigin { get; }
public GlyphRun GlyphRun { get; }
public Rect Bounds { get; }
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) => Array.Empty<float>();
public void Dispose()
{
//GlyphRun?.Dispose();
}
//_glyphRun?.Dispose();
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
=> Array.Empty<float>();
_glyphRun = null;
}
}
}

9
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -7,9 +7,7 @@ namespace Avalonia.Direct2D1.Media
{
internal sealed class ImageBrushImpl : BrushImpl
{
private readonly OptionalDispose<Bitmap> _bitmap;
private readonly Avalonia.Media.Imaging.BitmapInterpolationMode _bitmapInterpolationMode;
private readonly OptionalDispose<Bitmap1> _bitmap;
public ImageBrushImpl(
ITileBrush brush,
@ -40,8 +38,6 @@ namespace Avalonia.Direct2D1.Media
GetBrushProperties(brush, calc.DestinationRect));
}
}
_bitmapInterpolationMode = brush.BitmapInterpolationMode;
}
public override void Dispose()
@ -103,8 +99,7 @@ namespace Avalonia.Direct2D1.Media
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode);
context.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect);
context.PopClip();
}

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,7 +1,7 @@
using System;
using System.IO;
using Avalonia.Platform;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using D2DBitmap = SharpDX.Direct2D1.Bitmap1;
namespace Avalonia.Direct2D1.Media
{

11
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,7 +1,8 @@
using System;
using System.IO;
using Avalonia.Metadata;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using Bitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
@ -10,7 +11,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
internal class D2DBitmapImpl : BitmapImpl
{
private readonly Bitmap _direct2DBitmap;
private readonly Bitmap1 _direct2DBitmap;
/// <summary>
/// Initialize a new instance of the <see cref="BitmapImpl"/> class
@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap)
public D2DBitmapImpl(Bitmap1 d2DBitmap)
{
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
@ -36,9 +37,9 @@ namespace Avalonia.Direct2D1.Media
_direct2DBitmap.Dispose();
}
public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
public override OptionalDispose<Bitmap1> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<Bitmap>(_direct2DBitmap, false);
return new OptionalDispose<Bitmap1>(_direct2DBitmap, false);
}
public override void Save(Stream stream, int? quality = null)

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -5,7 +5,7 @@ using Avalonia.Rendering;
using Avalonia.Utilities;
using SharpDX;
using SharpDX.Direct2D1;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using D2DBitmap = SharpDX.Direct2D1.Bitmap1;
namespace Avalonia.Direct2D1.Media.Imaging
{
@ -14,7 +14,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
private readonly BitmapRenderTarget _renderTarget;
public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget)
: base(renderTarget.Bitmap)
: base(renderTarget.Bitmap.QueryInterface<Bitmap1>())
{
_renderTarget = renderTarget;
}
@ -53,7 +53,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_renderTarget.Bitmap, false);
return new OptionalDispose<D2DBitmap>(_renderTarget.Bitmap.QueryInterface<Bitmap1>(), false);
}
public override void Save(Stream stream, int? quality = null)

10
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -1,11 +1,10 @@
using System;
using System.IO;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Win32.Interop;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using AlphaFormat = Avalonia.Platform.AlphaFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using D2DBitmap = SharpDX.Direct2D1.Bitmap1;
using Avalonia.Platform;
using PixelFormat = SharpDX.WIC.PixelFormat;
@ -22,7 +21,7 @@ namespace Avalonia.Direct2D1.Media
{
switch (interpolationMode)
{
case Avalonia.Media.Imaging.BitmapInterpolationMode.Default:
case Avalonia.Media.Imaging.BitmapInterpolationMode.Unspecified:
return BitmapInterpolationMode.Fant;
case Avalonia.Media.Imaging.BitmapInterpolationMode.LowQuality:
@ -184,7 +183,10 @@ namespace Avalonia.Direct2D1.Media
{
using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
var d2dBitmap = D2DBitmap.FromWicBitmap(renderTarget, converter).QueryInterface<D2DBitmap>();
return new OptionalDispose<D2DBitmap>(d2dBitmap, true);
}
public override void Save(Stream stream, int? quality = null)

3
tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -51,8 +51,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph
bitmap,
1,
new Rect(1, 1, 1, 1),
new Rect(1, 1, 1, 1),
BitmapInterpolationMode.Default);
new Rect(1, 1, 1, 1));
Assert.Equal(2, bitmap.RefCount);

2
tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs

@ -78,7 +78,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
throw new NotImplementedException();
}

5
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

@ -15,12 +15,13 @@ namespace Avalonia.Benchmarks
public Matrix Transform { get; set; }
public RenderOptions RenderOptions { get; set; }
public void Clear(Color color)
{
}
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
{
}

21
tests/Avalonia.Benchmarks/NullGlyphRun.cs

@ -1,21 +0,0 @@
using System.Collections.Generic;
using Avalonia.Platform;
namespace Avalonia.Benchmarks
{
internal class NullGlyphRun : IGlyphRunImpl
{
public Rect Bounds => default;
public Point BaselineOrigin => default;
public void Dispose()
{
}
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
{
return null;
}
}
}

4
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -124,9 +124,9 @@ namespace Avalonia.Benchmarks
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
return new MockGlyphRun(glyphInfos);
return new MockGlyphRun(glyphTypeface, fontRenderingEmSize, baselineOrigin, bounds);
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)

23
tests/Avalonia.RenderTests/Media/GlyphRunTests.cs

@ -111,6 +111,29 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImages();
}
[Win32Fact("For consistent results")]
public async Task Should_Render_GlyphRun_Aliased()
{
var control = new PositionedGlyphRunControl
{
[TextElement.ForegroundProperty] = new SolidColorBrush { Color = Colors.Black }
};
RenderOptions.SetTextRenderingMode(control, TextRenderingMode.Alias);
Decorator target = new Decorator
{
Padding = new Thickness(8),
Width = 190,
Height = 120,
Child = control
};
await RenderToFile(target);
CompareImages();
}
public class GlyphRunGeometryControl : Control
{
public GlyphRunGeometryControl()

44
tests/Avalonia.RenderTests/Shapes/EllipseTests.cs

@ -35,5 +35,49 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task Should_Render_Circle_Aliased()
{
var target = new Border
{
Background = Brushes.White,
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 3.5,
}
};
RenderOptions.SetEdgeMode(target, EdgeMode.Aliased);
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task Should_Render_Circle_Antialiased()
{
var target = new Border
{
Background = Brushes.White,
Padding = new Thickness(8),
Width = 200,
Height = 200,
Child = new Ellipse
{
Stroke = Brushes.Black,
StrokeThickness = 3.5,
}
};
RenderOptions.SetEdgeMode(target, EdgeMode.Antialias);
await RenderToFile(target);
CompareImages();
}
}
}

25
tests/Avalonia.UnitTests/MockGlyphRun.cs

@ -1,33 +1,34 @@
using System;
using System.Collections.Generic;
using Avalonia.Media.TextFormatting;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.UnitTests
{
public class MockGlyphRun : IGlyphRunImpl
{
public MockGlyphRun(IReadOnlyList<GlyphInfo> glyphInfos)
public MockGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, Point baselineOrigin, Rect bounds)
{
var width = 0.0;
GlyphTypeface = glyphTypeface;
FontRenderingEmSize = fontRenderingEmSize;
BaselineOrigin = baselineOrigin;
Bounds =bounds;
}
for (var i = 0; i < glyphInfos.Count; ++i)
{
width += glyphInfos[i].GlyphAdvance;
}
public IGlyphTypeface GlyphTypeface { get; }
Bounds = new Rect(new Size(width, 10));
}
public double FontRenderingEmSize { get; }
public Rect Bounds { get; }
public Point BaselineOrigin { get; }
public Point BaselineOrigin => new Point(0, 8);
public Rect Bounds { get; }
public void Dispose()
{
}
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound)
public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
=> Array.Empty<float>();
}
}

21
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -5,7 +5,6 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using Avalonia.Rendering;
using Moq;
namespace Avalonia.UnitTests
@ -149,10 +148,9 @@ namespace Avalonia.UnitTests
throw new NotImplementedException();
}
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{
return new MockGlyphRun(glyphInfos);
return new MockGlyphRun(glyphTypeface, fontRenderingEmSize, baselineOrigin, bounds);
}
public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext) => this;
@ -162,21 +160,6 @@ namespace Avalonia.UnitTests
return Mock.Of<IGeometryImpl>();
}
public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IGlyphRunBuffer>();
}
public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IHorizontalGlyphRunBuffer>();
}
public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
{
return Mock.Of<IPositionedGlyphRunBuffer>();
}
public bool SupportsIndividualRoundRects { get; set; }
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;

BIN
tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Aliased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Fill_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 39 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_Fill.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Tile_UniformToFill.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_UniformToFill_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageBrush/ImageBrush_Uniform_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
tests/TestFiles/Direct2D1/Media/ImageDrawing/ImageDrawing_Fill.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Fill_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 39 KiB

BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

After

Width:  |  Height:  |  Size: 199 B

BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_UniformToFill_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Uniform_NoTile.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Ellipse/Should_Render_Circle_Aliased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
tests/TestFiles/Direct2D1/Shapes/Ellipse/Should_Render_Circle_Antialiased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Aliased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
tests/TestFiles/Skia/Shapes/Ellipse/Should_Render_Circle_Aliased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
tests/TestFiles/Skia/Shapes/Ellipse/Should_Render_Circle_Antialiased.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Loading…
Cancel
Save