From dd25cffdcce3886c4a9906bee7b319ce67ffb37b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 13 Feb 2017 02:13:13 +0300 Subject: [PATCH] [WIN32_CORE] Use flat GDI+ API for icon loading --- .../Avalonia.Win32.NetStandard.csproj | 2 + .../ComStreamWrapper.cs | 197 ++++++++++++++++++ .../Avalonia.Win32.NetStandard/Gdip.cs | 128 ++++++++++++ .../Avalonia.Win32.NetStandard/IconImpl.cs | 30 ++- .../NativeWin32Platform.cs | 16 +- 5 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 src/Windows/Avalonia.Win32.NetStandard/ComStreamWrapper.cs create mode 100644 src/Windows/Avalonia.Win32.NetStandard/Gdip.cs diff --git a/src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj b/src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj index e4a34a8b91..95d1b8a345 100644 --- a/src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj +++ b/src/Windows/Avalonia.Win32.NetStandard/Avalonia.Win32.NetStandard.csproj @@ -75,6 +75,8 @@ + + diff --git a/src/Windows/Avalonia.Win32.NetStandard/ComStreamWrapper.cs b/src/Windows/Avalonia.Win32.NetStandard/ComStreamWrapper.cs new file mode 100644 index 0000000000..083021f58d --- /dev/null +++ b/src/Windows/Avalonia.Win32.NetStandard/ComStreamWrapper.cs @@ -0,0 +1,197 @@ +// +// System.Drawing.ComIStreamWrapper.cs +// +// Author: +// Kornél Pál +// +// Copyright (C) 2005-2008 Kornél Pál +// + +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using STATSTG = System.Runtime.InteropServices.ComTypes.STATSTG; + +namespace Avalonia.Win32 +{ + // Stream to IStream wrapper for COM interop + internal sealed class ComIStreamWrapper : IStream + { + private const int STG_E_INVALIDFUNCTION = unchecked((int)0x80030001); + + private readonly Stream baseStream; + private long position = -1; + + internal ComIStreamWrapper(Stream stream) + { + baseStream = stream; + } + + private void SetSizeToPosition() + { + if (position != -1) + { + if (position > baseStream.Length) + baseStream.SetLength(position); + baseStream.Position = position; + position = -1; + } + } + + public void Read(byte[] pv, int cb, IntPtr pcbRead) + { + int read = 0; + + if (cb != 0) + { + SetSizeToPosition(); + read = baseStream.Read(pv, 0, cb); + } + + if (pcbRead != IntPtr.Zero) + Marshal.WriteInt32(pcbRead, read); + } + + public void Write(byte[] pv, int cb, IntPtr pcbWritten) + { + if (cb != 0) + { + SetSizeToPosition(); + baseStream.Write(pv, 0, cb); + } + + if (pcbWritten != IntPtr.Zero) + Marshal.WriteInt32(pcbWritten, cb); + } + + public void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) + { + long length = baseStream.Length; + long newPosition; + + switch ((SeekOrigin)dwOrigin) + { + case SeekOrigin.Begin: + newPosition = dlibMove; + break; + case SeekOrigin.Current: + if (position == -1) + newPosition = baseStream.Position + dlibMove; + else + newPosition = position + dlibMove; + break; + case SeekOrigin.End: + newPosition = length + dlibMove; + break; + default: + throw new COMException(null, STG_E_INVALIDFUNCTION); + } + + if (newPosition > length) + position = newPosition; + else + { + baseStream.Position = newPosition; + position = -1; + } + + if (plibNewPosition != IntPtr.Zero) + Marshal.WriteInt64(plibNewPosition, newPosition); + } + + public void SetSize(long libNewSize) + { + baseStream.SetLength(libNewSize); + } + + public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) + { + byte[] buffer; + long written = 0; + int read; + int count; + + if (cb != 0) + { + if (cb < 4096) + count = (int)cb; + else + count = 4096; + buffer = new byte[count]; + SetSizeToPosition(); + while (true) + { + if ((read = baseStream.Read(buffer, 0, count)) == 0) + break; + pstm.Write(buffer, read, IntPtr.Zero); + written += read; + if (written >= cb) + break; + if (cb - written < 4096) + count = (int)(cb - written); + } + } + + if (pcbRead != IntPtr.Zero) + Marshal.WriteInt64(pcbRead, written); + if (pcbWritten != IntPtr.Zero) + Marshal.WriteInt64(pcbWritten, written); + } + + public void Commit(int grfCommitFlags) + { + baseStream.Flush(); + SetSizeToPosition(); + } + + public void Revert() + { + throw new COMException(null, STG_E_INVALIDFUNCTION); + } + + public void LockRegion(long libOffset, long cb, int dwLockType) + { + throw new COMException(null, STG_E_INVALIDFUNCTION); + } + + public void UnlockRegion(long libOffset, long cb, int dwLockType) + { + throw new COMException(null, STG_E_INVALIDFUNCTION); + } + + public void Stat(out STATSTG pstatstg, int grfStatFlag) + { + pstatstg = new STATSTG(); + pstatstg.cbSize = baseStream.Length; + } + + public void Clone(out IStream ppstm) + { + ppstm = null; + throw new COMException(null, STG_E_INVALIDFUNCTION); + } + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32.NetStandard/Gdip.cs b/src/Windows/Avalonia.Win32.NetStandard/Gdip.cs new file mode 100644 index 0000000000..b3d1c28689 --- /dev/null +++ b/src/Windows/Avalonia.Win32.NetStandard/Gdip.cs @@ -0,0 +1,128 @@ +// +// Code copy-pasted from from Mono / System.Drawing.*.cs +// Original license below: +// +// Authors: +// Alexandre Pigolkine (pigolkine@gmx.de) +// Jordi Mas (jordi@ximian.com) +// Sanjay Gupta (gsanjay@novell.com) +// Ravindra (rkumar@novell.com) +// Peter Dennis Bartok (pbartok@novell.com) +// Sebastien Pouliot +// +// +// Copyright (C) 2004, 2007 Novell, Inc (http://www.novell.com) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Win32 +{ + static class Gdip + { + public enum Status + { + Ok = 0, + GenericError = 1, + InvalidParameter = 2, + OutOfMemory = 3, + ObjectBusy = 4, + InsufficientBuffer = 5, + NotImplemented = 6, + Win32Error = 7, + WrongState = 8, + Aborted = 9, + FileNotFound = 10, + ValueOverflow = 11, + AccessDenied = 12, + UnknownImageFormat = 13, + FontFamilyNotFound = 14, + FontStyleNotFound = 15, + NotTrueTypeFont = 16, + UnsupportedGdiplusVersion = 17, + GdiplusNotInitialized = 18, + PropertyNotFound = 19, + PropertyNotSupported = 20, + ProfileNotFound = 21 + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GdiplusStartupInput + { + // internalted to silent compiler + internal uint GdiplusVersion; + internal IntPtr DebugEventCallback; + internal int SuppressBackgroundThread; + internal int SuppressExternalCodecs; + + internal static GdiplusStartupInput MakeGdiplusStartupInput() + { + GdiplusStartupInput result = new GdiplusStartupInput(); + result.GdiplusVersion = 1; + result.DebugEventCallback = IntPtr.Zero; + result.SuppressBackgroundThread = 0; + result.SuppressExternalCodecs = 0; + return result; + } + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GdiplusStartupOutput + { + internal IntPtr NotificationHook; + internal IntPtr NotificationUnhook; + + internal static GdiplusStartupOutput MakeGdiplusStartupOutput() + { + GdiplusStartupOutput result = new GdiplusStartupOutput(); + result.NotificationHook = result.NotificationUnhook = IntPtr.Zero; + return result; + } + } + + + [DllImport("gdiplus.dll")] + public static extern Status GdiplusStartup(ref ulong token, ref GdiplusStartupInput input, ref GdiplusStartupOutput output); + + [DllImport("gdiplus.dll", ExactSpelling = true, CharSet = CharSet.Unicode)] + public static extern Status GdipLoadImageFromStream([MarshalAs(UnmanagedType.Interface, MarshalTypeRef = typeof(IStream))] IStream stream, out IntPtr image); + [DllImport("gdiplus.dll")] + public static extern Status GdipCreateHICONFromBitmap(IntPtr bmp, out IntPtr HandleIcon); + + [DllImport("gdiplus.dll")] + internal static extern Status GdipDisposeImage(IntPtr image); + + static Gdip() + { + ulong token = 0; + var input = GdiplusStartupInput.MakeGdiplusStartupInput(); + var output = GdiplusStartupOutput.MakeGdiplusStartupOutput(); + GdiplusStartup(ref token, ref input, ref output); + } + } +} diff --git a/src/Windows/Avalonia.Win32.NetStandard/IconImpl.cs b/src/Windows/Avalonia.Win32.NetStandard/IconImpl.cs index f21737027f..49d039e655 100644 --- a/src/Windows/Avalonia.Win32.NetStandard/IconImpl.cs +++ b/src/Windows/Avalonia.Win32.NetStandard/IconImpl.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Threading.Tasks; using Avalonia.Platform; @@ -10,10 +12,34 @@ namespace Avalonia.Win32 { public class IconImpl : IWindowIconImpl { - public IntPtr HIcon { get; set; } + private readonly MemoryStream _ms; + + public IconImpl(Stream data) + { + _ms = new MemoryStream(); + data.CopyTo(_ms); + _ms.Seek(0, SeekOrigin.Begin); + IntPtr bitmap; + var status = Gdip.GdipLoadImageFromStream(new ComIStreamWrapper(_ms), out bitmap); + if (status != Gdip.Status.Ok) + throw new Exception("Unable to load icon, gdip status: " + (int) status); + IntPtr icon; + status = Gdip.GdipCreateHICONFromBitmap(bitmap, out icon); + if (status != Gdip.Status.Ok) + throw new Exception("Unable to create HICON, gdip status: " + (int)status); + Gdip.GdipDisposeImage(bitmap); + HIcon = icon; + } + + public IntPtr HIcon { get;} public void Save(Stream outputStream) { - throw new NotImplementedException(); + lock (_ms) + { + _ms.Seek(0, SeekOrigin.Begin); + _ms.CopyTo(outputStream); + } } + } } diff --git a/src/Windows/Avalonia.Win32.NetStandard/NativeWin32Platform.cs b/src/Windows/Avalonia.Win32.NetStandard/NativeWin32Platform.cs index a269334be7..2695a5b8b6 100644 --- a/src/Windows/Avalonia.Win32.NetStandard/NativeWin32Platform.cs +++ b/src/Windows/Avalonia.Win32.NetStandard/NativeWin32Platform.cs @@ -11,10 +11,20 @@ namespace Avalonia.Win32 partial class Win32Platform { //TODO: An actual implementation - public IWindowIconImpl LoadIcon(string fileName) => new IconImpl(); + public IWindowIconImpl LoadIcon(string fileName) + { + //No file IO for netstandard, still waiting for proper net core tooling + throw new NotSupportedException(); + } - public IWindowIconImpl LoadIcon(Stream stream) => new IconImpl(); + public IWindowIconImpl LoadIcon(Stream stream) => new IconImpl(stream); - public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconImpl(); + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + var ms = new MemoryStream(); + bitmap.Save(ms); + ms.Seek(0, SeekOrigin.Begin); + return new IconImpl(ms); + } } }