Skip to content

Commit

Permalink
Add support for data: URIs in embedded images
Browse files Browse the repository at this point in the history
  • Loading branch information
mstefarov committed Sep 11, 2023
1 parent 39e169c commit 7462602
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 65 deletions.
71 changes: 42 additions & 29 deletions src/Toolkit/Toolkit/UI/Controls/PopupViewer/PopupMediaView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,36 +86,9 @@ private void UpdateImage()
if (img.Source is not BitmapImage bmi || bmi.UriSource?.OriginalString != sourceUrl)
#endif
{
if (sourceUrl.StartsWith("data:image/"))
if (TryCreateImageSource(sourceUrl, out var source))
{
// might be base64
var idx = sourceUrl.IndexOf(";base64,");
if (idx > 11 && sourceUrl.Length > idx + 8)
{
try
{
var base64data = sourceUrl.Substring(idx + 8);
var data = Convert.FromBase64String(base64data);
#if MAUI
var source = new StreamImageSource() { Stream = (token) => Task.FromResult<Stream>(new MemoryStream(data)) };
#else
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = new MemoryStream(data);
source.EndInit();
#endif
img.Source = source;
}
catch { }
}
}
else if (sourceUrl != null && Uri.TryCreate(sourceUrl, UriKind.Absolute, out Uri? result))
{
#if MAUI
img.Source = new RuntimeStreamImageSource(result);
#else
img.Source = new BitmapImage(result);
#endif
img.Source = source;
}
}
}
Expand Down Expand Up @@ -234,6 +207,46 @@ private void OnPopupMediaPropertyChanged()
InvalidateMeasure(); // Forces recalculation of available space for generating a new chart
}
}

// Also used for embedded images in TextPopupElement views
internal static bool TryCreateImageSource(string? sourceUri, out ImageSource? source)
{
source = null;
if (sourceUri != null && sourceUri.StartsWith("data:image/"))
{
// might be base64
var idx = sourceUri.IndexOf(";base64,");
if (idx > 11 && sourceUri.Length > idx + 8)
{
try
{
var base64data = sourceUri.Substring(idx + 8);
var data = Convert.FromBase64String(base64data);
#if MAUI
var newSource = new StreamImageSource() { Stream = (token) => Task.FromResult<Stream>(new MemoryStream(data)) };
#else
var newSource = new BitmapImage();
newSource.BeginInit();
newSource.StreamSource = new MemoryStream(data);
newSource.EndInit();
#endif
source = newSource;
return true;
}
catch { }
}
}
else if (Uri.TryCreate(sourceUri, UriKind.Absolute, out Uri? result))
{
#if MAUI
source = new RuntimeStreamImageSource(result);
#else
source = new BitmapImage(result);
#endif
return true;
}
return false;
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -191,25 +191,8 @@ private static View CreateBlock(MarkupNode node)

case MarkupType.Image:
var imageElement = new Image();
if (Uri.TryCreate(node.Content, UriKind.Absolute, out var imgUri))
{
imageElement.Loaded += OnImageElementLoaded;

async void OnImageElementLoaded(object? sender, EventArgs e)
{
imageElement.Loaded -= OnImageElementLoaded;
var taggedUri = imgUri;
var ri = new RuntimeImage(taggedUri); // Use Runtime's caching and authentication
try
{
imageElement.Source = await RuntimeImageExtensions.ToImageSourceAsync(ri);
}
catch
{
// Don't let one bad image take down the whole app. Better to ignore a failed image load.
}
}
}
if (PopupMediaView.TryCreateImageSource(node.Content, out var imageSource))
imageElement.Source = imageSource;
return imageElement;

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,23 +190,9 @@ private static Inline VisitInline(MarkupNode node)
return link;

case MarkupType.Image:
if (Uri.TryCreate(node.Content, UriKind.Absolute, out var imgUri))
if (PopupMediaView.TryCreateImageSource(node.Content, out var imageSource))
{
var imageElement = new Image { Tag = imgUri };
imageElement.Loaded += static async (sender, e) => // Start loading the image in the background once the image is actually displayed
{
var img = (Image)sender;
var taggedUri = (Uri)img.Tag;
var ri = new RuntimeImage(taggedUri); // Use Runtime's caching and authentication
try
{
img.Source = await ri.ToImageSourceAsync();
}
catch
{
// Don't let one bad image take down the whole app. Better to ignore a failed image load.
}
};
var imageElement = new Image { Source = imageSource };
return new InlineUIContainer(imageElement);
}
return new Run(); // TODO find a better placeholder when img src is invalid
Expand Down Expand Up @@ -287,7 +273,7 @@ private static void ApplyStyle(TextElement el, MarkupNode node)
else if (el is ListItem itemEl)
itemEl.TextAlignment = ConvertAlignment(node.Alignment);
}
if (node.IsUnderline.HasValue)
if (node.IsUnderline == true)
{
if (el is Inline inlineEl)
inlineEl.TextDecorations.Add(TextDecorations.Underline);
Expand Down

0 comments on commit 7462602

Please sign in to comment.