@ -0,0 +1,395 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Controls; |
|||
using Perspex.Controls.Shapes; |
|||
using Perspex.Layout; |
|||
using Perspex.Media; |
|||
using Perspex.Media.Imaging; |
|||
using Xunit; |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
namespace Perspex.Cairo.RenderTests.Media |
|||
#else
|
|||
namespace Perspex.Direct2D1.RenderTests.Media |
|||
#endif
|
|||
{ |
|||
public class ImageBrushTests : TestBase |
|||
{ |
|||
public ImageBrushTests() |
|||
: base(@"Media\ImageBrush") |
|||
{ |
|||
} |
|||
|
|||
private string BitmapPath |
|||
{ |
|||
get { return System.IO.Path.Combine(OutputPath, "github_icon.png"); } |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_Alignment_TopLeft() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
AlignmentX = AlignmentX.Left, |
|||
AlignmentY = AlignmentY.Top, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_Alignment_Center() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
AlignmentX = AlignmentX.Center, |
|||
AlignmentY = AlignmentY.Center, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_Alignment_BottomRight() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
AlignmentX = AlignmentX.Right, |
|||
AlignmentY = AlignmentY.Bottom, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_Fill_NoTile() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 920, |
|||
Height = 920, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.Fill, |
|||
TileMode = TileMode.None, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_Uniform_NoTile() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 300, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.Uniform, |
|||
TileMode = TileMode.None, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_UniformToFill_NoTile() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 300, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.UniformToFill, |
|||
TileMode = TileMode.None, |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
SourceRect = new RelativeRect(250, 250, 250, 250, RelativeUnit.Absolute), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_BottomRightQuarterDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
DestinationRect = new RelativeRect(92, 92, 92, 92, RelativeUnit.Absolute), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.None, |
|||
SourceRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), |
|||
DestinationRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.Tile, |
|||
SourceRect = new RelativeRect(0.5, 0.5, 0.5, 0.5, RelativeUnit.Relative), |
|||
DestinationRect = new RelativeRect(0.25, 0.25, 0.5, 0.5, RelativeUnit.Relative), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_FlipX_TopLeftDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.FlipX, |
|||
DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_FlipY_TopLeftDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.FlipY, |
|||
DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
|
|||
#if PERSPEX_CAIRO
|
|||
[Fact(Skip = "ImageBrush not yet implemented on Cairo")] |
|||
#else
|
|||
[Fact] |
|||
#endif
|
|||
public void ImageBrush_NoStretch_FlipXY_TopLeftDest() |
|||
{ |
|||
Decorator target = new Decorator |
|||
{ |
|||
Padding = new Thickness(8), |
|||
Width = 200, |
|||
Height = 200, |
|||
Child = new Rectangle |
|||
{ |
|||
Fill = new ImageBrush |
|||
{ |
|||
Stretch = Stretch.None, |
|||
TileMode = TileMode.FlipXY, |
|||
DestinationRect = new RelativeRect(0, 0, 0.5, 0.5, RelativeUnit.Relative), |
|||
Source = new Bitmap(BitmapPath), |
|||
} |
|||
} |
|||
}; |
|||
|
|||
RenderToFile(target); |
|||
CompareImages(); |
|||
} |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 116 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 17 KiB |
@ -0,0 +1,44 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using Perspex.Media.Imaging; |
|||
|
|||
namespace Perspex.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Paints an area with an <see cref="IBitmap"/>.
|
|||
/// </summary>
|
|||
public class ImageBrush : TileBrush |
|||
{ |
|||
/// <summary>
|
|||
/// Defines the <see cref="Visual"/> property.
|
|||
/// </summary>
|
|||
public static readonly PerspexProperty<IBitmap> SourceProperty = |
|||
PerspexProperty.Register<ImageBrush, IBitmap>("Source"); |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="VisualBrush"/> class.
|
|||
/// </summary>
|
|||
public ImageBrush() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="VisualBrush"/> class.
|
|||
/// </summary>
|
|||
/// <param name="source">The image to draw.</param>
|
|||
public ImageBrush(IBitmap source) |
|||
{ |
|||
Source = source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the image to draw.
|
|||
/// </summary>
|
|||
public IBitmap Source |
|||
{ |
|||
get { return GetValue(SourceProperty); } |
|||
set { SetValue(SourceProperty, value); } |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Perspex.Media; |
|||
using SharpDX.Direct2D1; |
|||
|
|||
namespace Perspex.Direct2D1.Media |
|||
{ |
|||
public class ImageBrushImpl : TileBrushImpl |
|||
{ |
|||
public ImageBrushImpl( |
|||
ImageBrush brush, |
|||
RenderTarget target, |
|||
Size targetSize) |
|||
{ |
|||
if (brush.Source == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var image = ((BitmapImpl)brush.Source.PlatformImpl).GetDirect2DBitmap(target); |
|||
var imageSize = new Size(brush.Source.PixelWidth, brush.Source.PixelHeight); |
|||
var tileMode = brush.TileMode; |
|||
var sourceRect = brush.SourceRect.ToPixels(imageSize); |
|||
var destinationRect = brush.DestinationRect.ToPixels(targetSize); |
|||
var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size); |
|||
var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale); |
|||
var intermediateSize = CalculateIntermediateSize(tileMode, targetSize, destinationRect.Size); |
|||
var brtOpts = CompatibleRenderTargetOptions.None; |
|||
|
|||
// TODO: There are times where we don't need to draw an intermediate bitmap. Identify
|
|||
// them and directly use 'image' in those cases.
|
|||
using (var intermediate = new BitmapRenderTarget(target, brtOpts, intermediateSize)) |
|||
{ |
|||
SharpDX.RectangleF drawRect; |
|||
|
|||
intermediate.BeginDraw(); |
|||
intermediate.Transform = CalculateIntermediateTransform( |
|||
tileMode, |
|||
sourceRect, |
|||
destinationRect, |
|||
scale, |
|||
translate, |
|||
out drawRect); |
|||
intermediate.PushAxisAlignedClip(drawRect, AntialiasMode.Aliased); |
|||
intermediate.DrawBitmap(image, 1, BitmapInterpolationMode.Linear); |
|||
intermediate.PopAxisAlignedClip(); |
|||
intermediate.EndDraw(); |
|||
|
|||
this.PlatformBrush = new BitmapBrush( |
|||
target, |
|||
intermediate.Bitmap, |
|||
GetBitmapBrushProperties(brush), |
|||
GetBrushProperties(brush, destinationRect)); |
|||
} |
|||
} |
|||
|
|||
private BitmapBrush CreateDirectBrush( |
|||
ImageBrush brush, |
|||
RenderTarget target, |
|||
Bitmap image, |
|||
Rect sourceRect, |
|||
Rect destinationRect) |
|||
{ |
|||
var tileMode = brush.TileMode; |
|||
var scale = brush.Stretch.CalculateScaling(destinationRect.Size, sourceRect.Size); |
|||
var translate = CalculateTranslate(brush, sourceRect, destinationRect, scale); |
|||
var transform = Matrix.CreateTranslation(-sourceRect.Position) * |
|||
Matrix.CreateScale(scale) * |
|||
Matrix.CreateTranslation(translate); |
|||
|
|||
var opts = new BrushProperties |
|||
{ |
|||
Transform = transform.ToDirect2D(), |
|||
Opacity = (float)brush.Opacity, |
|||
}; |
|||
|
|||
var bitmapOpts = new BitmapBrushProperties |
|||
{ |
|||
ExtendModeX = GetExtendModeX(tileMode), |
|||
ExtendModeY = GetExtendModeY(tileMode), |
|||
}; |
|||
|
|||
return new BitmapBrush(target, image, bitmapOpts, opts); |
|||
} |
|||
|
|||
private BitmapBrush CreateIndirectBrush() |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,135 @@ |
|||
// Copyright (c) The Perspex Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Perspex.Layout; |
|||
using Perspex.Media; |
|||
using SharpDX; |
|||
using SharpDX.Direct2D1; |
|||
|
|||
namespace Perspex.Direct2D1.Media |
|||
{ |
|||
public class TileBrushImpl : BrushImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Calculates a translate based on a <see cref="TileBrush"/>, a source and destination
|
|||
/// rectangle and a scale.
|
|||
/// </summary>
|
|||
/// <param name="brush">The brush.</param>
|
|||
/// <param name="sourceRect">The source rectangle.</param>
|
|||
/// <param name="destinationRect">The destination rectangle.</param>
|
|||
/// <param name="scale">The scale factor.</param>
|
|||
/// <returns>A vector with the X and Y translate.</returns>
|
|||
protected static Vector CalculateTranslate( |
|||
TileBrush brush, |
|||
Rect sourceRect, |
|||
Rect destinationRect, |
|||
Vector scale) |
|||
{ |
|||
var x = 0.0; |
|||
var y = 0.0; |
|||
var size = sourceRect.Size * scale; |
|||
|
|||
switch (brush.AlignmentX) |
|||
{ |
|||
case AlignmentX.Center: |
|||
x += (destinationRect.Width - size.Width) / 2; |
|||
break; |
|||
case AlignmentX.Right: |
|||
x += destinationRect.Width - size.Width; |
|||
break; |
|||
} |
|||
|
|||
switch (brush.AlignmentY) |
|||
{ |
|||
case AlignmentY.Center: |
|||
y += (destinationRect.Height - size.Height) / 2; |
|||
break; |
|||
case AlignmentY.Bottom: |
|||
y += destinationRect.Height - size.Height; |
|||
break; |
|||
} |
|||
|
|||
return new Vector(x, y); |
|||
} |
|||
|
|||
protected static Size2F CalculateIntermediateSize( |
|||
TileMode tileMode, |
|||
Size targetSize, |
|||
Size destinationSize) |
|||
{ |
|||
var result = tileMode == TileMode.None ? targetSize : destinationSize; |
|||
return result.ToSharpDX(); |
|||
} |
|||
|
|||
protected static Matrix3x2 CalculateIntermediateTransform( |
|||
TileMode tileMode, |
|||
Rect sourceRect, |
|||
Rect destinationRect, |
|||
Vector scale, |
|||
Vector translate, |
|||
out SharpDX.RectangleF drawRect) |
|||
{ |
|||
var transform = Matrix.CreateTranslation(-sourceRect.Position) * |
|||
Matrix.CreateScale(scale) * |
|||
Matrix.CreateTranslation(translate); |
|||
Rect dr; |
|||
|
|||
if (tileMode == TileMode.None) |
|||
{ |
|||
dr = destinationRect; |
|||
transform *= Matrix.CreateTranslation(destinationRect.Position); |
|||
} |
|||
else |
|||
{ |
|||
dr = new Rect(destinationRect.Size); |
|||
} |
|||
|
|||
drawRect = (dr * -transform).ToSharpDX(); |
|||
|
|||
return transform.ToDirect2D(); |
|||
} |
|||
|
|||
protected static BrushProperties GetBrushProperties(TileBrush brush, Rect destinationRect) |
|||
{ |
|||
var tileMode = brush.TileMode; |
|||
|
|||
return new BrushProperties |
|||
{ |
|||
Opacity = (float)brush.Opacity, |
|||
Transform = brush.TileMode != TileMode.None ? |
|||
SharpDX.Matrix3x2.Translation( |
|||
(float)destinationRect.X, |
|||
(float)destinationRect.Y) : |
|||
SharpDX.Matrix3x2.Identity, |
|||
}; |
|||
} |
|||
|
|||
protected static BitmapBrushProperties GetBitmapBrushProperties(TileBrush brush) |
|||
{ |
|||
var tileMode = brush.TileMode; |
|||
|
|||
return new BitmapBrushProperties |
|||
{ |
|||
ExtendModeX = GetExtendModeX(tileMode), |
|||
ExtendModeY = GetExtendModeY(tileMode), |
|||
}; |
|||
} |
|||
|
|||
protected static ExtendMode GetExtendModeX(TileMode tileMode) |
|||
{ |
|||
return (tileMode & TileMode.FlipX) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; |
|||
} |
|||
|
|||
protected static ExtendMode GetExtendModeY(TileMode tileMode) |
|||
{ |
|||
return (tileMode & TileMode.FlipY) != 0 ? ExtendMode.Mirror : ExtendMode.Wrap; |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose(); |
|||
base.Dispose(); |
|||
} |
|||
} |
|||
} |
|||