diff --git a/Perspex.Direct2D1.RenderTests/Controls/ImageTests.cs b/Perspex.Direct2D1.RenderTests/Controls/ImageTests.cs
new file mode 100644
index 0000000000..b904150570
--- /dev/null
+++ b/Perspex.Direct2D1.RenderTests/Controls/ImageTests.cs
@@ -0,0 +1,107 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Direct2D1.RenderTests.Controls
+{
+ using System.IO;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using Perspex.Controls;
+ using Perspex.Layout;
+ using Perspex.Media;
+ using Perspex.Media.Imaging;
+
+ [TestClass]
+ public class ImageTests : TestBase
+ {
+ private Bitmap bitmap;
+
+ public ImageTests()
+ : base(@"Controls\Image")
+ {
+ this.bitmap = new Bitmap(Path.Combine(this.OutputPath, "test.png"));
+ }
+
+ [TestMethod]
+ public void Image_Stretch_None()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(20, 8),
+ Width = 200,
+ Height = 200,
+ Content = new Image
+ {
+ Background = Brushes.Red,
+ Source = this.bitmap,
+ Stretch = Stretch.None,
+ }
+ };
+
+ this.RenderToFile(target);
+ this.CompareImages();
+ }
+
+ [TestMethod]
+ public void Image_Stretch_Fill()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(20, 8),
+ Width = 200,
+ Height = 200,
+ Content = new Image
+ {
+ Background = Brushes.Red,
+ Source = this.bitmap,
+ Stretch = Stretch.Fill,
+ }
+ };
+
+ this.RenderToFile(target);
+ this.CompareImages();
+ }
+
+ [TestMethod]
+ public void Image_Stretch_Uniform()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(20, 8),
+ Width = 200,
+ Height = 200,
+ Content = new Image
+ {
+ Background = Brushes.Red,
+ Source = this.bitmap,
+ Stretch = Stretch.Uniform,
+ }
+ };
+
+ this.RenderToFile(target);
+ this.CompareImages();
+ }
+
+ [TestMethod]
+ public void Image_Stretch_UniformToFill()
+ {
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(20, 8),
+ Width = 200,
+ Height = 200,
+ Content = new Image
+ {
+ Background = Brushes.Red,
+ Source = this.bitmap,
+ Stretch = Stretch.UniformToFill,
+ }
+ };
+
+ this.RenderToFile(target);
+ this.CompareImages();
+ }
+ }
+}
diff --git a/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj b/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj
index 4dac4719fa..6f019ee28d 100644
--- a/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj
+++ b/Perspex.Direct2D1.RenderTests/Perspex.Direct2D1.RenderTests.csproj
@@ -58,6 +58,7 @@
+
diff --git a/Perspex.Direct2D1.RenderTests/TestBase.cs b/Perspex.Direct2D1.RenderTests/TestBase.cs
index 1ca3108e5f..42893414a7 100644
--- a/Perspex.Direct2D1.RenderTests/TestBase.cs
+++ b/Perspex.Direct2D1.RenderTests/TestBase.cs
@@ -11,7 +11,7 @@ namespace Perspex.Direct2D1.RenderTests
using ImageMagick;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Perspex.Controls;
- using Perspex.Media;
+ using Perspex.Media.Imaging;
public class TestBase
{
diff --git a/Perspex.Direct2D1/Direct2D1Platform.cs b/Perspex.Direct2D1/Direct2D1Platform.cs
index b0049f02bb..d997bf5787 100644
--- a/Perspex.Direct2D1/Direct2D1Platform.cs
+++ b/Perspex.Direct2D1/Direct2D1Platform.cs
@@ -57,5 +57,10 @@ namespace Perspex.Direct2D1
{
return new StreamGeometryImpl();
}
+
+ public IBitmapImpl LoadBitmap(string fileName)
+ {
+ return new BitmapImpl(imagingFactory, fileName);
+ }
}
}
diff --git a/Perspex.Direct2D1/Media/DrawingContext.cs b/Perspex.Direct2D1/Media/DrawingContext.cs
index 00fb0bdaae..a8c3f5b659 100644
--- a/Perspex.Direct2D1/Media/DrawingContext.cs
+++ b/Perspex.Direct2D1/Media/DrawingContext.cs
@@ -51,6 +51,18 @@ namespace Perspex.Direct2D1.Media
this.renderTarget.EndDraw();
}
+ public void DrawImage(Perspex.Media.Imaging.Bitmap bitmap, double opacity, Rect sourceRect, Rect destRect)
+ {
+ BitmapImpl impl = (BitmapImpl)bitmap.PlatformImpl;
+ Bitmap d2d = impl.GetDirect2DBitmap(this.renderTarget);
+ this.renderTarget.DrawBitmap(
+ d2d,
+ destRect.ToSharpDX(),
+ (float)opacity,
+ BitmapInterpolationMode.Linear,
+ sourceRect.ToSharpDX());
+ }
+
///
/// Draws a line.
///
diff --git a/Perspex.Direct2D1/Media/Imaging/BitmapImpl.cs b/Perspex.Direct2D1/Media/Imaging/BitmapImpl.cs
index 7426b6446d..7a93a434e2 100644
--- a/Perspex.Direct2D1/Media/Imaging/BitmapImpl.cs
+++ b/Perspex.Direct2D1/Media/Imaging/BitmapImpl.cs
@@ -15,23 +15,57 @@ namespace Perspex.Direct2D1.Media
{
private ImagingFactory factory;
+ private SharpDX.Direct2D1.Bitmap direct2D;
+
+ public BitmapImpl(ImagingFactory factory, string fileName)
+ {
+ this.factory = factory;
+
+ using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
+ {
+ this.WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
+ }
+ }
+
public BitmapImpl(ImagingFactory factory, int width, int height)
{
this.factory = factory;
this.WicImpl = new Bitmap(
- factory,
+ factory,
width,
height,
PixelFormat.Format32bppPBGRA,
BitmapCreateCacheOption.CacheOnLoad);
}
+ public int PixelHeight
+ {
+ get { return this.WicImpl.Size.Width; }
+ }
+
+ public int PixelWidth
+ {
+ get { return this.WicImpl.Size.Height; }
+ }
+
public Bitmap WicImpl
{
get;
private set;
}
+ public SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
+ {
+ if (this.direct2D == null)
+ {
+ FormatConverter converter = new FormatConverter(this.factory);
+ converter.Initialize(this.WicImpl, PixelFormat.Format32bppPBGRA);
+ this.direct2D = SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
+ }
+
+ return this.direct2D;
+ }
+
public void Save(string fileName)
{
if (Path.GetExtension(fileName) != ".png")
diff --git a/Perspex.Direct2D1/PrimitiveExtensions.cs b/Perspex.Direct2D1/PrimitiveExtensions.cs
index d3469d78a0..48704bd6c0 100644
--- a/Perspex.Direct2D1/PrimitiveExtensions.cs
+++ b/Perspex.Direct2D1/PrimitiveExtensions.cs
@@ -15,6 +15,11 @@ namespace Perspex.Direct2D1
return new Rect(r.X, r.Y, r.Width, r.Height);
}
+ public static RectangleF ToSharpDX(this Rect r)
+ {
+ return new RectangleF((float)r.X, (float)r.Y, (float)r.Width, (float)r.Height);
+ }
+
public static Vector2 ToSharpDX(this Perspex.Point p)
{
return new Vector2((float)p.X, (float)p.Y);
diff --git a/Perspex/Controls/Control.cs b/Perspex/Controls/Control.cs
index 16a342f651..e23df4c4a3 100644
--- a/Perspex/Controls/Control.cs
+++ b/Perspex/Controls/Control.cs
@@ -8,11 +8,9 @@ namespace Perspex.Controls
{
using System;
using System.Collections.Generic;
- using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using Perspex.Input;
- using Perspex.Layout;
using Perspex.Media;
using Perspex.Styling;
using Splat;
diff --git a/Perspex/Controls/Image.cs b/Perspex/Controls/Image.cs
new file mode 100644
index 0000000000..cb594b3340
--- /dev/null
+++ b/Perspex/Controls/Image.cs
@@ -0,0 +1,115 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Controls
+{
+ using System;
+ using Perspex.Media;
+ using Perspex.Media.Imaging;
+
+ public class Image : Control
+ {
+ public static readonly PerspexProperty SourceProperty =
+ PerspexProperty.Register("Source");
+
+ public static readonly PerspexProperty StretchProperty =
+ PerspexProperty.Register("Stretch", Stretch.Uniform);
+
+ public Bitmap Source
+ {
+ get { return this.GetValue(SourceProperty); }
+ set { this.SetValue(SourceProperty, value); }
+ }
+
+ public Stretch Stretch
+ {
+ get { return (Stretch)this.GetValue(StretchProperty); }
+ set { this.SetValue(StretchProperty, value); }
+ }
+
+ public override void Render(IDrawingContext drawingContext)
+ {
+ Brush background = this.Background;
+ Bitmap source = this.Source;
+
+ if (background != null)
+ {
+ drawingContext.FillRectange(background, new Rect(this.ActualSize));
+ }
+
+ if (source != null)
+ {
+ Rect viewPort = new Rect(this.ActualSize);
+ Size sourceSize = new Size(source.PixelWidth, source.PixelHeight);
+ Vector scale = CalculateScaling(this.ActualSize, sourceSize, this.Stretch);
+ Size scaledSize = sourceSize * scale;
+ Rect destRect = viewPort
+ .Center(new Rect(scaledSize))
+ .Intersect(viewPort);
+ Rect sourceRect = new Rect(sourceSize)
+ .Center(new Rect(destRect.Size / scale));
+
+ drawingContext.DrawImage(source, 1, sourceRect, destRect);
+ }
+ }
+
+ private static Vector CalculateScaling(Size availableSize, Size imageSize, Stretch stretch)
+ {
+ double scaleX = 1;
+ double scaleY = 1;
+
+ if (stretch != Stretch.None)
+ {
+ scaleX = availableSize.Width / imageSize.Width;
+ scaleY = availableSize.Height / imageSize.Height;
+
+ switch (stretch)
+ {
+ case Stretch.Uniform:
+ scaleX = scaleY = Math.Min(scaleX, scaleY);
+ break;
+ case Stretch.UniformToFill:
+ scaleX = scaleY = Math.Max(scaleX, scaleY);
+ break;
+ }
+ }
+
+ return new Vector(scaleX, scaleY);
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ double width = 0;
+ double height = 0;
+ Vector scale = new Vector();
+
+ if (this.Source != null)
+ {
+ width = this.Source.PixelWidth;
+ height = this.Source.PixelHeight;
+
+ if (this.Width > 0)
+ {
+ availableSize = new Size(this.Width, availableSize.Height);
+ }
+
+ if (this.Height > 0)
+ {
+ availableSize = new Size(availableSize.Width, this.Height);
+ }
+
+ scale = CalculateScaling(availableSize, new Size(width, height), this.Stretch);
+ }
+
+ return new Size(width * scale.X, height * scale.Y);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ return finalSize;
+ }
+ }
+}
diff --git a/Perspex/Media/IDrawingContext.cs b/Perspex/Media/IDrawingContext.cs
index c22fbfe172..e36d6be8d2 100644
--- a/Perspex/Media/IDrawingContext.cs
+++ b/Perspex/Media/IDrawingContext.cs
@@ -7,12 +7,16 @@
namespace Perspex.Media
{
using System;
+ using Perspex.Media.Imaging;
+
///
/// Defines the interface through which drawing occurs.
///
public interface IDrawingContext : IDisposable
{
+ void DrawImage(Bitmap source, double opacity, Rect sourceRect, Rect destRect);
+
///
/// Draws a line.
///
diff --git a/Perspex/Media/Imaging/Bitmap.cs b/Perspex/Media/Imaging/Bitmap.cs
index 37ab4ea6d0..9e98886f0e 100644
--- a/Perspex/Media/Imaging/Bitmap.cs
+++ b/Perspex/Media/Imaging/Bitmap.cs
@@ -4,13 +4,19 @@
//
// -----------------------------------------------------------------------
-namespace Perspex.Media
+namespace Perspex.Media.Imaging
{
using Perspex.Platform;
using Splat;
- public class Bitmap
+ public class Bitmap : IBitmap
{
+ public Bitmap(string fileName)
+ {
+ IPlatformRenderInterface factory = Locator.Current.GetService();
+ this.PlatformImpl = factory.LoadBitmap(fileName);
+ }
+
public Bitmap(int width, int height)
{
IPlatformRenderInterface factory = Locator.Current.GetService();
@@ -22,6 +28,16 @@ namespace Perspex.Media
this.PlatformImpl = impl;
}
+ public int PixelWidth
+ {
+ get { return this.PlatformImpl.PixelWidth; }
+ }
+
+ public int PixelHeight
+ {
+ get { return this.PlatformImpl.PixelHeight; }
+ }
+
public IBitmapImpl PlatformImpl
{
get;
diff --git a/Perspex/Media/Imaging/IBitmap.cs b/Perspex/Media/Imaging/IBitmap.cs
new file mode 100644
index 0000000000..c070f0113f
--- /dev/null
+++ b/Perspex/Media/Imaging/IBitmap.cs
@@ -0,0 +1,17 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2013 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex.Media.Imaging
+{
+ public interface IBitmap
+ {
+ int PixelHeight { get; }
+
+ int PixelWidth { get; }
+
+ void Save(string fileName);
+ }
+}
\ No newline at end of file
diff --git a/Perspex/Media/Imaging/RenderTargetBitmap.cs b/Perspex/Media/Imaging/RenderTargetBitmap.cs
index 3edc655a1f..3f436941da 100644
--- a/Perspex/Media/Imaging/RenderTargetBitmap.cs
+++ b/Perspex/Media/Imaging/RenderTargetBitmap.cs
@@ -4,7 +4,7 @@
//
// -----------------------------------------------------------------------
-namespace Perspex.Media
+namespace Perspex.Media.Imaging
{
using Perspex.Platform;
using Splat;
diff --git a/Perspex/Perspex.csproj b/Perspex/Perspex.csproj
index bfa2ffe711..3f03832332 100644
--- a/Perspex/Perspex.csproj
+++ b/Perspex/Perspex.csproj
@@ -76,6 +76,7 @@
+
@@ -105,6 +106,7 @@
+
@@ -119,6 +121,7 @@
+
diff --git a/Perspex/Platform/IBitmapImpl.cs b/Perspex/Platform/IBitmapImpl.cs
index 0bbb5314cb..b6ae7a7ff3 100644
--- a/Perspex/Platform/IBitmapImpl.cs
+++ b/Perspex/Platform/IBitmapImpl.cs
@@ -8,6 +8,10 @@ namespace Perspex.Platform
{
public interface IBitmapImpl
{
+ int PixelWidth { get; }
+
+ int PixelHeight { get; }
+
void Save(string fileName);
}
}
diff --git a/Perspex/Platform/IPlatformRenderInterface.cs b/Perspex/Platform/IPlatformRenderInterface.cs
index 71d3c54451..a5bb3be06c 100644
--- a/Perspex/Platform/IPlatformRenderInterface.cs
+++ b/Perspex/Platform/IPlatformRenderInterface.cs
@@ -20,5 +20,7 @@ namespace Perspex.Platform
IRenderer CreateRenderer(IntPtr handle, double width, double height);
IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height);
+
+ IBitmapImpl LoadBitmap(string fileName);
}
}
diff --git a/Perspex/Rect.cs b/Perspex/Rect.cs
index 84bf9731a5..88f8abfe6b 100644
--- a/Perspex/Rect.cs
+++ b/Perspex/Rect.cs
@@ -191,6 +191,32 @@ namespace Perspex
get { return this.width == 0 && this.height == 0; }
}
+ public static Rect operator *(Rect rect, Vector scale)
+ {
+ double centerX = rect.x + rect.width / 2;
+ double centerY = rect.y + rect.height / 2;
+ double width = rect.width * scale.X;
+ double height = rect.height * scale.Y;
+ return new Rect(
+ centerX - (width / 2),
+ centerY - (height / 2),
+ width,
+ height);
+ }
+
+ public static Rect operator /(Rect rect, Vector scale)
+ {
+ double centerX = rect.x + rect.width / 2;
+ double centerY = rect.y + rect.height / 2;
+ double width = rect.width / scale.X;
+ double height = rect.height / scale.Y;
+ return new Rect(
+ centerX - (width / 2),
+ centerY - (height / 2),
+ width,
+ height);
+ }
+
///
/// Determines whether a points in in the bounds of the rectangle.
///
@@ -202,6 +228,15 @@ namespace Perspex
p.Y >= this.y && p.Y < this.y + this.height;
}
+ public Rect Center(Rect rect)
+ {
+ return new Rect(
+ this.x + ((this.width - rect.width) / 2),
+ this.y + ((this.height - rect.height) / 2),
+ rect.width,
+ rect.height);
+ }
+
///
/// Deflates the rectangle.
///
@@ -226,6 +261,27 @@ namespace Perspex
this.Size.Deflate(thickness));
}
+ public Rect Intersect(Rect rect)
+ {
+ double x = Math.Max(this.x, rect.x);
+ double y = Math.Max(this.y, rect.y);
+ double width = Math.Min(this.Right, rect.Right) - x;
+ double height = Math.Min(this.Bottom, rect.Bottom) - y;
+
+ if (width < 0 || height < 0)
+ {
+ return new Rect(
+ double.PositiveInfinity,
+ double.PositiveInfinity,
+ double.NegativeInfinity,
+ double.NegativeInfinity);
+ }
+ else
+ {
+ return new Rect(x, y, width, height);
+ }
+ }
+
///
/// Returns the string representation of the rectangle.
///
diff --git a/Perspex/Size.cs b/Perspex/Size.cs
index e32fe843f8..23e4cab500 100644
--- a/Perspex/Size.cs
+++ b/Perspex/Size.cs
@@ -73,6 +73,28 @@ namespace Perspex
return !(left == right);
}
+ ///
+ /// Scales a size.
+ ///
+ /// The size
+ /// The scaling factor.
+ /// The scaled size.
+ public static Size operator *(Size size, Vector scale)
+ {
+ return new Size(size.width * scale.X, size.height * scale.Y);
+ }
+
+ ///
+ /// Scales a size.
+ ///
+ /// The size
+ /// The scaling factor.
+ /// The scaled size.
+ public static Size operator /(Size size, Vector scale)
+ {
+ return new Size(size.width / scale.X, size.height / scale.Y);
+ }
+
///
/// Constrains the size.
///
diff --git a/Perspex/Thickness.cs b/Perspex/Thickness.cs
index 3ae4c67542..65f01fefc2 100644
--- a/Perspex/Thickness.cs
+++ b/Perspex/Thickness.cs
@@ -41,6 +41,20 @@ namespace Perspex
this.left = this.top = this.right = this.bottom = uniformLength;
}
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The thickness on the left and right.
+ /// The thickness on the top and bottom.
+ public Thickness(double horizontal, double vertical)
+ {
+ Contract.Requires(horizontal >= 0);
+ Contract.Requires(vertical >= 0);
+
+ this.left = this.right = horizontal;
+ this.top = this.bottom = vertical;
+ }
+
///
/// Initializes a new instance of the structure.
///
diff --git a/Perspex/Vector.cs b/Perspex/Vector.cs
new file mode 100644
index 0000000000..279f8004a4
--- /dev/null
+++ b/Perspex/Vector.cs
@@ -0,0 +1,72 @@
+// -----------------------------------------------------------------------
+//
+// Copyright 2014 MIT Licence. See licence.md for more information.
+//
+// -----------------------------------------------------------------------
+
+namespace Perspex
+{
+ using System.Globalization;
+
+ ///
+ /// Defines a vector.
+ ///
+ public struct Vector
+ {
+ ///
+ /// The X vector.
+ ///
+ private double x;
+
+ ///
+ /// The Y vector.
+ ///
+ private double y;
+
+ ///
+ /// Initializes a new instance of the structure.
+ ///
+ /// The X vector.
+ /// The Y vector.
+ public Vector(double x, double y)
+ {
+ this.x = x;
+ this.y = y;
+ }
+
+ ///
+ /// Gets the X vector.
+ ///
+ public double X
+ {
+ get { return this.x; }
+ }
+
+ ///
+ /// Gets the Y vector.
+ ///
+ public double Y
+ {
+ get { return this.y; }
+ }
+
+ public static Vector operator +(Vector a, Vector b)
+ {
+ return new Vector(a.x + b.x, a.y + b.y);
+ }
+
+ public static Vector operator -(Vector a, Vector b)
+ {
+ return new Vector(a.x - b.x, a.y - b.y);
+ }
+
+ ///
+ /// Returns the string representation of the point.
+ ///
+ /// The string representation of the point.
+ public override string ToString()
+ {
+ return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", this.x, this.y);
+ }
+ }
+}
diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs
index 325d5da082..1e820bbb29 100644
--- a/TestApplication/Program.cs
+++ b/TestApplication/Program.cs
@@ -10,6 +10,7 @@ using Perspex.Controls;
using Perspex.Input;
using Perspex.Layout;
using Perspex.Media;
+using Perspex.Media.Imaging;
using Perspex.Shapes;
using Perspex.Styling;
using Perspex.Themes.Default;
@@ -69,6 +70,11 @@ namespace TestApplication
new TextBox
{
Text = "Hello World!",
+ },
+ new Image
+ {
+ Source = new Bitmap("github_icon.png"),
+ Width = 200,
}
}
}
diff --git a/TestApplication/TestApplication.csproj b/TestApplication/TestApplication.csproj
index f7524686ca..2b5eb4fd9d 100644
--- a/TestApplication/TestApplication.csproj
+++ b/TestApplication/TestApplication.csproj
@@ -98,6 +98,11 @@
Perspex
+
+
+ PreserveNewest
+
+