Browse Source

Merge pull request #2115 from AvaloniaUI/fixes/1614-tile-brush-dpi-compatibility

Fix tilebrush scaling
pull/2142/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
bf0baa64db
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      src/Avalonia.Visuals/Media/PixelSize.cs
  2. 4
      src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs
  3. 48
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  4. 8
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  5. 6
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  6. 19
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  7. 13
      src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs
  8. 276
      tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
  9. 8
      tests/Avalonia.RenderTests/TestBase.cs
  10. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png
  11. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_192_Dpi.expected.png
  12. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png
  13. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png
  14. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png
  15. BIN
      tests/TestFiles/Direct2D1/Media/VisualBrush/VisualBrush_Grip_96_Dpi.expected.png
  16. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png
  17. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_192_Dpi.expected.png
  18. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png
  19. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_144_Dpi.expected.png
  20. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png
  21. BIN
      tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_96_Dpi.expected.png

4
src/Avalonia.Visuals/Media/PixelSize.cs

@ -159,8 +159,8 @@ namespace Avalonia
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize(
(int)(size.Width * (dpi.X / 96)),
(int)(size.Height * (dpi.Y / 96)));
(int)Math.Ceiling(size.Width * (dpi.X / 96)),
(int)Math.Ceiling(size.Height * (dpi.Y / 96)));
/// <summary>
/// Returns the string representation of the size.

4
src/Avalonia.Visuals/Rendering/Utilities/TileBrushCalculator.cs

@ -116,8 +116,8 @@ namespace Avalonia.Rendering.Utilities
return true;
if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio)
return false;
if ((int)SourceRect.Width != _imageSize.Width ||
(int)SourceRect.Height != _imageSize.Height)
if (SourceRect.Width != _imageSize.Width ||
SourceRect.Height != _imageSize.Height)
return true;
return false;
}

48
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -225,10 +225,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var normalizedDpi = new Vector(_dpi.X / SkiaPlatform.DefaultDpi.X, _dpi.Y / SkiaPlatform.DefaultDpi.Y);
var pixelSize = size * normalizedDpi;
return CreateRenderTarget((int) pixelSize.Width, (int) pixelSize.Height, _dpi);
return CreateRenderTarget(size);
}
/// <inheritdoc />
@ -387,26 +384,27 @@ namespace Avalonia.Skia
/// <param name="targetSize">Target size.</param>
/// <param name="tileBrush">Tile brush to use.</param>
/// <param name="tileBrushImage">Tile brush image.</param>
/// <param name="interpolationMode">The bitmap interpolation mode.</param>
private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
{
var calc = new TileBrushCalculator(tileBrush,
new Size(tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height), targetSize);
var intermediate = CreateRenderTarget(
(int)calc.IntermediateSize.Width,
(int)calc.IntermediateSize.Height, _dpi);
var calc = new TileBrushCalculator(tileBrush, tileBrushImage.PixelSize.ToSize(_dpi), targetSize);
var intermediate = CreateRenderTarget(calc.IntermediateSize);
paintWrapper.AddDisposable(intermediate);
using (var context = intermediate.CreateDrawingContext(null))
{
var rect = new Rect(0, 0, tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height);
var sourceRect = new Rect(tileBrushImage.PixelSize.ToSize(96));
var targetRect = new Rect(tileBrushImage.PixelSize.ToSize(_dpi));
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(RefCountable.CreateUnownedNotClonable(tileBrushImage), 1, rect, rect, tileBrush.BitmapInterpolationMode);
context.DrawImage(
RefCountable.CreateUnownedNotClonable(tileBrushImage),
1,
sourceRect,
targetRect,
tileBrush.BitmapInterpolationMode);
context.PopClip();
}
@ -433,7 +431,14 @@ namespace Avalonia.Skia
var image = intermediate.SnapshotImage();
paintWrapper.AddDisposable(image);
using (var shader = image.ToShader(tileX, tileY, tileTransform))
var paintTransform = default(SKMatrix);
SKMatrix.Concat(
ref paintTransform,
tileTransform,
SKMatrix.MakeScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
using (var shader = image.ToShader(tileX, tileY, paintTransform))
{
paintWrapper.Paint.Shader = shader;
}
@ -457,7 +462,7 @@ namespace Avalonia.Skia
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
var intermediate = CreateRenderTarget((int)intermediateSize.Width, (int)intermediateSize.Height, _dpi);
var intermediate = CreateRenderTarget(intermediateSize);
using (var ctx = intermediate.CreateDrawingContext(visualBrushRenderer))
{
@ -609,18 +614,17 @@ namespace Avalonia.Skia
/// <summary>
/// Create new render target compatible with this drawing context.
/// </summary>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
/// <param name="dpi">Drawing dpi.</param>
/// <param name="size">The size of the render target in DIPs.</param>
/// <param name="format">Pixel format.</param>
/// <returns></returns>
private SurfaceRenderTarget CreateRenderTarget(int width, int height, Vector dpi, PixelFormat? format = null)
private SurfaceRenderTarget CreateRenderTarget(Size size, PixelFormat? format = null)
{
var pixelSize = PixelSize.FromSize(size, _dpi);
var createInfo = new SurfaceRenderTarget.CreateInfo
{
Width = width,
Height = height,
Dpi = dpi,
Width = pixelSize.Width,
Height = pixelSize.Height,
Dpi = _dpi,
Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering,
GrContext = _grContext

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

@ -434,10 +434,16 @@ namespace Avalonia.Direct2D1.Media
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
// We need to ensure the size we're requesting is an integer pixel size, otherwise
// D2D alters the DPI of the render target, which messes stuff up. PixelSize.FromSize
// will do the rounding for us.
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = PixelSize.FromSize(intermediateSize, dpi);
using (var intermediate = new BitmapRenderTarget(
_deviceContext,
CompatibleRenderTargetOptions.None,
intermediateSize.ToSharpDX()))
pixelSize.ToSize(dpi).ToSharpDX()))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer))
{

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

@ -20,7 +20,8 @@ namespace Avalonia.Direct2D1.Media
BitmapImpl bitmap,
Size targetSize)
{
var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(96), targetSize);
var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height);
var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(dpi), targetSize);
if (!calc.NeedsIntermediate)
{
@ -99,7 +100,8 @@ namespace Avalonia.Direct2D1.Media
using (var context = new RenderTarget(result).CreateDrawingContext(null))
{
var rect = new Rect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height);
var dpi = new Vector(target.DotsPerInch.Width, target.DotsPerInch.Height);
var rect = new Rect(bitmap.PixelSize.ToSize(dpi));
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);

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

@ -1,8 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.IO;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Utilities;
using SharpDX;
using SharpDX.Direct2D1;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
@ -49,5 +51,22 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
return new OptionalDispose<D2DBitmap>(_renderTarget.Bitmap, false);
}
public override void Save(Stream stream)
{
using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi))
{
using (var dc = wic.CreateDrawingContext(null))
{
dc.DrawImage(
RefCountable.CreateUnownedNotClonable(this),
1,
new Rect(PixelSize.ToSize(Dpi.X)),
new Rect(PixelSize.ToSize(Dpi.X)));
}
wic.Save(stream);
}
}
}
}

13
src/Windows/Avalonia.Direct2D1/Utils/DebugUtils.cs

@ -0,0 +1,13 @@
using Avalonia.Direct2D1.Media.Imaging;
namespace Avalonia.Direct2D1.Utils
{
internal static class DebugUtils
{
public static void Save(SharpDX.Direct2D1.BitmapRenderTarget bitmap, string filename)
{
var rtb = new D2DRenderTargetBitmapImpl(bitmap);
rtb.Save(filename);
}
}
}

276
tests/Avalonia.RenderTests/Media/VisualBrushTests.cs

@ -449,5 +449,281 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task VisualBrush_Grip_96_Dpi()
{
var target = new Border
{
Width = 100,
Height = 10,
Background = new VisualBrush
{
SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Stretch = Stretch.UniformToFill,
Visual = new Canvas
{
Width = 4,
Height = 5,
Background = Brushes.WhiteSmoke,
Children =
{
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.TopProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
[Canvas.TopProperty] = 4,
}
}
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task VisualBrush_Grip_144_Dpi()
{
var target = new Border
{
Width = 100,
Height = 7.5,
Background = new VisualBrush
{
SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Stretch = Stretch.UniformToFill,
Visual = new Canvas
{
Width = 4,
Height = 5,
Background = Brushes.WhiteSmoke,
Children =
{
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.TopProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
[Canvas.TopProperty] = 4,
}
}
}
}
};
await RenderToFile(target, dpi: 144);
CompareImages();
}
[Fact]
public async Task VisualBrush_Grip_192_Dpi()
{
var target = new Border
{
Width = 100,
Height = 10,
Background = new VisualBrush
{
SourceRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
DestinationRect = new RelativeRect(0, 0, 4, 5, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Stretch = Stretch.UniformToFill,
Visual = new Canvas
{
Width = 4,
Height = 5,
Background = Brushes.WhiteSmoke,
Children =
{
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.TopProperty] = 2,
},
new Rectangle
{
Width = 1,
Height = 1,
Fill = Brushes.Red,
[Canvas.LeftProperty] = 2,
[Canvas.TopProperty] = 4,
}
}
}
}
};
await RenderToFile(target, dpi: 192);
CompareImages();
}
[Fact]
public async Task VisualBrush_Checkerboard_96_Dpi()
{
var target = new Border
{
Width = 200,
Height = 200,
Background = new VisualBrush
{
DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Visual = new Canvas
{
Width = 16,
Height= 16,
Background = Brushes.Red,
Children =
{
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
},
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
[Canvas.LeftProperty] = 8,
[Canvas.TopProperty] = 8,
},
}
}
}
};
await RenderToFile(target);
CompareImages();
}
[Fact]
public async Task VisualBrush_Checkerboard_144_Dpi()
{
var target = new Border
{
Width = 200,
Height = 200,
Background = new VisualBrush
{
DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Visual = new Canvas
{
Width = 16,
Height = 16,
Background = Brushes.Red,
Children =
{
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
},
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
[Canvas.LeftProperty] = 8,
[Canvas.TopProperty] = 8,
},
}
}
}
};
await RenderToFile(target, dpi: 144);
CompareImages();
}
[Fact]
public async Task VisualBrush_Checkerboard_192_Dpi()
{
var target = new Border
{
Width = 200,
Height = 200,
Background = new VisualBrush
{
DestinationRect = new RelativeRect(0, 0, 16, 16, RelativeUnit.Absolute),
TileMode = TileMode.Tile,
Visual = new Canvas
{
Width = 16,
Height = 16,
Background = Brushes.Red,
Children =
{
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
},
new Rectangle
{
Width = 8,
Height = 8,
Fill = Brushes.Green,
[Canvas.LeftProperty] = 8,
[Canvas.TopProperty] = 8,
},
}
}
}
};
await RenderToFile(target, dpi: 192);
CompareImages();
}
}
}

8
tests/Avalonia.RenderTests/TestBase.cs

@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests
get;
}
protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "")
protected async Task RenderToFile(Control target, [CallerMemberName] string testName = "", double dpi = 96)
{
if (!Directory.Exists(OutputPath))
{
@ -75,9 +75,9 @@ namespace Avalonia.Direct2D1.RenderTests
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var pixelSize = new PixelSize((int)target.Width, (int)target.Height);
var size = new Size(target.Width, target.Height);
var dpi = new Vector(96, 96);
var dpiVector = new Vector(dpi, dpi);
using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpi))
using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpiVector))
{
target.Measure(size);
target.Arrange(new Rect(size));
@ -85,7 +85,7 @@ namespace Avalonia.Direct2D1.RenderTests
bitmap.Save(immediatePath);
}
using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpi))
using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpiVector))
using (var renderer = new DeferredRenderer(target, rtb))
{
target.Measure(size);

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

BIN
tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_144_Dpi.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 B

BIN
tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_192_Dpi.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

BIN
tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Checkerboard_96_Dpi.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 B

BIN
tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_192_Dpi.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 B

BIN
tests/TestFiles/Skia/Media/VisualBrush/VisualBrush_Grip_96_Dpi.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B

Loading…
Cancel
Save