diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 38619fe276..2f8aeff451 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -76,5 +76,10 @@ namespace Avalonia.Android { return null; } + + public IIconImpl LoadIcon(IBitmapImpl bitmap) + { + return null; + } } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Icon.cs b/src/Avalonia.Controls/Icon.cs index f7a55e0478..3d4f9a3d52 100644 --- a/src/Avalonia.Controls/Icon.cs +++ b/src/Avalonia.Controls/Icon.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Media.Imaging; namespace Avalonia.Controls { @@ -13,6 +14,11 @@ namespace Avalonia.Controls /// public class Icon { + public Icon(IBitmap bitmap) + { + PlatformImpl = AvaloniaLocator.Current.GetService().LoadIcon(bitmap.PlatformImpl); + } + public Icon(string fileName) { PlatformImpl = AvaloniaLocator.Current.GetService().LoadIcon(fileName); diff --git a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs index 4a22c3e7a8..7c604dc4a3 100644 --- a/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs +++ b/src/Avalonia.Controls/Platform/IPlatformIconLoader.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Media.Imaging; namespace Avalonia.Platform { @@ -11,5 +12,6 @@ namespace Avalonia.Platform { IIconImpl LoadIcon(string fileName); IIconImpl LoadIcon(Stream stream); + IIconImpl LoadIcon(IBitmapImpl bitmap); } } diff --git a/src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs b/src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs index 3473c4a094..1800110e68 100644 --- a/src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs @@ -1,6 +1,7 @@ // 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; using System.IO; using Avalonia.Platform; @@ -66,5 +67,10 @@ namespace Avalonia.Media.Imaging { PlatformImpl.Save(fileName); } + + public void Save(Stream stream) + { + PlatformImpl.Save(stream); + } } } diff --git a/src/Avalonia.SceneGraph/Media/Imaging/IBitmap.cs b/src/Avalonia.SceneGraph/Media/Imaging/IBitmap.cs index b07858799a..5a10145d84 100644 --- a/src/Avalonia.SceneGraph/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.SceneGraph/Media/Imaging/IBitmap.cs @@ -1,6 +1,7 @@ // 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; namespace Avalonia.Media.Imaging @@ -30,5 +31,11 @@ namespace Avalonia.Media.Imaging /// /// The filename. void Save(string fileName); + + /// + /// Saves the bitmap to a stream in png format. + /// + /// The stream. + void Save(Stream stream); } } diff --git a/src/Avalonia.SceneGraph/Platform/IBitmapImpl.cs b/src/Avalonia.SceneGraph/Platform/IBitmapImpl.cs index fb47147907..7a552ed643 100644 --- a/src/Avalonia.SceneGraph/Platform/IBitmapImpl.cs +++ b/src/Avalonia.SceneGraph/Platform/IBitmapImpl.cs @@ -1,6 +1,8 @@ // 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; + namespace Avalonia.Platform { /// @@ -23,5 +25,11 @@ namespace Avalonia.Platform /// /// The filename. void Save(string fileName); + + /// + /// Saves the bitmap to a stream. + /// + /// The stream. + void Save(Stream stream); } } diff --git a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs index 696274ed69..63d5e32bd6 100644 --- a/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs +++ b/src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs @@ -85,25 +85,25 @@ namespace Avalonia.Cairo.Media _context.Scale(scale.X, scale.Y); destRect /= scale; - if (opacityOverride < 1.0f) { - _context.PushGroup (); - Gdk.CairoHelper.SetSourcePixbuf ( - _context, - impl.Surface, - -sourceRect.X + destRect.X, - -sourceRect.Y + destRect.Y); - - _context.Rectangle (destRect.ToCairo ()); - _context.Fill (); - _context.PopGroupToSource (); - _context.PaintWithAlpha (opacityOverride); - } else { - _context.PushGroup (); - Gdk.CairoHelper.SetSourcePixbuf ( - _context, - impl.Surface, - -sourceRect.X + destRect.X, - -sourceRect.Y + destRect.Y); + if (opacityOverride < 1.0f) { + _context.PushGroup (); + Gdk.CairoHelper.SetSourcePixbuf ( + _context, + impl, + -sourceRect.X + destRect.X, + -sourceRect.Y + destRect.Y); + + _context.Rectangle (destRect.ToCairo ()); + _context.Fill (); + _context.PopGroupToSource (); + _context.PaintWithAlpha (opacityOverride); + } else { + _context.PushGroup (); + Gdk.CairoHelper.SetSourcePixbuf ( + _context, + impl, + -sourceRect.X + destRect.X, + -sourceRect.Y + destRect.Y); _context.Rectangle (destRect.ToCairo ()); _context.Fill (); diff --git a/src/Gtk/Avalonia.Cairo/Media/Imaging/BitmapImpl.cs b/src/Gtk/Avalonia.Cairo/Media/Imaging/BitmapImpl.cs index 1ae0ab8ae3..fd98cd1d71 100644 --- a/src/Gtk/Avalonia.Cairo/Media/Imaging/BitmapImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/Imaging/BitmapImpl.cs @@ -6,28 +6,30 @@ using Avalonia.Platform; namespace Avalonia.Cairo.Media.Imaging { + using System.IO; using Cairo = global::Cairo; - public class BitmapImpl : IBitmapImpl + public class BitmapImpl : Gdk.Pixbuf, IBitmapImpl { public BitmapImpl(Gdk.Pixbuf pixbuf) + :base(pixbuf, 0, 0, pixbuf.Width, pixbuf.Height) { - Surface = pixbuf; } - public int PixelWidth => Surface.Width; + public int PixelWidth => Width; - public int PixelHeight => Surface.Height; + public int PixelHeight => Height; - public Gdk.Pixbuf Surface + public void Save(string fileName) { - get; + // TODO: Test + Save(fileName, "png"); } - public void Save(string fileName) + public void Save(Stream stream) { - // TODO: Test - Surface.Save(fileName, "png"); + var buffer = SaveToBuffer("png"); + stream.Write(buffer, 0, buffer.Length); } } } diff --git a/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs b/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs index 4e7e25bd38..db0aa082cc 100644 --- a/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/Imaging/RenderTargetBitmapImpl.cs @@ -8,6 +8,7 @@ using Avalonia.Rendering; namespace Avalonia.Cairo.Media.Imaging { + using System.IO; using Cairo = global::Cairo; public class RenderTargetBitmapImpl : IRenderTargetBitmapImpl @@ -43,5 +44,15 @@ namespace Avalonia.Cairo.Media.Imaging { return _renderTarget.CreateDrawingContext(); } + + public void Save(Stream stream) + { + var tempFileName = Path.GetTempFileName(); + Surface.WriteToPng(tempFileName); + using (var tempFile = new FileStream(tempFileName, FileMode.Create)) + { + tempFile.CopyTo(stream); + } + } } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs index 27216c3f58..9030b0138b 100644 --- a/src/Gtk/Avalonia.Gtk/GtkPlatform.cs +++ b/src/Gtk/Avalonia.Gtk/GtkPlatform.cs @@ -124,5 +124,21 @@ namespace Avalonia.Gtk { return new IconImpl(new Gdk.Pixbuf(stream)); } + + public IIconImpl LoadIcon(IBitmapImpl bitmap) + { + if (bitmap is Gdk.Pixbuf) + { + return new IconImpl((Gdk.Pixbuf)bitmap); + } + else + { + using (var memoryStream = new MemoryStream()) + { + bitmap.Save(memoryStream); + return new IconImpl(new Gdk.Pixbuf(memoryStream)); + } + } + } } } \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs index 2c654aff18..5d2698116b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Avalonia.Controls; +using Avalonia.Media.Imaging; using Avalonia.Platform; using OmniXaml.TypeConversion; @@ -14,7 +16,7 @@ namespace Avalonia.Markup.Xaml.Converters { public bool CanConvertFrom(IValueContext context, Type sourceType) { - return sourceType == typeof(string); + return sourceType == typeof(string) || typeof(IBitmap).GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()); } public bool CanConvertTo(IValueContext context, Type destinationType) @@ -24,14 +26,34 @@ namespace Avalonia.Markup.Xaml.Converters public object ConvertFrom(IValueContext context, CultureInfo culture, object value) { - var uri = new Uri((string)value, UriKind.RelativeOrAbsolute); + var path = value as string; + if (path != null) + { + return CreateIconFromPath(context, path); + } + var bitmap = value as IBitmap; + if (bitmap != null) + { + return CreateIconFromBitmap(bitmap); + } + throw new NotSupportedException(); + } + + private Icon CreateIconFromBitmap(IBitmap bitmap) + { + return new Icon(bitmap); + } + + private Icon CreateIconFromPath(IValueContext context, string path) + { + var uri = new Uri(path, UriKind.RelativeOrAbsolute); var baseUri = GetBaseUri(context); var scheme = uri.IsAbsoluteUri ? uri.Scheme : "file"; switch (scheme) { case "file": - return new Icon((string)value); + return new Icon(path); default: var assets = AvaloniaLocator.Current.GetService(); return new Icon(assets.Open(uri, baseUri)); diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index e7c3abdf9f..fc9963fecc 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -80,5 +80,14 @@ namespace Avalonia.Skia return new DrawingContext(new BitmapDrawingContext(Bitmap)); } + public void Save(Stream stream) + { + IntPtr length; + using (var image = SKImage.FromPixels(Bitmap.Info, Bitmap.GetPixels(out length), Bitmap.RowBytes)) + using (var data = image.Encode()) + { + data.SaveTo(stream); + } + } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 2251ccb831..d0f0aaff21 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -117,15 +117,20 @@ namespace Avalonia.Direct2D1.Media using (FileStream s = new FileStream(fileName, FileMode.Create)) { - PngBitmapEncoder encoder = new PngBitmapEncoder(_factory); - encoder.Initialize(s); - - BitmapFrameEncode frame = new BitmapFrameEncode(encoder); - frame.Initialize(); - frame.WriteSource(WicImpl); - frame.Commit(); - encoder.Commit(); + Save(s); } } + + public void Save(Stream stream) + { + PngBitmapEncoder encoder = new PngBitmapEncoder(_factory); + encoder.Initialize(stream); + + BitmapFrameEncode frame = new BitmapFrameEncode(encoder); + frame.Initialize(); + frame.WriteSource(WicImpl); + frame.Commit(); + encoder.Commit(); + } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 0ed8dbc02a..5a1cca8f07 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -200,5 +200,14 @@ namespace Avalonia.Win32 var icon = new System.Drawing.Bitmap(stream); return new IconImpl(icon); } + + public IIconImpl LoadIcon(IBitmapImpl bitmap) + { + using (var memoryStream = new MemoryStream()) + { + bitmap.Save(memoryStream); + return new IconImpl(new System.Drawing.Bitmap(memoryStream)); + } + } } } diff --git a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs index 9cf60e7ce4..620aa67c05 100644 --- a/src/iOS/Avalonia.iOS/PlatformIconLoader.cs +++ b/src/iOS/Avalonia.iOS/PlatformIconLoader.cs @@ -8,6 +8,11 @@ namespace Avalonia.iOS { class PlatformIconLoader : IPlatformIconLoader { + public IIconImpl LoadIcon(IBitmapImpl bitmap) + { + return null; + } + public IIconImpl LoadIcon(Stream stream) { return null;