Add transparent bitmap support for credential tiles
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace Lithnet.CredentialProvider.UnitTests
|
||||||
|
{
|
||||||
|
public class BitmapControlTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TransparentBufferPreservesAlphaChannel()
|
||||||
|
{
|
||||||
|
using (Bitmap source = new Bitmap(2, 1, PixelFormat.Format32bppArgb))
|
||||||
|
{
|
||||||
|
source.SetPixel(0, 0, Color.FromArgb(0, 10, 20, 30));
|
||||||
|
source.SetPixel(1, 0, Color.FromArgb(128, 40, 50, 60));
|
||||||
|
|
||||||
|
var control = new UserTileControl("image", "Image", source);
|
||||||
|
|
||||||
|
byte[] bytes = GetBitmapBuffer(control);
|
||||||
|
|
||||||
|
Assert.That(bytes, Has.Length.GreaterThan(8));
|
||||||
|
Assert.That(bytes[0], Is.EqualTo(0x89));
|
||||||
|
Assert.That(bytes[1], Is.EqualTo(0x50));
|
||||||
|
Assert.That(bytes[2], Is.EqualTo(0x4e));
|
||||||
|
Assert.That(bytes[3], Is.EqualTo(0x47));
|
||||||
|
|
||||||
|
using (MemoryStream stream = new MemoryStream(bytes))
|
||||||
|
using (Bitmap decoded = new Bitmap(stream))
|
||||||
|
{
|
||||||
|
Assert.That(decoded.GetPixel(0, 0).A, Is.EqualTo(0));
|
||||||
|
Assert.That(decoded.GetPixel(1, 0).A, Is.EqualTo(128));
|
||||||
|
Assert.That(decoded.GetPixel(1, 0).R, Is.EqualTo(40));
|
||||||
|
Assert.That(decoded.GetPixel(1, 0).G, Is.EqualTo(50));
|
||||||
|
Assert.That(decoded.GetPixel(1, 0).B, Is.EqualTo(60));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void BitmapBufferDoesNotApplyConfiguredBackgroundColor()
|
||||||
|
{
|
||||||
|
using (Bitmap source = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
|
||||||
|
{
|
||||||
|
source.SetPixel(0, 0, Color.Transparent);
|
||||||
|
|
||||||
|
var control = new UserTileControl("image", "Image", source)
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.FromArgb(12, 34, 56)
|
||||||
|
};
|
||||||
|
|
||||||
|
byte[] bytes = GetBitmapBuffer(control);
|
||||||
|
|
||||||
|
using (MemoryStream stream = new MemoryStream(bytes))
|
||||||
|
using (Bitmap decoded = new Bitmap(stream))
|
||||||
|
{
|
||||||
|
Assert.That(decoded.GetPixel(0, 0).A, Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CloneCopiesBitmapAndBackgroundColor()
|
||||||
|
{
|
||||||
|
using (Bitmap source = new Bitmap(1, 1))
|
||||||
|
{
|
||||||
|
var control = new UserTileControl("image", "Image", source)
|
||||||
|
{
|
||||||
|
BackgroundColor = Color.CornflowerBlue
|
||||||
|
};
|
||||||
|
|
||||||
|
var clone = (UserTileControl)control.Clone();
|
||||||
|
|
||||||
|
Assert.That(clone.Bitmap, Is.SameAs(source));
|
||||||
|
Assert.That(clone.BackgroundColor, Is.EqualTo(Color.CornflowerBlue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GetBitmapBuffer(BitmapControl control)
|
||||||
|
{
|
||||||
|
IntPtr buffer = control.GetBitmapBuffer(out uint size);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[size];
|
||||||
|
Marshal.Copy(buffer, bytes, 0, checked((int)size));
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Marshal.FreeCoTaskMem(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
|
<ProjectReference Include="..\Lithnet.CredentialProvider\Lithnet.CredentialProvider.csproj" />
|
||||||
|
<Compile Include="..\Lithnet.CredentialProvider.UnitTests.x64\BitmapControlTests.cs" Link="BitmapControlTests.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -22,11 +22,16 @@ namespace Lithnet.CredentialProvider
|
|||||||
this.backgroundColor = Color.FromArgb(70, 70, 70);
|
this.backgroundColor = Color.FromArgb(70, 70, 70);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BitmapControl(BitmapControl source) : base(source) { }
|
protected BitmapControl(BitmapControl source) : base(source)
|
||||||
|
{
|
||||||
|
this.bitmap = source.bitmap;
|
||||||
|
this.backgroundColor = source.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the background color that should replace any transparent elements of the image. This defaults to #707070
|
/// Specifies the background color used to replace transparent pixels for <see cref="CredentialTile"/> and <see cref="CredentialTile2"/>. This defaults to #707070.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>This property does not apply to <see cref="CredentialTile3"/>.</remarks>
|
||||||
public Color BackgroundColor
|
public Color BackgroundColor
|
||||||
{
|
{
|
||||||
get { return this.backgroundColor; }
|
get { return this.backgroundColor; }
|
||||||
@@ -53,10 +58,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
{
|
{
|
||||||
this.bitmap = value;
|
this.bitmap = value;
|
||||||
|
|
||||||
if (this.Events is ICredentialProviderCredentialEvents2 e)
|
this.UpdateBitmap();
|
||||||
{
|
|
||||||
e.SetFieldBitmap(this.Credential, this.Id, this.GetHBitmap());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.RaisePropertyChanged();
|
this.RaisePropertyChanged();
|
||||||
}
|
}
|
||||||
@@ -76,26 +78,48 @@ namespace Lithnet.CredentialProvider
|
|||||||
internal IntPtr GetBitmapBuffer(out uint size)
|
internal IntPtr GetBitmapBuffer(out uint size)
|
||||||
{
|
{
|
||||||
size = 0;
|
size = 0;
|
||||||
var hbitmap = this.GetHBitmap();
|
|
||||||
|
|
||||||
if (hbitmap == IntPtr.Zero)
|
if (this.bitmap == null)
|
||||||
{
|
{
|
||||||
return IntPtr.Zero;
|
return IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
var image = Bitmap.FromHbitmap(hbitmap);
|
|
||||||
|
|
||||||
IntPtr buffer = IntPtr.Zero;
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (MemoryStream ms = new MemoryStream())
|
||||||
{
|
{
|
||||||
image.Save(ms, ImageFormat.Bmp);
|
this.bitmap.Save(ms, ImageFormat.Png);
|
||||||
var bitmapBytes = ms.ToArray();
|
var bitmapBytes = ms.ToArray();
|
||||||
size = (uint)bitmapBytes.Length;
|
size = checked((uint)bitmapBytes.Length);
|
||||||
buffer = Marshal.AllocCoTaskMem(bitmapBytes.Length);
|
IntPtr buffer = Marshal.AllocCoTaskMem(bitmapBytes.Length);
|
||||||
Marshal.Copy(bitmapBytes, 0, buffer, bitmapBytes.Length);
|
Marshal.Copy(bitmapBytes, 0, buffer, bitmapBytes.Length);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBitmap()
|
||||||
|
{
|
||||||
|
if (this.Credential is ICredentialProviderCredential3 && this.Events is ICredentialProviderCredentialEvents3 events3)
|
||||||
|
{
|
||||||
|
IntPtr buffer = this.GetBitmapBuffer(out uint size);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
events3.SetFieldBitmapBuffer(this.Credential, this.Id, size, buffer);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (buffer != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.FreeCoTaskMem(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer;
|
if (this.Events is ICredentialProviderCredentialEvents2 events2)
|
||||||
|
{
|
||||||
|
events2.SetFieldBitmap(this.Credential, this.Id, this.GetHBitmap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,10 +35,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
|
|
||||||
internal override ControlBase Clone()
|
internal override ControlBase Clone()
|
||||||
{
|
{
|
||||||
var clone = new CredentialProviderLogoControl(this);
|
return new CredentialProviderLogoControl(this);
|
||||||
clone.Bitmap = this.Bitmap;
|
|
||||||
clone.BackgroundColor = this.BackgroundColor;
|
|
||||||
return clone;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
|
|
||||||
internal override ControlBase Clone()
|
internal override ControlBase Clone()
|
||||||
{
|
{
|
||||||
var clone = new UserTileControl(this);
|
return new UserTileControl(this);
|
||||||
clone.Bitmap = this.Bitmap;
|
|
||||||
clone.BackgroundColor = this.BackgroundColor;
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ using Lithnet.CredentialProvider.Interop;
|
|||||||
|
|
||||||
namespace Lithnet.CredentialProvider
|
namespace Lithnet.CredentialProvider
|
||||||
{
|
{
|
||||||
internal abstract partial class CredentialTile3 : ICredentialProviderCredential3
|
public abstract partial class CredentialTile3 : ICredentialProviderCredential3
|
||||||
{
|
{
|
||||||
int ICredentialProviderCredential3.GetBitmapBufferValue(uint dwFieldID, out uint pImageBufferSize, out IntPtr ppImageBuffer)
|
int ICredentialProviderCredential3.GetBitmapBufferValue(uint dwFieldID, out uint pImageBufferSize, out IntPtr ppImageBuffer)
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
|
|
||||||
if (this.Controls.TryGetControl<BitmapControl>(dwFieldID, FieldType.TileImage, out var instance))
|
if (this.Controls.TryGetControl<BitmapControl>(dwFieldID, FieldType.TileImage, out var instance))
|
||||||
{
|
{
|
||||||
var hbitmap = instance.GetBitmapBuffer(out pImageBufferSize);
|
ppImageBuffer = instance.GetBitmapBuffer(out pImageBufferSize);
|
||||||
return HRESULT.S_OK;
|
return HRESULT.S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Lithnet.CredentialProvider
|
|||||||
/// Represents a user credential tile that implements the functionality of <see cref="CredentialTile"/> and <see cref="CredentialTile2"/>, but includes support for dynamically updating bitmap images.
|
/// Represents a user credential tile that implements the functionality of <see cref="CredentialTile"/> and <see cref="CredentialTile2"/>, but includes support for dynamically updating bitmap images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>This interface is public, but undocumented by Microsoft. It is recommended to use <see cref="CredentialTile2"/> tiles unless this specific functionality is needed</remarks>
|
/// <remarks>This interface is public, but undocumented by Microsoft. It is recommended to use <see cref="CredentialTile2"/> tiles unless this specific functionality is needed</remarks>
|
||||||
internal abstract partial class CredentialTile3 : CredentialTile2
|
public abstract partial class CredentialTile3 : CredentialTile2
|
||||||
{
|
{
|
||||||
protected CredentialTile3(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
|
protected CredentialTile3(CredentialProviderBase credentialProvider) : this(credentialProvider, null) { }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user