Browse Source

Merge pull request #3373 from AvaloniaUI/fixes/3371-image-sizing

Added Image.StretchDirection and fix Image measurement
release/0.9.2
Jumar Macato 6 years ago
committed by Dan Walmsley
parent
commit
39f0c30f23
  1. 32
      src/Avalonia.Controls/Image.cs
  2. 90
      src/Avalonia.Visuals/Media/MediaExtensions.cs
  3. 25
      src/Avalonia.Visuals/Media/StretchDirection.cs
  4. 70
      tests/Avalonia.Controls.UnitTests/ImageTests.cs

32
src/Avalonia.Controls/Image.cs

@ -23,6 +23,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
/// Defines the <see cref="StretchDirection"/> property.
/// </summary>
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Image, StretchDirection>(
nameof(StretchDirection),
StretchDirection.Both);
static Image()
{
AffectsRender<Image>(SourceProperty, StretchProperty);
@ -43,10 +51,19 @@ namespace Avalonia.Controls
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Gets or sets a value controlling in what direction the image will be stretched.
/// </summary>
public StretchDirection StretchDirection
{
get { return GetValue(StretchDirectionProperty); }
set { SetValue(StretchDirectionProperty, value); }
}
/// <summary>
/// Renders the control.
/// </summary>
@ -59,7 +76,7 @@ namespace Avalonia.Controls
{
Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
@ -85,15 +102,8 @@ namespace Avalonia.Controls
if (source != null)
{
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{
result = sourceSize;
}
else
{
result = Stretch.CalculateSize(availableSize, sourceSize);
}
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
result = Stretch.CalculateSize(availableSize, sourceSize, StretchDirection);
}
return result;

90
src/Avalonia.Visuals/Media/MediaExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -16,24 +17,82 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>A vector with the X and Y scaling factors.</returns>
public static Vector CalculateScaling(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Vector CalculateScaling(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
double scaleX = 1;
double scaleY = 1;
var scaleX = 1.0;
var scaleY = 1.0;
if (stretch != Stretch.None)
bool isConstrainedWidth = !double.IsPositiveInfinity(destinationSize.Width);
bool isConstrainedHeight = !double.IsPositiveInfinity(destinationSize.Height);
if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
&& (isConstrainedWidth || isConstrainedHeight))
{
scaleX = destinationSize.Width / sourceSize.Width;
scaleY = destinationSize.Height / sourceSize.Height;
// Compute scaling factors for both axes
scaleX = MathUtilities.IsZero(sourceSize.Width) ? 0.0 : destinationSize.Width / sourceSize.Width;
scaleY = MathUtilities.IsZero(sourceSize.Height) ? 0.0 : destinationSize.Height / sourceSize.Height;
switch (stretch)
if (!isConstrainedWidth)
{
scaleX = scaleY;
}
else if (!isConstrainedHeight)
{
scaleY = scaleX;
}
else
{
case Stretch.Uniform:
scaleX = scaleY = Math.Min(scaleX, scaleY);
// If not preserving aspect ratio, then just apply transform to fit
switch (stretch)
{
case Stretch.Uniform:
// Find minimum scale that we use for both axes
double minscale = scaleX < scaleY ? scaleX : scaleY;
scaleX = scaleY = minscale;
break;
case Stretch.UniformToFill:
// Find maximum scale that we use for both axes
double maxscale = scaleX > scaleY ? scaleX : scaleY;
scaleX = scaleY = maxscale;
break;
case Stretch.Fill:
// We already computed the fill scale factors above, so just use them
break;
}
}
// Apply stretch direction by bounding scales.
// In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
// In the uniform fill case, we have the same result too.
// In the fill case, note that we change aspect ratio, but that is okay
switch (stretchDirection)
{
case StretchDirection.UpOnly:
if (scaleX < 1.0)
scaleX = 1.0;
if (scaleY < 1.0)
scaleY = 1.0;
break;
case StretchDirection.DownOnly:
if (scaleX > 1.0)
scaleX = 1.0;
if (scaleY > 1.0)
scaleY = 1.0;
break;
case Stretch.UniformToFill:
scaleX = scaleY = Math.Max(scaleX, scaleY);
case StretchDirection.Both:
break;
default:
break;
}
}
@ -47,10 +106,15 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>The size of the stretched source.</returns>
public static Size CalculateSize(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Size CalculateSize(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize);
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize, stretchDirection);
}
}
}

25
src/Avalonia.Visuals/Media/StretchDirection.cs

@ -0,0 +1,25 @@
namespace Avalonia.Media
{
/// <summary>
/// Describes the type of scaling that can be used when scaling content.
/// </summary>
public enum StretchDirection
{
/// <summary>
/// Only scales the content upwards when the content is smaller than the available space.
/// If the content is larger, no scaling downwards is done.
/// </summary>
UpOnly,
/// <summary>
/// Only scales the content downwards when the content is larger than the available space.
/// If the content is smaller, no scaling upwards is done.
/// </summary>
DownOnly,
/// <summary>
/// Always stretches to fit the available space according to the stretch mode.
/// </summary>
Both,
}
}

70
tests/Avalonia.Controls.UnitTests/ImageTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -52,7 +52,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -62,10 +62,59 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(50, 50), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_With_StretchDirection_DownOnly()
{
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.StretchDirection = StretchDirection.DownOnly;
target.Source = bitmap;
target.Measure(new Size(150, 150));
Assert.Equal(new Size(50, 100), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(200, double.PositiveInfinity));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, 400));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Assert.Equal(new Size(50, 100), image.DesiredSize);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -79,7 +128,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -93,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -107,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -117,5 +166,10 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(25, 100), target.Bounds.Size);
}
private IBitmap CreateBitmap(int width, int height)
{
return Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(width, height));
}
}
}

Loading…
Cancel
Save