Browse Source

Merge pull request #9584 from Gillibald/feature/TextRenderingOptions

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

28
Avalonia.sln

@ -262,9 +262,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SafeAreaDemo.iOS", "samples
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headless", "Headless", "{FF237916-7150-496B-89ED-6CA3292896E7}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.XUnit", "src\Headless\Avalonia.Headless.XUnit\Avalonia.Headless.XUnit.csproj", "{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Headless.UnitTests", "tests\Avalonia.Headless.UnitTests\Avalonia.Headless.UnitTests.csproj", "{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -605,14 +605,6 @@ Global
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU {13F1135D-BA1A-435C-9C5B-A368D1D63DE4}.Release|Any CPU.Build.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU {A82AD1BC-EBE6-4FC3-A13B-D52A50297533}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -637,6 +629,14 @@ Global
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Build.0 = Release|Any CPU
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD}.Release|Any CPU.Deploy.0 = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119}.Release|Any CPU.Build.0 = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -681,6 +681,8 @@ Global
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} {4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -704,10 +706,6 @@ Global
{C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {C692FE73-43DB-49CE-87FC-F03ED61F25C9} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{8C89950F-F5D9-47FC-8066-CBC1EC3DF8FC} = {FF237916-7150-496B-89ED-6CA3292896E7}
{B859AE7C-F34F-4A9E-88AE-E0E7229FDE1E} = {FF237916-7150-496B-89ED-6CA3292896E7}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {DDA28789-C21A-4654-86CE-D01E81F095C5} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {2D7C812B-7E73-4252-8EFD-BC8A4D5CCB9F} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098} {A82AD1BC-EBE6-4FC3-A13B-D52A50297533} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -715,6 +713,8 @@ Global
{22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098} {22E3BC08-EAF7-4889-BDC4-B4D3046C4E2D} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098} {4CDAD037-34A2-4CCF-A03A-C6C7B988A572} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FC956F9A-4C3A-4A1A-ACDD-BB54DCB661DD} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F47F8316-4D4B-4026-8EF3-16B2CFDA8119} = {FF237916-7150-496B-89ED-6CA3292896E7}
{3B2405E8-9E7A-46D1-8E2D-EF9ED124C9F2} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
samples/Sandbox/MainWindow.axaml.cs

@ -1,6 +1,7 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Win32.WinRT.Composition; using Avalonia.Win32.WinRT.Composition;

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

@ -1,11 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using System.ComponentModel;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -54,12 +53,10 @@ namespace Avalonia.Media
/// <param name="source">The image.</param> /// <param name="source">The image.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="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)
public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode = default)
{ {
_ = source ?? throw new ArgumentNullException(nameof(source)); _ = source ?? throw new ArgumentNullException(nameof(source));
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode); source.Draw(this, sourceRect, destRect);
} }
/// <summary> /// <summary>
@ -69,8 +66,7 @@ namespace Avalonia.Media
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="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);
internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary> /// <summary>
/// Draws a line. /// Draws a line.
@ -287,8 +283,7 @@ namespace Avalonia.Media
Opacity, Opacity,
Clip, Clip,
GeometryClip, GeometryClip,
OpacityMask, OpacityMask
BitmapBlendMode
} }
public RestoreState(DrawingContext context, PushedStateType type) public RestoreState(DrawingContext context, PushedStateType type)
@ -313,8 +308,6 @@ namespace Avalonia.Media
_context.PopGeometryClipCore(); _context.PopGeometryClipCore();
else if (_type == PushedStateType.OpacityMask) else if (_type == PushedStateType.OpacityMask)
_context.PopOpacityMaskCore(); _context.PopOpacityMaskCore();
else if (_type == PushedStateType.BitmapBlendMode)
_context.PopBitmapBlendModeCore();
} }
} }
@ -395,16 +388,6 @@ namespace Avalonia.Media
} }
protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds); 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> /// <summary>
/// Pushes a matrix transformation. /// Pushes a matrix transformation.
/// </summary> /// </summary>
@ -432,7 +415,6 @@ namespace Avalonia.Media
protected abstract void PopGeometryClipCore(); protected abstract void PopGeometryClipCore();
protected abstract void PopOpacityCore(); protected abstract void PopOpacityCore();
protected abstract void PopOpacityMaskCore(); protected abstract void PopOpacityMaskCore();
protected abstract void PopBitmapBlendModeCore();
protected abstract void PopTransformCore(); protected abstract void PopTransformCore();
private static bool PenIsVisible(IPen? pen) private static bool PenIsVisible(IPen? pen)

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

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

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

@ -1,6 +1,5 @@
using System; using System;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -43,8 +42,7 @@ namespace Avalonia.Media
void IImage.Draw( void IImage.Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
var drawing = Drawing; 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> /// <summary>
/// Gets the conservative bounding box of the <see cref="GlyphRun"/>. /// Gets the conservative bounding box of the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public Rect Bounds => PlatformImpl.Item.Bounds; public Rect Bounds => new Rect(new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height));
/// <summary> /// <summary>
/// ///
@ -166,7 +166,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public Point BaselineOrigin public Point BaselineOrigin
{ {
get => PlatformImpl.Item.BaselineOrigin; get => _baselineOrigin ?? new Point(0, Metrics.Baseline);
set => Set(ref _baselineOrigin, value); set => Set(ref _baselineOrigin, value);
} }
@ -676,13 +676,17 @@ namespace Avalonia.Media
} }
} }
return new GlyphRunMetrics( return new GlyphRunMetrics
width, {
trailingWhitespaceLength, Baseline = -GlyphTypeface.Metrics.Ascent * Scale,
newLineLength, Width = width,
firstCluster, WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace,
lastCluster Height = height,
); NewLineLength = newLineLength,
TrailingWhitespaceLength = trailingWhitespaceLength,
FirstCluster = firstCluster,
LastCluster = lastCluster
};
} }
private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount) private int GetTrailingWhitespaceLength(bool isReversed, out int newLineLength, out int glyphCount)
@ -820,10 +824,11 @@ namespace Avalonia.Media
private IRef<IGlyphRunImpl> CreateGlyphRunImpl() private IRef<IGlyphRunImpl> CreateGlyphRunImpl()
{ {
var platformImpl = s_renderInterface.CreateGlyphRun( var platformImpl = s_renderInterface.CreateGlyphRun(
GlyphTypeface, GlyphTypeface,
FontRenderingEmSize, FontRenderingEmSize,
GlyphInfos, GlyphInfos,
_baselineOrigin ?? new Point(0, -GlyphTypeface.Metrics.Ascent * Scale)); BaselineOrigin,
Bounds);
_platformImpl = RefCountable.Create(platformImpl); _platformImpl = RefCountable.Create(platformImpl);
@ -835,5 +840,16 @@ namespace Avalonia.Media
_platformImpl?.Dispose(); _platformImpl?.Dispose();
_platformImpl = null; _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 readonly record struct GlyphRunMetrics
{ {
public GlyphRunMetrics(double width, int trailingWhitespaceLength, int newLineLength, int firstCluster, int lastCluster) public double Baseline { get; init; }
{
Width = width;
TrailingWhitespaceLength = trailingWhitespaceLength;
NewLineLength = newLineLength;
FirstCluster = firstCluster;
LastCluster = lastCluster;
}
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="context">The drawing context.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void Draw( void Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect);
BitmapInterpolationMode bitmapInterpolationMode);
} }
} }

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

@ -39,13 +39,5 @@ namespace Avalonia.Media
/// Gets the brush's tile mode. /// Gets the brush's tile mode.
/// </summary> /// </summary>
TileMode TileMode { get; } 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( void IImage.Draw(
DrawingContext context, DrawingContext context,
Rect sourceRect, Rect sourceRect,
Rect destRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
context.DrawBitmap( context.DrawBitmap(
PlatformImpl, PlatformImpl,
1, 1,
sourceRect, sourceRect,
destRect, destRect);
bitmapInterpolationMode);
} }
private static IPlatformRenderInterface GetFactory() private static IPlatformRenderInterface GetFactory()

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

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

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

@ -3,12 +3,14 @@
/// <summary> /// <summary>
/// Controls the performance and quality of bitmap scaling. /// Controls the performance and quality of bitmap scaling.
/// </summary> /// </summary>
public enum BitmapInterpolationMode public enum BitmapInterpolationMode : byte
{ {
Unspecified,
/// <summary> /// <summary>
/// Uses the default behavior of the underling render backend. /// Disable interpolation.
/// </summary> /// </summary>
Default, None,
/// <summary> /// <summary>
/// The best performance but worst image quality. /// The best performance but worst image quality.
@ -18,7 +20,7 @@
/// <summary> /// <summary>
/// Good performance and decent image quality. /// Good performance and decent image quality.
/// </summary> /// </summary>
MediumQuality, MediumQuality,
/// <summary> /// <summary>
/// Highest quality but worst performance. /// 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) if (Source is not IBitmap bmp)
return; return;
var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi); 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

@ -79,11 +79,10 @@ namespace Avalonia.Media
/// <param name="source">The bitmap.</param> /// <param name="source">The bitmap.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="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)
public void DrawBitmap(IBitmap source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
{ {
_ = source ?? throw new ArgumentNullException(nameof(source)); _ = source ?? throw new ArgumentNullException(nameof(source));
PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect, bitmapInterpolationMode); PlatformImpl.DrawBitmap(source.PlatformImpl, 1, sourceRect, destRect);
} }
/// <summary> /// <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. /// How the source rectangle will be stretched to fill the destination rect.
/// </param> /// </param>
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public ImmutableImageBrush( public ImmutableImageBrush(
IBitmap? source, IBitmap? source,
AlignmentX alignmentX = AlignmentX.Center, AlignmentX alignmentX = AlignmentX.Center,
@ -33,8 +32,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin = default, RelativePoint transformOrigin = default,
RelativeRect? sourceRect = null, RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform, Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None, TileMode tileMode = TileMode.None)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
: base( : base(
alignmentX, alignmentX,
alignmentY, alignmentY,
@ -44,8 +42,7 @@ namespace Avalonia.Media.Immutable
transformOrigin, transformOrigin,
sourceRect ?? RelativeRect.Fill, sourceRect ?? RelativeRect.Fill,
stretch, stretch,
tileMode, tileMode)
bitmapInterpolationMode)
{ {
Source = source; 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. /// How the source rectangle will be stretched to fill the destination rect.
/// </param> /// </param>
/// <param name="tileMode">The tile mode.</param> /// <param name="tileMode">The tile mode.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
protected ImmutableTileBrush( protected ImmutableTileBrush(
AlignmentX alignmentX, AlignmentX alignmentX,
AlignmentY alignmentY, AlignmentY alignmentY,
@ -31,8 +30,7 @@ namespace Avalonia.Media.Immutable
RelativePoint transformOrigin, RelativePoint transformOrigin,
RelativeRect sourceRect, RelativeRect sourceRect,
Stretch stretch, Stretch stretch,
TileMode tileMode, TileMode tileMode)
BitmapInterpolationMode bitmapInterpolationMode)
{ {
AlignmentX = alignmentX; AlignmentX = alignmentX;
AlignmentY = alignmentY; AlignmentY = alignmentY;
@ -43,7 +41,6 @@ namespace Avalonia.Media.Immutable
SourceRect = sourceRect; SourceRect = sourceRect;
Stretch = stretch; Stretch = stretch;
TileMode = tileMode; TileMode = tileMode;
BitmapInterpolationMode = bitmapInterpolationMode;
} }
/// <summary> /// <summary>
@ -60,8 +57,7 @@ namespace Avalonia.Media.Immutable
source.TransformOrigin, source.TransformOrigin,
source.SourceRect, source.SourceRect,
source.Stretch, source.Stretch,
source.TileMode, source.TileMode)
source.BitmapInterpolationMode)
{ {
} }
@ -95,8 +91,5 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/> /// <inheritdoc/>
public TileMode TileMode { get; } public TileMode TileMode { get; }
/// <inheritdoc/>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
} }
} }

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

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

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

@ -1,36 +1,131 @@
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
namespace Avalonia.Media 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> /// <summary>
/// Defines the <see cref="BitmapInterpolationMode"/> property. /// Sets the value of the BitmapInterpolationMode attached property for a visual.
/// </summary> /// </summary>
public static readonly StyledProperty<BitmapInterpolationMode> BitmapInterpolationModeProperty = /// <param name="visual">The control.</param>
AvaloniaProperty.RegisterAttached<RenderOptions, AvaloniaObject, BitmapInterpolationMode>( /// <param name="value">The left value.</param>
"BitmapInterpolationMode", public static void SetBitmapInterpolationMode(Visual visual, BitmapInterpolationMode value)
BitmapInterpolationMode.MediumQuality, {
inherits: true); visual.RenderOptions = visual.RenderOptions with { BitmapInterpolationMode = value };
}
/// <summary> /// <summary>
/// Gets the value of the BitmapInterpolationMode attached property for a control. /// Gets the value of the BitmapBlendingMode attached property for a visual.
/// </summary> /// </summary>
/// <param name="element">The control.</param> /// <param name="visual">The control.</param>
/// <returns>The control's left coordinate.</returns> /// <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> /// <summary>
/// Sets the value of the BitmapInterpolationMode attached property for a control. /// Sets the value of the BitmapBlendingMode attached property for a visual.
/// </summary> /// </summary>
/// <param name="element">The control.</param> /// <param name="visual">The control.</param>
/// <param name="value">The left value.</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;
using Avalonia.Collections.Pooled;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Media namespace Avalonia.Media
{ {
@ -218,7 +214,7 @@ namespace Avalonia.Media
{ {
var offsetY = glyphRun.BaselineOrigin.Y - origin.Y; 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) 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, SourceRectProperty,
StretchProperty, StretchProperty,
TileModeProperty); TileModeProperty);
RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue<TileBrush>(BitmapInterpolationMode.Default);
} }
/// <summary> /// <summary>
@ -140,17 +139,5 @@ namespace Avalonia.Media
get { return (TileMode)GetValue(TileModeProperty); } get { return (TileMode)GetValue(TileModeProperty); }
set { SetValue(TileModeProperty, value); } 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); }
}
} }
} }

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

@ -1,8 +1,8 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Media.Imaging;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
@ -12,6 +12,11 @@ namespace Avalonia.Platform
[Unstable] [Unstable]
public interface IDrawingContextImpl : IDisposable 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> /// <summary>
/// Gets or sets the current transform of the drawing context. /// Gets or sets the current transform of the drawing context.
/// </summary> /// </summary>
@ -30,8 +35,7 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="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);
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary> /// <summary>
/// Draws a bitmap image. /// Draws a bitmap image.
@ -157,15 +161,6 @@ namespace Avalonia.Platform
void PopGeometryClip(); void PopGeometryClip();
/// <summary> /// <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> /// <summary>
/// Attempts to get an optional feature from the drawing context implementation /// Attempts to get an optional feature from the drawing context implementation

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;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
/// <summary> /// <summary>
/// Actual implementation of a glyph run that stores platform dependent resources. /// An immutable platform representation of a <see cref="GlyphRun"/>.
/// </summary> /// </summary>
[Unstable] [Unstable]
public interface IGlyphRunImpl : IDisposable public interface IGlyphRunImpl : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the conservative bounding box of the glyph run./>. /// Gets the <see cref="IGlyphTypeface"/> for the <see cref="IGlyphRunImpl"/>.
/// </summary> /// </summary>
Rect Bounds { get; } IGlyphTypeface GlyphTypeface { get; }
/// <summary>
/// Gets the em size used for rendering the <see cref="IGlyphRunImpl"/>.
/// </summary>
double FontRenderingEmSize { get; }
/// <summary> /// <summary>
/// Gets the baseline origin of the glyph run./>. /// Gets the baseline origin of the glyph run./>.
/// </summary> /// </summary>
Point BaselineOrigin { get; } Point BaselineOrigin { get; }
/// <summary>
/// Gets the conservative bounding box of the glyph run./>.
/// </summary>
Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets the intersections of specified upper and lower limit. /// Gets the intersections of specified upper and lower limit.
/// </summary> /// </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="fontRenderingEmSize">The font rendering em size.</param>
/// <param name="glyphInfos">The list of glyphs.</param> /// <param name="glyphInfos">The list of glyphs.</param>
/// <param name="baselineOrigin">The baseline origin of the run. Can be null.</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> /// <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> /// <summary>
/// Creates a backend-specific object using a low-level API graphics context /// 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)) if (!comp.Effect.EffectEquals(visual.Effect))
comp.Effect = visual.Effect?.ToImmutable(); comp.Effect = visual.Effect?.ToImmutable();
comp.RenderOptions = visual.RenderOptions;
var renderTransform = Matrix.Identity; var renderTransform = Matrix.Identity;
if (visual.HasMirrorTransform) if (visual.HasMirrorTransform)
@ -272,8 +274,6 @@ public class CompositingRenderer : IRendererWithCompositor
renderTransform *= (-offset) * visual.RenderTransform.Value * (offset); renderTransform *= (-offset) * visual.RenderTransform.Value * (offset);
} }
comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform); comp.TransformMatrix = MatrixUtils.ToMatrix4x4(renderTransform);
_recorder.BeginUpdate(comp.DrawList); _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, internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
var next = NextDrawAs<ImageNode>(); var next = NextDrawAs<ImageNode>();
if (next == null || 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 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() protected override void PopOpacityCore()
{ {
var next = NextDrawAs<OpacityNode>(); var next = NextDrawAs<OpacityNode>();
@ -354,21 +339,6 @@ internal sealed class CompositionDrawingContext : DrawingContext, IDrawingContex
_needsToPopOpacityMask.Push(needsToPop); _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 private void Add<T>(T node) where T : class, IDrawOperation
{ {
if (_drawOperationIndex < _builder.Count) 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; set => _impl.Transform = (_transform = value) * PostTransform;
} }
public RenderOptions RenderOptions
{
get => _impl.RenderOptions;
set => _impl.RenderOptions = value;
}
public void Clear(Color color) public void Clear(Color color)
{ {
_impl.Clear(color); _impl.Clear(color);
} }
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{ {
_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) public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
@ -133,16 +138,6 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl,
_impl.PopGeometryClip(); _impl.PopGeometryClip();
} }
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_impl.PushBitmapBlendMode(blendingMode);
}
public void PopBitmapBlendMode()
{
_impl.PopBitmapBlendMode();
}
public object? GetFeature(Type t) => _impl.GetFeature(t); public object? GetFeature(Type t) => _impl.GetFeature(t);

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

@ -181,7 +181,7 @@ namespace Avalonia.Rendering.Composition.Server
else else
targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1, targetContext.DrawBitmap(RefCountable.CreateUnownedNotClonable(_layer), 1,
new Rect(_layerSize), new Rect(_layerSize),
new Rect(Size), BitmapInterpolationMode.LowQuality); new Rect(Size));
if (DebugOverlays != RendererDebugOverlays.None) 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) if (OpacityMaskBrush != null)
canvas.PushOpacityMask(OpacityMaskBrush, boundsRect); canvas.PushOpacityMask(OpacityMaskBrush, boundsRect);
canvas.RenderOptions = RenderOptions;
RenderCore(canvas, currentTransformedClip); RenderCore(canvas, currentTransformedClip);
// Hack to force invalidation of SKMatrix // Hack to force invalidation of SKMatrix
@ -122,6 +124,11 @@ namespace Avalonia.Rendering.Composition.Server
var wasVisible = IsVisibleInFrame; var wasVisible = IsVisibleInFrame;
if(Parent != null)
{
RenderOptions = RenderOptions.MergeWith(Parent.RenderOptions);
}
// Calculate new parent-relative transform // Calculate new parent-relative transform
if (_combinedTransformDirty) if (_combinedTransformDirty)
{ {

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

@ -44,79 +44,99 @@ namespace Avalonia.Rendering
public static void Render(DrawingContext context, Visual visual, Rect clipRect) public static void Render(DrawingContext context, Visual visual, Rect clipRect)
{ {
var opacity = visual.Opacity; var currentRenderOptions = default(RenderOptions);
var clipToBounds = visual.ClipToBounds; var platformContext = context as PlatformDrawingContext;
var bounds = new Rect(visual.Bounds.Size);
if (visual.IsVisible && opacity > 0) try
{ {
var m = Matrix.CreateTranslation(visual.Bounds.Position); if (platformContext != null)
var renderTransform = Matrix.Identity;
// this should be calculated BEFORE renderTransform
if (visual.HasMirrorTransform)
{ {
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); currentRenderOptions = platformContext.RenderOptions;
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null) platformContext.RenderOptions = visual.RenderOptions.MergeWith(platformContext.RenderOptions);
{
var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * (offset);
renderTransform *= finalTransform;
} }
m = renderTransform * m; var opacity = visual.Opacity;
var clipToBounds = visual.ClipToBounds;
var bounds = new Rect(visual.Bounds.Size);
if (clipToBounds) if (visual.IsVisible && opacity > 0)
{ {
var m = Matrix.CreateTranslation(visual.Bounds.Position);
var renderTransform = Matrix.Identity;
// this should be calculated BEFORE renderTransform
if (visual.HasMirrorTransform)
{
var mirrorMatrix = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0);
renderTransform *= mirrorMatrix;
}
if (visual.RenderTransform != null) if (visual.RenderTransform != null)
{ {
clipRect = new Rect(visual.Bounds.Size); var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin);
var finalTransform = (-offset) * visual.RenderTransform.Value * (offset);
renderTransform *= finalTransform;
} }
else
m = renderTransform * m;
if (clipToBounds)
{ {
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size)); if (visual.RenderTransform != null)
{
clipRect = new Rect(visual.Bounds.Size);
}
else
{
clipRect = clipRect.Intersect(new Rect(visual.Bounds.Size));
}
} }
}
using (context.PushTransform(m)) using (context.PushTransform(m))
using (context.PushOpacity(opacity, bounds)) using (context.PushOpacity(opacity, bounds))
using (clipToBounds using (clipToBounds
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
? visual is IVisualWithRoundRectClip roundClipVisual ? visual is IVisualWithRoundRectClip roundClipVisual
? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius))
: context.PushClip(bounds) : context.PushClip(bounds)
: default) : default)
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default)
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default)
using (context.PushTransform(Matrix.Identity)) using (context.PushTransform(Matrix.Identity))
{
visual.Render(context);
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable<Visual>)visual.VisualChildren;
foreach (var child in childrenEnumerable)
{ {
var childBounds = GetTransformedBounds(child); visual.Render(context);
var childrenEnumerable = visual.HasNonUniformZIndexChildren
? visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance)
: (IEnumerable<Visual>)visual.VisualChildren;
if (!child.ClipToBounds || clipRect.Intersects(childBounds)) foreach (var child in childrenEnumerable)
{ {
var childClipRect = child.RenderTransform == null var childBounds = GetTransformedBounds(child);
? clipRect.Translate(-childBounds.Position)
: clipRect; if (!child.ClipToBounds || clipRect.Intersects(childBounds))
Render(context, child, childClipRect); {
} var childClipRect = child.RenderTransform == null
? clipRect.Translate(-childBounds.Position)
: clipRect;
Render(context, child, childClipRect);
}
}
} }
} }
} }
finally
{
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.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -18,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="glyphRun">The glyph run to draw.</param> /// <param name="glyphRun">The glyph run to draw.</param>
public GlyphRunNode( public GlyphRunNode(
Matrix transform, Matrix transform,
IImmutableBrush foreground, IImmutableBrush? foreground,
IRef<IGlyphRunImpl> glyphRun) IRef<IGlyphRunImpl> glyphRun)
: base(glyphRun.Item.Bounds, transform, foreground) : base(glyphRun.Item.Bounds, transform, foreground)
{ {

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

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

4
src/Avalonia.Base/Visual.cs

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

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

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

4
src/Avalonia.Controls/Image.cs

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

13
src/Headless/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -2,11 +2,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Numerics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
@ -127,7 +125,8 @@ namespace Avalonia.Headless
IGlyphTypeface glyphTypeface, IGlyphTypeface glyphTypeface,
double fontRenderingEmSize, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin) Point baselineOrigin,
Rect bounds)
{ {
return new HeadlessGlyphRunStub(); return new HeadlessGlyphRunStub();
} }
@ -138,6 +137,10 @@ namespace Avalonia.Headless
public Point BaselineOrigin => new Point(0, 8); public Point BaselineOrigin => new Point(0, 8);
public IGlyphTypeface GlyphTypeface => new HeadlessGlyphTypefaceImpl();
public double FontRenderingEmSize => 12;
public void Dispose() public void Dispose()
{ {
} }
@ -374,6 +377,8 @@ namespace Avalonia.Headless
public Matrix Transform { get; set; } public Matrix Transform { get; set; }
public RenderOptions RenderOptions { get; set; }
public void Clear(Color color) public void Clear(Color color)
{ {
@ -451,7 +456,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)
{ {
} }

39
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -24,10 +24,8 @@ namespace Avalonia.Skia
private readonly Vector _dpi; private readonly Vector _dpi;
private readonly Stack<PaintWrapper> _maskStack = new(); private readonly Stack<PaintWrapper> _maskStack = new();
private readonly Stack<double> _opacityStack = new(); private readonly Stack<double> _opacityStack = new();
private readonly Stack<BitmapBlendingMode> _blendingModeStack = new();
private readonly Matrix? _postTransform; private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f; private double _currentOpacity = 1.0f;
private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver;
private readonly bool _canTextUseLcdRendering; private readonly bool _canTextUseLcdRendering;
private Matrix _currentTransform; private Matrix _currentTransform;
private bool _disposed; private bool _disposed;
@ -168,6 +166,8 @@ namespace Avalonia.Skia
public SKCanvas Canvas { get; } public SKCanvas Canvas { get; }
public SKSurface? Surface { get; } public SKSurface? Surface { get; }
public RenderOptions RenderOptions { get; set; }
private void CheckLease() private void CheckLease()
{ {
if (_leased) if (_leased)
@ -182,7 +182,7 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <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(); CheckLease();
var drawableImage = (IDrawableBitmapImpl)source.Item; var drawableImage = (IDrawableBitmapImpl)source.Item;
@ -191,8 +191,8 @@ namespace Avalonia.Skia
var paint = SKPaintCache.Shared.Get(); var paint = SKPaintCache.Shared.Get();
paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity))); paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity * (_useOpacitySaveLayer ? 1 : _currentOpacity)));
paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); paint.FilterQuality = RenderOptions.BitmapInterpolationMode.ToSKFilterQuality();
paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); paint.BlendMode = RenderOptions.BitmapBlendingMode.ToSKBlendMode();
drawableImage.Draw(this, s, d, paint); drawableImage.Draw(this, s, d, paint);
SKPaintCache.Shared.ReturnReset(paint); SKPaintCache.Shared.ReturnReset(paint);
@ -203,7 +203,7 @@ namespace Avalonia.Skia
{ {
CheckLease(); CheckLease();
PushOpacityMask(opacityMask, opacityMaskRect); 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(); PopOpacityMask();
} }
@ -519,7 +519,9 @@ namespace Avalonia.Skia
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; 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); (float)glyphRun.Item.BaselineOrigin.Y, paintWrapper.Paint);
} }
} }
@ -649,21 +651,6 @@ namespace Avalonia.Skia
Canvas.Restore(); Canvas.Restore();
} }
/// <inheritdoc />
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
CheckLease();
_blendingModeStack.Push(_currentBlendingMode);
_currentBlendingMode = blendingMode;
}
/// <inheritdoc />
public void PopBitmapBlendMode()
{
CheckLease();
_currentBlendingMode = _blendingModeStack.Pop();
}
/// <inheritdoc /> /// <inheritdoc />
public void PushOpacityMask(IBrush mask, Rect bounds) public void PushOpacityMask(IBrush mask, Rect bounds)
{ {
@ -905,12 +892,14 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform; context.Transform = calc.IntermediateTransform;
context.RenderOptions = RenderOptions;
context.DrawBitmap( context.DrawBitmap(
RefCountable.CreateUnownedNotClonable(tileBrushImage), RefCountable.CreateUnownedNotClonable(tileBrushImage),
1, 1,
sourceRect, sourceRect,
targetRect, targetRect);
tileBrush.BitmapInterpolationMode);
context.PopClip(); context.PopClip();
} }
@ -1143,7 +1132,7 @@ namespace Avalonia.Skia
{ {
var paintWrapper = new PaintWrapper(paint); var paintWrapper = new PaintWrapper(paint);
paint.IsAntialias = true; paint.IsAntialias = RenderOptions.EdgeMode != EdgeMode.Aliased;
double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity); double opacity = brush.Opacity * (_useOpacitySaveLayer ? 1 :_currentOpacity);

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

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform; using Avalonia.Platform;
using SkiaSharp; using SkiaSharp;
@ -7,30 +9,122 @@ namespace Avalonia.Skia
{ {
internal class GlyphRunImpl : IGlyphRunImpl 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; BaselineOrigin = baselineOrigin;
Bounds = bounds;
} }
/// <summary> public IGlyphTypeface GlyphTypeface => _glyphTypefaceImpl;
/// Gets the text blob to draw.
/// </summary>
public SKTextBlob TextBlob { get; }
public Rect Bounds { get; } public double FontRenderingEmSize { get; }
public Point BaselineOrigin { get; } public Point BaselineOrigin { get; }
public IReadOnlyList<float> GetIntersections(float upperBound, float lowerBound) => public Rect Bounds { get; }
TextBlob.GetIntercepts(lowerBound, upperBound);
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.Platform;
using Avalonia.Rendering;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {

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

@ -5,8 +5,8 @@ using Avalonia.Media;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Media.TextFormatting;
using SkiaSharp; using SkiaSharp;
using Avalonia.Media.TextFormatting;
namespace Avalonia.Skia namespace Avalonia.Skia
{ {
@ -209,67 +209,10 @@ namespace Avalonia.Skia
return new WriteableBitmapImpl(size, dpi, format, alphaFormat); return new WriteableBitmapImpl(size, dpi, format, alphaFormat);
} }
public IGlyphRunImpl CreateGlyphRun( public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IGlyphTypeface glyphTypeface, IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos,
Point baselineOrigin)
{ {
if (glyphTypeface == null) return new GlyphRunImpl(glyphTypeface, fontRenderingEmSize, glyphInfos, baselineOrigin, bounds);
{
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);
} }
} }
} }

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

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

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

@ -160,59 +160,9 @@ namespace Avalonia.Direct2D1
public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl(); public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList<IGeometryImpl> children) => new GeometryGroupImpl(fillRule, children); 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 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, Rect bounds)
public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin)
{ {
var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; return new GlyphRunImpl(glyphTypeface, fontRenderingEmSize, glyphInfos, baselineOrigin, bounds);
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);
} }
class D2DApi : IPlatformRenderInterfaceContext class D2DApi : IPlatformRenderInterfaceContext

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

@ -74,6 +74,16 @@ namespace Avalonia.Direct2D1.Media
set => throw new NotSupportedException(); set => throw new NotSupportedException();
} }
public RenderOptions RenderOptions
{
get => _renderOptions;
set
{
_renderOptions = value;
ApplyRenderOptions(value);
}
}
/// <inheritdoc/> /// <inheritdoc/>
public void Clear(Color color) public void Clear(Color color)
{ {
@ -117,15 +127,14 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacity">The opacity to draw with.</param> /// <param name="opacity">The opacity to draw with.</param>
/// <param name="sourceRect">The rect in the image to draw.</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="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)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{ {
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{ {
var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); var interpolationMode = GetInterpolationMode(RenderOptions.BitmapInterpolationMode);
// TODO: How to implement CompositeMode here? // TODO: How to implement CompositeMode here?
_deviceContext.DrawBitmap( _deviceContext.DrawBitmap(
d2d.Value, d2d.Value,
destRect.ToSharpDX(), destRect.ToSharpDX(),
@ -141,13 +150,14 @@ namespace Avalonia.Direct2D1.Media
switch (interpolationMode) switch (interpolationMode)
{ {
case BitmapInterpolationMode.LowQuality: case BitmapInterpolationMode.LowQuality:
return InterpolationMode.NearestNeighbor;
case BitmapInterpolationMode.MediumQuality:
return InterpolationMode.Linear; return InterpolationMode.Linear;
case BitmapInterpolationMode.MediumQuality:
return InterpolationMode.MultiSampleLinear;
case BitmapInterpolationMode.HighQuality: case BitmapInterpolationMode.HighQuality:
return InterpolationMode.HighQualityCubic; return InterpolationMode.HighQualityCubic;
case BitmapInterpolationMode.Default: case BitmapInterpolationMode.None:
return InterpolationMode.Linear; case BitmapInterpolationMode.Unspecified:
return InterpolationMode.NearestNeighbor;
default: default:
throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null); throw new ArgumentOutOfRangeException(nameof(interpolationMode), interpolationMode, null);
} }
@ -156,15 +166,16 @@ namespace Avalonia.Direct2D1.Media
public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode) public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode)
{ {
switch (blendingMode) switch (blendingMode)
{ {
case BitmapBlendingMode.SourceIn: case BitmapBlendingMode.SourceIn:
return CompositeMode.SourceIn; return CompositeMode.SourceIn;
case BitmapBlendingMode.SourceOut: case BitmapBlendingMode.SourceOut:
return CompositeMode.SourceOut; return CompositeMode.SourceOut;
case BitmapBlendingMode.Unspecified:
case BitmapBlendingMode.SourceOver: case BitmapBlendingMode.SourceOver:
return CompositeMode.SourceOver; return CompositeMode.SourceOver;
case BitmapBlendingMode.SourceAtop: case BitmapBlendingMode.SourceAtop:
return CompositeMode.SourceAtop; return CompositeMode.SourceAtop;
case BitmapBlendingMode.DestinationIn: case BitmapBlendingMode.DestinationIn:
return CompositeMode.DestinationIn; return CompositeMode.DestinationIn;
case BitmapBlendingMode.DestinationOut: case BitmapBlendingMode.DestinationOut:
@ -191,8 +202,10 @@ namespace Avalonia.Direct2D1.Media
/// <param name="destRect">The rect in the output to draw to.</param> /// <param name="destRect">The rect in the output to draw to.</param>
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) 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 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 d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D())) using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{ {
@ -386,9 +399,11 @@ namespace Avalonia.Direct2D1.Media
{ {
using (var brush = CreateBrush(foreground, glyphRun.Item.Bounds.Size)) 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); brush.PlatformBrush, MeasuringMode.Natural);
} }
} }
@ -431,6 +446,8 @@ namespace Avalonia.Direct2D1.Media
readonly Stack<Layer> _layers = new Stack<Layer>(); readonly Stack<Layer> _layers = new Stack<Layer>();
private readonly Stack<Layer> _layerPool = new Stack<Layer>(); private readonly Stack<Layer> _layerPool = new Stack<Layer>();
private RenderOptions _renderOptions;
/// <summary> /// <summary>
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
@ -544,7 +561,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl( return new ImageBrushImpl(
sceneBrushContent.Brush, sceneBrushContent.Brush,
_deviceContext, _deviceContext,
new D2DBitmapImpl(intermediate.Bitmap), new D2DBitmapImpl(intermediate.Bitmap.QueryInterface<Bitmap1>()),
destinationSize); destinationSize);
} }
@ -608,5 +625,25 @@ namespace Avalonia.Direct2D1.Media
} }
public object GetFeature(Type t) => null; 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;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.DirectWrite; using SharpDX.DirectWrite;
#nullable enable
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
internal class GlyphRunImpl : IGlyphRunImpl 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; 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 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() public void Dispose()
{ {
//GlyphRun?.Dispose(); //_glyphRun?.Dispose();
}
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) _glyphRun = null;
=> Array.Empty<float>(); }
} }
} }

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

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

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

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

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

@ -1,7 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using Avalonia.Metadata;
using SharpDX.Direct2D1;
using SharpDX.WIC; using SharpDX.WIC;
using Bitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -10,7 +11,7 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
internal class D2DBitmapImpl : BitmapImpl internal class D2DBitmapImpl : BitmapImpl
{ {
private readonly Bitmap _direct2DBitmap; private readonly Bitmap1 _direct2DBitmap;
/// <summary> /// <summary>
/// Initialize a new instance of the <see cref="BitmapImpl"/> class /// 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"/>, /// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable. /// the device associated with this context, to be renderable.
/// </remarks> /// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap) public D2DBitmapImpl(Bitmap1 d2DBitmap)
{ {
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
} }
@ -36,9 +37,9 @@ namespace Avalonia.Direct2D1.Media
_direct2DBitmap.Dispose(); _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) 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 Avalonia.Utilities;
using SharpDX; using SharpDX;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using D2DBitmap = SharpDX.Direct2D1.Bitmap; using D2DBitmap = SharpDX.Direct2D1.Bitmap1;
namespace Avalonia.Direct2D1.Media.Imaging namespace Avalonia.Direct2D1.Media.Imaging
{ {
@ -14,7 +14,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
private readonly BitmapRenderTarget _renderTarget; private readonly BitmapRenderTarget _renderTarget;
public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget) public D2DRenderTargetBitmapImpl(BitmapRenderTarget renderTarget)
: base(renderTarget.Bitmap) : base(renderTarget.Bitmap.QueryInterface<Bitmap1>())
{ {
_renderTarget = renderTarget; _renderTarget = renderTarget;
} }
@ -53,7 +53,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) 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) 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;
using System.IO; using System.IO;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using SharpDX.WIC; using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat; using APixelFormat = Avalonia.Platform.PixelFormat;
using AlphaFormat = Avalonia.Platform.AlphaFormat; using AlphaFormat = Avalonia.Platform.AlphaFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap; using D2DBitmap = SharpDX.Direct2D1.Bitmap1;
using Avalonia.Platform; using Avalonia.Platform;
using PixelFormat = SharpDX.WIC.PixelFormat; using PixelFormat = SharpDX.WIC.PixelFormat;
@ -22,7 +21,7 @@ namespace Avalonia.Direct2D1.Media
{ {
switch (interpolationMode) switch (interpolationMode)
{ {
case Avalonia.Media.Imaging.BitmapInterpolationMode.Default: case Avalonia.Media.Imaging.BitmapInterpolationMode.Unspecified:
return BitmapInterpolationMode.Fant; return BitmapInterpolationMode.Fant;
case Avalonia.Media.Imaging.BitmapInterpolationMode.LowQuality: case Avalonia.Media.Imaging.BitmapInterpolationMode.LowQuality:
@ -184,7 +183,10 @@ namespace Avalonia.Direct2D1.Media
{ {
using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory); using var converter = new FormatConverter(Direct2D1Platform.ImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA); 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) 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, bitmap,
1, 1,
new Rect(1, 1, 1, 1), new Rect(1, 1, 1, 1),
new Rect(1, 1, 1, 1), new Rect(1, 1, 1, 1));
BitmapInterpolationMode.Default);
Assert.Equal(2, bitmap.RefCount); 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, public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize,
IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin) IReadOnlyList<GlyphInfo> glyphInfos, Point baselineOrigin, Rect bounds)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

5
tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs

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

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, 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) public IPlatformRenderInterfaceContext CreateBackendContext(IPlatformGraphicsContext graphicsContext)

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

@ -111,6 +111,29 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImages(); 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 class GlyphRunGeometryControl : Control
{ {
public GlyphRunGeometryControl() public GlyphRunGeometryControl()

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

@ -35,5 +35,49 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
await RenderToFile(target); await RenderToFile(target);
CompareImages(); 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;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media.TextFormatting; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.UnitTests namespace Avalonia.UnitTests
{ {
public class MockGlyphRun : IGlyphRunImpl 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) public IGlyphTypeface GlyphTypeface { get; }
{
width += glyphInfos[i].GlyphAdvance;
}
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 void Dispose()
{ {
} }
public IReadOnlyList<float> GetIntersections(float lowerBound, float upperBound) public IReadOnlyList<float> GetIntersections(float lowerLimit, float upperLimit)
=> Array.Empty<float>(); => Array.Empty<float>();
} }
} }

21
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

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