UWP app show image from the Local folder in the WebView - c#

I am working on a UWP app that supports both Windows 8.1 and 10. There I have a WebView to which I set some html generated in the code using NavigateToString. There I need to show some images which are in the Appdata folder e.g. AppData\Local\Packages\1bf5b84a-6650-4124-ae7f-a2910e5e8991_egahdtqjx9ddg\LocalState\c9dfd4d5-d8a9-41c6-bd08-e7f4ae70e423\4f31f54f-1b5b-4812-9463-ba23ea7988a0\Images
I have tried using ms-appdata:///local/ , file:/// , forward slashes, back slashes everything in the img src. But none of them are loading the images.
Since the above path is too long, I have a sample image at AppData\Local\Packages\1bf5b84a-6650-4124-ae7f-a2910e5e8991_egahdtqjx9ddg\LocalState\1.png and trying to access it in different ways and none of them are showing that image in the web view. But I can access that from a browser.
And if I give a http url for the img src in the below code it works.
Please let me know how I can show images in the LocalState folder in a WebView.
e.g 1.
string figures = "<figure><img src=\"ms-appdata:///local/1.png\" alt =\"aaa\" height=\"400\" width=\"400\"/><figcaption>Figure : thumb_IMG_0057_1024</figcaption></figure>";
procedureWebView.NavigateToString(figures);
e.g. 2
string figures = "<figure><img src='file:///C://Users//xxx//AppData//Local//Packages//1bf5b84a-6650-4124-ae7f-a2910e5e8991_egahdtqjx9ddg//LocalState//1.png' alt=\"thumb_IMG_0057_1024\" height=\"100\" width=\"100\"/><figcaption>Figure : thumb_IMG_0057_1024</figcaption></figure>";

Since the above path is too long, I have a sample image at AppData\Local\Packages\1bf5b84a-6650-4124-ae7f-a2910e5e8991_egahdtqjx9ddg\LocalState\1.png and trying to access it in different ways and none of them are showing that image in the web view.
WebView holds a web context,which won't have access to WinRT API or using WinRT Scheme.
But you can convert the Image to Base64 DataUri and pass it to your webview. For details about converting a picture to Base64 DataUri you can refer to Reading and Writing Base64 in the Windows Runtime.
After reading the Blog, I made a basic demo and successfully pass the image to webview.
MainPage.xaml.cs:
private async Task<String> ToBase64(byte[] image, uint height, uint width, double dpiX = 96, double dpiY= 96)
{
var encoded = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, encoded);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, height, width, dpiX, dpiY, image);
await encoder.FlushAsync();
encoded.Seek(0);
var bytes = new byte[encoded.Size];
await encoded.AsStream().ReadAsync(bytes, 0, bytes.Length);
return Convert.ToBase64String(bytes);
}
private async Task<String> ToBase64(WriteableBitmap bitmap)
{
var bytes = bitmap.PixelBuffer.ToArray();
return await ToBase64(bytes, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight);
}
private async void myBtn_Click(object sender, RoutedEventArgs e)
{
StorageFile myImage = await GetFileAsync();
ImageProperties properties = await myImage.Properties.GetImagePropertiesAsync();
WriteableBitmap bmp = new WriteableBitmap((int)properties.Width, (int)properties.Height);
bmp.SetSource(await myImage.OpenReadAsync());
String dataStr=await ToBase64(bmp);
String fileType = myImage.FileType.Substring(1);
String str = "<figure><img src=\"data:image/"+myImage.FileType+";base64,"+dataStr+"\" alt =\"aaa\" height=\"400\" width=\"400\"/><figcaption>Figure : thumb_IMG_0057_1024</figcaption></figure>";
myWebView.NavigateToString(str);
}
private async Task<StorageFile> GetFileAsync()
{
StorageFile myImage = await ApplicationData.Current.LocalFolder.GetFileAsync("myImage.jpg");
return myImage;
}
MainPage.xaml:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel VerticalAlignment="Center">
<WebView Name="myWebView" Width="500" Height="500" ></WebView>
<Button Name="myBtn" Click="myBtn_Click">Click Me to Load WebView</Button>
</StackPanel>
</Grid>
And here is the output:

Related

How to save media element's current frame?

I am developing a media player like vlc. One of it's features is saving snapshot during playback. I am able to successfully save the snapshot of any element using rendertargetbitmap class, using the following c# code.
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(RenderedGrid, width, height);
RenderedImage.Source = renderTargetBitmap;
But it does not allow to capture media element. Please help me with a workaround. I guess, I am not sure, may it be possible by extracting a thumbnail from a specific position using position of StorageItemThumbnail Class. If even so, it will be a thumbnail not an image of current frame. Someone help please. It is essential feature of my app!
in a digital signage uwp app that I developed I had also a similar requirement. Searching the web on how I can do I found that in UWP you can't directly take a screenshot of a media element due to a protection policy (for example you could create a small app that captures all the frames of a video file without DRM management).
I came up with a workaround: I have the video file locally so I can extract a single frame at a certain point and save it to disk with the same dimensions of the video rendered on screen (in this way I had a full image of the frame to use later). To doing this I wrote this function:
private async Task<string> Capture(StorageFile file, TimeSpan timeOfFrame, Size imageDimension)
{
if (file == null)
{
return null;
}
//Get FrameWidth & FrameHeight
List<string> encodingPropertiesToRetrieve = new List<string>();
encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
IDictionary<string, object> encodingProperties = await file.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
//Get image stream
var clip = await MediaClip.CreateFromFileAsync(file);
var composition = new MediaComposition();
composition.Clips.Add(clip);
var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
//Create BMP
var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
writableBitmap.SetSource(imageStream);
//Get stream from BMP
string mediaCaptureFileName = "IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg";
var saveAsTarget = await CreateMediaFile(mediaCaptureFileName);
Stream stream = writableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)writableBitmap.PixelWidth,
(uint)writableBitmap.PixelHeight,
96,
96,
pixels);
await encoder.FlushAsync();
using (var outputStream = writeStream.GetOutputStreamAt(0))
{
await outputStream.FlushAsync();
}
}
return saveAsTarget.Name;
}
This function save the frame to disk as image and return his filename.
In my application I had more "widget" (some of those with images, some with video) so I used this function to get the video frames and put a new image on top of the media element with the same dimensions and a higher Canvas Z-Index in order to use RenderTargetBitmap for take the final screenshot.
It's not a clean solution but it was a workaround for RenderTargetBitmap not considering MediaElement.
Edit:
I forgot a reference to an internal function in my application so I canghed the previous code and the line var file = await _mediaEngine.CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg"); is now var file = await CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg");
The async function CreateMediaFile(string fileName) simply create a new file on the disk:
public async Task<StorageFile> CreateMediaFile(string filename)
{
StorageFolder _mediaFolder = KnownFolders.PicturesLibrary;
return await _mediaFolder.CreateFileAsync(filename);
}

Windows Phone 8.1 - Getting a scaled down image before loading it

I'm making a Windows Phone 8.1 app (Windows Runtime, not Windows Phone Silverlight 8.1). In my app I need to work with images. If the image is 12MP or less, everything works fine, but if the image is too large, let's say, for example, 41MP, an OutOfMemoryException is thrown.
I know that with BitmapImage you can set the DecodePixelWidth or DecodePixelHeight property to make the image smaller before even decoding it.
But I need to work with WriteableBitmap. So, is there a way to convert a BitmapImage into a WriteableBitmap? If not, how can I set the WriteableBitmap to be scaled down before even decoding its content?
My code is as follows:
StorageFolder installationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
string imagePath = #"Assets\trees201205150056_1337809980.jpg";
StorageFile file = await installationFolder.GetFileAsync(imagePath);
using(Stream stream = await file.OpenStreamForReadAsync())
{
WriteableBitmap writeableBmp = await BitmapFactory.New(1, 1).FromStream(stream.AsRandomAccessStream());
testPreviewImage.Source = writeableBmp;
writeableBmp = null;
}
With BitmapImage it would be something like this:
StorageFolder installationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
string imagePath = #"Assets\trees201205150056_1337809980.jpg";
StorageFile file = await installationFolder.GetFileAsync(imagePath);
using(Stream stream = await file.OpenStreamForReadAsync())
{
BitmapImage bitmapImage = new BitmapImage { DecodePixelWidth = 2000};
testPreviewImage.Source = bitmapImage;
bitmapImage = null;
}
Thank you.

WinRT binding an Image to a string or StorageFile

I am trying to display an image from a StorageFile after selecting it from a FilePicker. Since the Source of an Image has to be either a URI or an ImageSource, I am trying to get either of those from the StorageFile.
I am having a hard time getting data binding to work on an Image in XAML. I have tried the following:
<Image Width="300"
Height="300"
x:Name="LogoImage">
<Image.Source>
<BitmapImage UriSource="{Binding ImagePath}" />
</Image.Source>
</Image>
This way doesn't work, as the Path property of a StorageFile is not a URI. Also, I can't bind directly to the StorageFile itself, as it is not an ImageSource.
I tried to use this method:
public async Task<Image> GetImageAsync(StorageFile storageFile)
{
BitmapImage bitmapImage = new BitmapImage();
FileRandomAccessStream stream = (FileRandomAccessStream)await storageFile.OpenAsync(FileAccessMode.Read);
bitmapImage.SetSource(stream);
Image image = new Image();
image.Source = bitmapImage;
return image;
}
But, it returns a Task<Image>, which is also not an ImageSource or URI. It seems like it should be something more straightforward than what I am trying to do, but I am just not seeing it. Also, I have tried just specifying a file in the XAML for the Image.Source and it works fine. I just haven't been able to link it up based on the selected file from the FilePicker.
My ultimate goal is: select a file from FilePicker, update the ImageSource of the displayed Image, encode to base64 for storage in database. Then later, load existing base64 string from database, convert back to Image to be displayed.
Edit:
I was able to accomplish this task using the solution I posted below. Thanks in great part to Jerry Nixon's blog post: http://blog.jerrynixon.com/2014/11/reading-and-writing-base64-in-windows.html
The best solution would be to set the source in code behind instead of using a binding as it would let you handle things like cancellation in case the ImagePath gets updated while the previous image is still loading.
Alternatively, you could create a bitmap, start a task of loading it and return before that task is complete, e.g.
public Image GetImage(StorageFile storageFile)
{
var bitmapImage = new BitmapImage();
GetImageAsync(bitmapImage, storageFile);
// Create an image or return a bitmap that's started loading
var image = new Image();
image.Source = bitmapImage;
return image ;
}
private async Task GetImageAsync(BitmapImage bitmapImage, StorageFile storageFile)
{
using (var stream = (FileRandomAccessStream)await storageFile.OpenAsync(FileAccessMode.Read))
{
await bitmapImage.SetSource(stream);
}
}
If anyone else comes across this question looking for an answer, what I ultimately ended up doing for my situation, is take the StorageFile that is selected from the FilePicker, encode that as a base64 string (which I will save to my database for later retrieval).
Once I have the base64 string, I can decode that string into an ImageSource to set in code-behind. Here is my full ButtonClick event handler:
private async void ChooseImage_Click(object sender, RoutedEventArgs e)
{
var filePicker = new FileOpenPicker();
filePicker.FileTypeFilter.Add(".jpg");
filePicker.ViewMode = PickerViewMode.Thumbnail;
filePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
filePicker.SettingsIdentifier = "picker1";
filePicker.CommitButtonText = "Open File to Process";
var files = await filePicker.PickMultipleFilesAsync();
if (files.Count > 0)
{
// encode the storage file to base64
var base64 = await Encoding.ToBase64(files[0]);
// asynchronously save base64 string to database
// ...
// decode the base64 and set the image source
LogoImage.Source = await Encoding.FromBase64(base64);
}
}
The base64 encoding/decoding I found at the great Jerry Nixon's blog here: http://blog.jerrynixon.com/2014/11/reading-and-writing-base64-in-windows.html

WriteableBitmap renders PNG incorrectly

I am having trouble rendering PNGs that use Palette as "Color Type". Here is some simple code to reproduce the issue:
private async System.Threading.Tasks.Task Fetch()
{
HttpClient httpClient = new HttpClient();
Uri uri = new Uri("http://static.splashnology.com/articles/How-to-Optimize-PNG-and-JPEG-without-Quality-Loss/PNG-Palette.png");
HttpResponseMessage response = await httpClient.GetAsync(uri);
if (response.StatusCode == HttpStatusCode.Ok)
{
try
{
var content = await response.Content.ReadAsBufferAsync();
WriteableBitmap image = await BitmapFactory.New(1, 1).FromStream(content.AsStream());
Rect destination = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
Rect source = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
WriteableBitmap canvas = new WriteableBitmap(image.PixelWidth, image.PixelHeight);
canvas.Blit(destination, image, source);
RadarImage.Source = canvas;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
System.Diagnostics.Debug.WriteLine(e.StackTrace);
}
}
}
If I run that code using Windows Phone 8.1, the image appears using wrong colors. If I do the same test using a PNG that is using RGB as "Color Type", then everything is works fine.
I have looked at Codeplex forum and haven't seen any post related to that. I have reported it as an issue although it might be related to the way I'm rendering it. Is there any mistake in the way I'm using WriteableBitmap that could cause the wrong rendering?
UPDATE
According to this discussion
https://writeablebitmapex.codeplex.com/discussions/274445
the issue is related to an unexpected order of the bytes. These comments are from one and a half years ago, so I think there should be a proper fix somewhere...
The image that is rendered incorrectly is in the code above.
This one, using the same code, is rendered correctly.
http://www.queness.com/resources/images/png/apple_ex.png
The only difference between these two images is the "Color Type" property. The one that fails is set to "Palette" and the one rendered correctly is set to "RGB Alpha".
Thanks!
Carlos.
The problem appears to be in the FromStream extension, which seems to translate the paletted png to RGBA. As you note, WriteableBitmap wants BGRA. I suspect FromStream passes non-palette pngs' pixels unswizzled. This lets the apple start and finish as BGRA while the monkey ends up RGBA and draws with red and blue reversed.
You can bypass this problem by skipping FromStream and using a BitmapDecoder so you specify the format you want it to decode into:
// Read the retrieved image's bytes and write them into an IRandomAccessStream
IBuffer buffer = await response.Content.ReadAsBufferAsync();
var randomAccessStream = new InMemoryRandomAccessStream();
await randomAccessStream.WriteAsync(buffer);
// Decode the downloaded image as Bgra8 with premultiplied alpha
// GetPixelDataAsync lets us pass in the desired format and it'll do the magic to translate as it decodes
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(randomAccessStream);
var pixelData = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, new BitmapTransform(), ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
// Get the decoded bytes
byte[] imageData = pixelData.DetachPixelData();
// And stick them in a WriteableBitmap
WriteableBitmap image = new WriteableBitmap((int)decoder.PixelWidth,(int) decoder.PixelHeight);
Stream pixelStream = image.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
pixelStream.Write(imageData, 0, imageData.Length);
// And stick it in an Image so we can see it.
RadarImage.Source = image;

Access Denied when trying to replace file with binding in WinRT

I have a WinRT app that takes pictures and displays them in an Image. The first picture correctly takes and displays the Image. However, subsequent pictures, which are set to overwrite the original picture, throw an UnauthorizedAccessException with ACCESSDENIED. I'm also binding the Image source to the Uri of the student. I'm also positive the binding is what is causing the issue in that WinRT doesn't like me replacing a file that is currently in use. I've tried setting the source to null before the file replace, etc, but that is not working. What is an elegant way to handle this? Also note that I bind this file on the page before this one also and I needed to remove the binding on that page also in order to avoid the error.
private async void btnTakePic_Click(object sender, RoutedEventArgs e)
{
await CameraCapture();
}
private async Task CameraCapture()
{
CameraCaptureUI camUI = new CameraCaptureUI();
camUI.PhotoSettings.AllowCropping = true;
camUI.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.MediumXga;
camUI.PhotoSettings.CroppedAspectRatio = new Size(4, 3);
Windows.Storage.StorageFile imageFile = await camUI.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (imageFile != null)
{
IRandomAccessStream stream = await imageFile.OpenAsync(FileAccessMode.Read);
BitmapImage bitmapCamera = new BitmapImage();
bitmapCamera.SetSource(stream);
// Use unique id for image name since name could change
string filename = student.StudentID + "Photo.jpg";
await imageFile.MoveAsync(ApplicationData.Current.LocalFolder, filename, NameCollisionOption.ReplaceExisting);
student.UriPhoto = new Uri(string.Format("ms-appdata:///local/{0}", filename), UriKind.Absolute);
}
}
Here is the binding portion just for fun:
<Image x:Name="imgStudent" Grid.Row="0" Width="400" Height="300" Margin="15" Grid.ColumnSpan="2">
<Image.Source>
<BitmapImage DecodePixelWidth="200" UriSource="{Binding UriPhoto}" />
</Image.Source>
</Image>
Ok, well after several hours of research, I've concluded that it is near impossible to bind to a Uri, and the alternative is to bind a BitmapImage. I couldn't find the exact reason, but it has something to do with the fact that using a Uri leaves the BitmapImage open and binding quickly breaks this paradigm. The solution is to bind the BitmapImage and set the BitmapImage to a stream, which seems to support binding quite well.
I liked the idea of the Uri since its easily seriliazable, while the BitmapImage is more difficult and takes up significantly more space (since you are serializing the picture data as opposed to a link). The solution that I decided on and that works is to serialize the Uri, and use the OnDeserialized attribute to create the BitmapImage on startup. I then needed a method/event to reset the BitmapImage whenever the Uri changed.
Here's the final code to recap:
private async Task CameraCapture()
{
CameraCaptureUI camUI = new CameraCaptureUI();
camUI.PhotoSettings.AllowCropping = true;
camUI.PhotoSettings.MaxResolution = CameraCaptureUIMaxPhotoResolution.MediumXga;
camUI.PhotoSettings.CroppedAspectRatio = new Size(4, 3);
Windows.Storage.StorageFile imageFile = await camUI.CaptureFileAsync(CameraCaptureUIMode.Photo);
if (imageFile != null)
{
// Use unique id for image name since name could change
string filename = student.StudentID + "Photo.jpg";
// Move photo to Local Storate and overwrite existing file
await imageFile.MoveAsync(ApplicationData.Current.LocalFolder, filename, NameCollisionOption.ReplaceExisting);
// Open file stream of photo
IRandomAccessStream stream = await imageFile.OpenAsync(FileAccessMode.Read);
BitmapImage bitmapCamera = new BitmapImage();
bitmapCamera.SetSource(stream);
student.BitmapPhoto = bitmapCamera // BitmapImage
// Save Uri to photo since we can serialize this and re-create BitmapPhoto on startup/deserialization
student.UriPhoto = new Uri(string.Format("ms-appdata:///local/{0}", filename), UriKind.Absolute);
}
}
And the new XAML binding to the student BitmapImage
<Image x:Name="imgStudent" Source="{Binding BitmapPhoto}" Grid.Row="0" Width="400" Height="300" Margin="15" Grid.ColumnSpan="2"/>

Categories