Get WebView2 page's favicon and convert .ICO to .PNG format
See the question and my original answer on StackOverflowA site can define a Favicon (or not) using many ways, so here is a code that directly uses WebView2's FavIcon features: the FaviconChanged Event the FaviconUri Property and the GetFaviconAsync() Method.
So you can just use this:
tabViewItem.IconSource = new BitmapIconSource
{
UriSource = new Uri(MyWebView.CoreWebView2.FaviconUri),
ShowAsMonochrome = false,
};
I've been one step further in my context, as I wanted to use the favicon for the application icon too. This uses WinUI3's AppWindow.SetIcon method.
The tricky part is AppWindow.SetIcon
only supports a file path to a .ico format file while WebView2 only support PNG or JPG streams. Luckily, .ico files can be encoded using PNG bytes (since Windows Vista).
Xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="url" />
<Button x:Name="navigate" Click="navigate_Click">Go</Button>
</StackPanel>
<controls:WebView2
x:Name="MyWebView"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
C#:
In the following code, I avoid rewriting the .ico for a given favicon uri
each time it changes but this is up to you.
public MainWindow()
{
InitializeComponent();
MyWebView.NavigationCompleted += MyWebView_NavigationCompleted;
}
private async void navigate_Click(object sender, RoutedEventArgs e)
{
await MyWebView.EnsureCoreWebView2Async();
MyWebView.CoreWebView2.Navigate(url.Text);
}
private void MyWebView_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
{
// unhook from ourselves, we shouldn't need it anymore
MyWebView.NavigationCompleted -= MyWebView_NavigationCompleted;
// hook on favicon changes
MyWebView.CoreWebView2.FaviconChanged += (s, e) => _ = UpdateFavicon();
// first time set
_ = UpdateFavicon();
}
private async ValueTask UpdateFavicon()
{
var uri = MyWebView.CoreWebView2.FaviconUri;
// in your TabViewItem case, this is where you would do
// tabViewItem.IconSource = new BitmapIconSource...
if (string.IsNullOrEmpty(uri))
{
// Note: when the site has no favicon (like with www.example.com),
// uri is the empty string and GetFaviconAsync will return an
// "undefined" icon stream that can still be used to save a .ico.
// Here I just remove the icon but we could continue
// and use this "undefined" icon instead.
AppWindow.SetIcon(null);
return;
}
// come up with a unique (cache) file path
using var md5 = MD5.Create();
var name = new Guid(md5.ComputeHash(Encoding.UTF8.GetBytes(uri))) + ".ico";
var path = Path.Combine(Path.GetTempPath(), name);
// don't rewrite if already there
if (!File.Exists(path))
{
// get PNG bytes
using var iconStream = await MyWebView.CoreWebView2.GetFaviconAsync(CoreWebView2FaviconImageFormat.Png);
// write to memory (shouldn't be too big)
using var ms = new MemoryStream();
using var stream = iconStream.AsStream();
await stream.CopyToAsync(ms);
stream.Position = 0;
// decode it to determine width & height
// seems WebView2 sends always 16x16 streams but not sure it's contractual
var decoder = await BitmapDecoder.CreateAsync(ms.AsRandomAccessStream());
// write icon in PNG format
WritePNGIcon(path, decoder.PixelWidth, decoder.PixelHeight, ms.ToArray());
}
AppWindow.SetIcon(path);
}
public static void WritePNGIcon(string iconFilePath, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue)
{
ArgumentNullException.ThrowIfNull(iconFilePath);
ArgumentNullException.ThrowIfNull(pngBytes);
using var stream = File.OpenWrite(iconFilePath);
WritePNGIcon(stream, width, height, pngBytes, pngBytesIndex, pngBytesCount);
}
public static void WritePNGIcon(Stream iconFileStream, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue)
{
ArgumentNullException.ThrowIfNull(iconFileStream);
ArgumentNullException.ThrowIfNull(pngBytes);
if (pngBytesCount == int.MaxValue || pngBytesCount < 0)
{
pngBytesCount = pngBytes.Length;
}
var startPos = iconFileStream.Position;
var writer = new BinaryWriter(iconFileStream);
writer.Write((short)0); // reserved
writer.Write((short)1); // image type (1 for .ico)
writer.Write((short)1); // count
const int maxIconSize = 256;
writer.Write(width == maxIconSize ? (byte)0 : (byte)width);
writer.Write(height == maxIconSize ? (byte)0 : (byte)height);
writer.Write((byte)0); // number of colors in the color palette
writer.Write((byte)0); // reserved
writer.Write((short)1); // color planes
writer.Write((short)32); // bits per pixel (ARGB=32)
var offsets = iconFileStream.Position;
writer.Write(0); // size of image data, we'll rewrite that later
writer.Write(0); // offset of data from beginning;
var pngOffset = iconFileStream.Position - startPos;
writer.Write(pngBytes, pngBytesIndex, pngBytesCount);
// save end position
var endPos = iconFileStream.Position;
iconFileStream.Seek(offsets, SeekOrigin.Begin);
writer.Write(pngBytesCount);
writer.Write((int)pngOffset);
iconFileStream.Position = endPos; // restore end position
}
Results on www.stackoverflow.com:
Note: the .ico path can also be used as the UriSource
for the TabViewItem
.