UWP application hangs when converting a canvas to image - c#

I trying to convert the canvas in UWP to image (RenderTargetBitmap). I have two options to return the image to end user.
StorageFile
System.IO.Stream
When I use the storage file everything works as expected. But when I use a memory stream, the application hangs. I have created a simple sample to reproduce the issue.
<Grid Background="White" Name ="Main_Grid">
<Button Content="UIToImage" Margin="141,159,0,0" VerticalAlignment="Top" Click="UIToImageAsync"></Button>
</Grid>
private async void UIToImageAsync(object sender, RoutedEventArgs e)
{
//Pick a folder
var folder = KnownFolders.PicturesLibrary;
var storageFile = await folder.CreateFileAsync("Output.png", CreationCollisionOption.ReplaceExisting);
//using (var inputImgStream = await storageFile.OpenStreamForWriteAsync())//this works
using (var inputImgStream = new MemoryStream())//this doesn't work
{
//Draw a line
Windows.UI.Xaml.Shapes.Path path = new Windows.UI.Xaml.Shapes.Path();
DrawShape(path);
//The canvas to hold the above shape - line
var canvas = new Canvas();
//Add canvas to the grid in XAML
Main_Grid.Children.Add(canvas);
canvas.Children.Add(path);
//Draw the canvas to the image
RenderTargetBitmap bitmap = null;
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
bitmap = new RenderTargetBitmap();
canvas.Height = 800;
canvas.Width = 1380;
canvas.RenderTransform = new TranslateTransform { X = 1, Y = 100
};
});
//Render a bitmap image
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,async () =>
{
await bitmap.RenderAsync(canvas, 1380, 800);
});
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, inputImgStream.AsRandomAccessStream());// I suspect passing the MemoryStream is the issue. While 'StorageFile' is used there are no issues.
IBuffer pixelBuffer = await bitmap.GetPixelsAsync();
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)bitmap.PixelWidth,
(uint)bitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync(); // The application hangs here
}
}
private void DrawShape(Windows.UI.Xaml.Shapes.Path path)
{
PathGeometry lineGeometry = new PathGeometry();
PathFigure lineFigure = new PathFigure();
LineSegment lineSegment = new LineSegment();
lineFigure.StartPoint = new Point(100, 100);
lineSegment.Point = new Point(200, 200);
lineFigure.Segments.Add(lineSegment);
path.Data = lineGeometry;
SolidColorBrush strokeBrush = new SolidColorBrush(Windows.UI.Color.FromArgb(255, 255, 0, 0));
path.Stroke = strokeBrush;
path.StrokeThickness = 5;
lineGeometry.Figures.Add(lineFigure);
}
Can anyone point me out about what causing this?

It really seems that using a simple MemoryStream with AsRandomAccessStream does not workm, although I am not sure about the reason. Instead, you can use InMemoryRandomAccessStream, which will work as expected.
There is however another problem, which might be a source of problems, or at least it caused it to crash on my machine:
//Render a bitmap image
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
CoreDispatcherPriority.Normal, async () =>
{
await bitmap.RenderAsync(canvas, 1380, 800);
});
Although it seems that the await will wait for the RenderAsync call to finish, it unfortunately does not. The second parameter is only a DispatchedHandler. This delegate has the following signature:
public delegate void DispatchedHandler()
As you can see there is no Task return value. That means it will create just a async void lambda. The lambda will start running and when it reaches the RenderAsync, it will start executing it, but hte RunAsync's await may (and most likely will) finish before the RunAsync does. So it is likely, that you will start to execute the bitmap.GetPixelAsync while the bitmap is still completely empty.
To fix this, you should move the code inside the lambda:
//Render a bitmap image
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
await bitmap.RenderAsync(canvas, 1380, 800);
using (var inputImgStream = new InMemoryRandomAccessStream()) //this doesn't work
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId,
inputImgStream
); // I suspect passing the MemoryStream is the issue. While 'StorageFile' is used there are no issues.
IBuffer pixelBuffer = await bitmap.GetPixelsAsync();
Debug.WriteLine($"Capacity = {pixelBuffer.Capacity}, Length={pixelBuffer.Length}");
var pixelArray = pixelBuffer.ToArray();
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint) bitmap.PixelWidth,
(uint) bitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelArray
);
await encoder.FlushAsync(); // The application hangs here
}
});
As you can see, you must also move the using block for the stream inside the lambda, because if it were outside, the same fate would happen - the using might Dispose of the stream before the RenderAsync finished.

Related

How to convert a UIElement which is created in code behind into stream using C#?

I have created UIelements dynamically in code behind without using Xaml and I need to convert it into stream. I have tried with RenderTargetBitmap. But it doesnot work, Is there any possible other ways for it?
private async void ConverteToImage(UIElement element, int pageIndex)
{
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(element); // Render canvas.
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
using (var stream = new InMemoryRandomAccessStream())
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
logicalDpi,
logicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
SaveAsCustomStamp(stream.AsStream(), pageIndex, loadedDocument);
}
}

How to create thumbnail/snapshot from bing maps in c# for universal solution

I have a universal solution with platform supporting Windows Phone. After the user has tapped on Bing Maps, a pin is created an snapshot of the map is also created, but this image doesn't contain the pin.
I am using Windows.UI.Xaml.Controls.Maps.MapControl with this xaml:-
XAML:
<mp:MapControl x:Name="mpWinPh"
Grid.Row="1"
Width="{Binding ElementName=ControlRoot,
Path=Width}"
Height="{StaticResource ActivityLogGfxSize}"
MapServiceToken="{Binding Credentials}"
MapTapped="mpWinPh_MapTapped" />
C#:
private async void mpWinPh_MapTapped(MapControl sender, MapInputEventArgs args)
{
var location = args.Location;
BasicGeoposition snPosition = new BasicGeoposition
{
Latitude = location.Position.Latitude,
Longitude = location.Position.Longitude,
Altitude = location.Position.Altitude
};
Geopoint snPoint = new Geopoint(snPosition);
MapIcon icon = new MapIcon { Location = snPoint };
mpWinPh.Center = snPoint;
mpWinPh.ZoomLevel = 15;
mpWinPh.MapElements.Clear();
mpWinPh.MapElements.Add(icon);
Field.GpsCoordinate coordinate = new Field.GpsCoordinate { Latitude = location.Position.Latitude, Longitude = location.Position.Longitude };
(viewModel as LocationControlViewModel).UpdateGps(coordinate);
await SaveImage();
}
private async Task SaveImage()
{
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(mpWinPh);
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
// Useful for rendering in the correct DPI
var displayInformation = DisplayInformation.GetForCurrentView();
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
displayInformation.RawDpiX,
displayInformation.RawDpiY,
pixels);
await encoder.FlushAsync();
stream.Seek(0);
byte[] resizedData = new byte[stream.Size];
await stream.ReadAsync(resizedData.AsBuffer(), (uint)stream.Size, InputStreamOptions.None);
await SaveStreamToTempLocation(() => Task.FromResult(new MemoryStream(resizedData) as Stream));
}
Since adding data to the map is an asynchronous operation, after adding any data to the map (such as a MapIcon), or changing the map view with any of the view setting API's, you should wait until the LoadingStatus of the map control becomes MapLoadingStatus.Loaded.
This indicates the map control has finished the asynch work and is fully rendered. You can register for the LoadingStatusChanged event on the map control.

Get a new Image by cropping

I'm trying to convert my cropped image (and/or Grid) to new Normal Image , but it returns as it cropped , in same position, how to fix it, there is how works my program after cropping
private async void Save(object sender, RoutedEventArgs e)
{
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(GGrid, 4096 , 2448);
// IBuffer pixels = await renderTargetBitmap.GetPixelsAsync();
IBuffer pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
FileSavePicker fileSavePicker = new FileSavePicker();
fileSavePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileSavePicker.FileTypeChoices.Add("JPEG files", new List<string>() { ".png"});
fileSavePicker.SuggestedFileName = "image";
var outputFile = await fileSavePicker.PickSaveFileAsync();
if (outputFile == null)
{
// The user cancelled the picking operation
return;
}
using (IRandomAccessStream stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
try
{
await encoder.FlushAsync();
}
catch (Exception err)
{
switch (err.HResult)
{
case unchecked((int)0x88982F81): //WINCODEC_ERR_UNSUPPORTEDOPERATION
// If the encoder does not support writing a thumbnail, then try again
// but disable thumbnail generation.
encoder.IsThumbnailGenerated = false;
break;
default:
throw err;
}
}
//if (encoder.IsThumbnailGenerated == false)
//{
// await encoder.FlushAsync();
//}
}
}
You need to translate and crop the bitmap.
double x,y,w,h;
GGrid.RenderTransform = new TranslateTransform
{
X = -x, // translate back to origin (0,0)
Y = -y
};
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(GGrid, w, h); // use crop size

BitmapEncoder flush throws Argument Exception

I'm trying to resize images in my UWP application. Most of the time the appended code works, but sometimes await encoder.FlushAsync(); throws an ArgumentException.
I've headed over to MSDN (https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmapencoder.bitmaptransform.aspx) and they tell me (at "Remarks"):
If you try scale an image stored in an indexed pixel format using the BitmapTransform member, FlushAsync fails with HRESULT WINCODEC_ERR_INVALIDPARAMETER . Instead, you must use GetPixelDataAsync to obtain the scaled pixel data and then use SetPixelData to set it on the encoder.
I've tried to do that, see the two commented lines (which look somehow wrong to me due to the repetition). On the second line (where I try to SetPixelData) the Encoder rewards me with an buffer allocated not sufficient Exception.
var decoder = await BitmapDecoder.CreateAsync(streamToReadFrom.AsStream().AsRandomAccessStream());
if (decoder.OrientedPixelHeight > height ||
decoder.OrientedPixelWidth > width)
{
var resizedStream = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(resizedStream, decoder);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
encoder.BitmapTransform.ScaledHeight = newHeight;
encoder.BitmapTransform.ScaledWidth = newWidth;
//"buffer allocated not sufficient"
// var pd = await decoder.GetPixelDataAsync(BitmapPixelFormat.Rgba16, BitmapAlphaMode.Ignore,
// encoder.BitmapTransform, ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
// encoder.SetPixelData(BitmapPixelFormat.Rgba16, BitmapAlphaMode.Ignore,
// decoder.OrientedPixelWidth, decoder.OrientedPixelHeight, decoder.DpiX, decoder.DpiY, pd.DetachPixelData());
// write out to the stream
// might fail cause https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmapencoder.bitmaptransform.aspx
await encoder.FlushAsync();
// Read out resizedStream and return
}
Example image which causes this issue: http://www.spiegel.de/images/image-1028227-hppano-lqbn.jpg. Unit Test here: https://github.com/famoser/OfflineMedia/blob/master/Famoser.OfflineMedia.UnitTests/Presentation/ImageResizeTest.cs
How can I avoid the ArgumentException? How do I know an image is in an "indexed pixel format", and how can I resize this format too?
On the second line (where I try to SetPixelData) the Encoder rewards me with an buffer allocated not sufficient Exception.
This is because when you SetPixelData, the pixel data doesn't match it from GetPixelDataAsync. You can for example code like this:
if (file != null)
{
BitmapImage bmp = new BitmapImage();
using(var imageStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
InMemoryRandomAccessStream pixelras = new InMemoryRandomAccessStream();
BitmapEncoder pixelencoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, pixelras);
BitmapTransform transform = new BitmapTransform();
transform.InterpolationMode = BitmapInterpolationMode.Fant;
transform.ScaledHeight = 400;
transform.ScaledWidth = 400;
var provider = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
var pixels = provider.DetachPixelData();
pixelencoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, 400,
400, decoder.DpiX, decoder.DpiY, pixels);
try
{
await pixelencoder.FlushAsync();
}
catch(Exception ex)
{
}
bmp.SetSource(pixelras);
img.Source = bmp;
}
}
How do I know an image is in an "indexed pixel format", and how can I resize this format too?
I couldn't find any effective way to detect an indexed pixel format image, but since it is said
If you try scale an image stored in an indexed pixel format using the BitmapTransform member, FlushAsync fails with HRESULT WINCODEC_ERR_INVALIDPARAMETER . Instead, you must use GetPixelDataAsync to obtain the scaled pixel data and then use SetPixelData to set it on the encoder.
It is a method to use catch the exception and use SetPixelData again, for example:
if (file != null)
{
BitmapImage bmp = new BitmapImage();
using(var imageStream = await file.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
InMemoryRandomAccessStream ras = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(ras, decoder);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
encoder.BitmapTransform.ScaledHeight = 400;
encoder.BitmapTransform.ScaledWidth = 400;
try
{
await encoder.FlushAsync();
bmp.SetSource(ras);
}
catch (Exception ex)
{
if (ex.HResult.ToString() == "WINCODEC_ERR_INVALIDPARAMETER")
{
InMemoryRandomAccessStream pixelras = new InMemoryRandomAccessStream();
BitmapEncoder pixelencoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, pixelras)
BitmapTransform transform = new BitmapTransform();
transform.InterpolationMode = BitmapInterpolationMode.Fant;
transform.ScaledHeight = 400;
transform.ScaledWidth = 400;
var provider = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
var pixels = provider.DetachPixelData();
pixelencoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, 400,
400, decoder.DpiX, decoder.DpiY, pixels);
try
{
await pixelencoder.FlushAsync();
bmp.SetSource(pixelras);
}
catch
{
}
}
}
img.Source = bmp;
}
}

How to share filtered image in UWP C#

I have an image processing windows 10 application. I am applying filters on that image and showing it on the image element. This is how I am applyting filter and setting it as a source to MainImage element.
ProcessImage processImage = new ProcessImage(sourcePixels, width, height);
byte[] blurEffect = processImage.BlurEffect(width, height);
WriteableBitmap blurImage = new WriteableBitmap((int)width, (int)height);
using (Stream stream = blurImage.PixelBuffer.AsStream())
{
await stream.WriteAsync(blurEffect, 0, blurEffect.Length);
MainImage.Source = blurImage;
}
Up till now I have set the WriteableBitmap image to the source. Now I want to share this image using DataTransferManager's Data requested event as shown
dataTransferManager = DataTransferManager.GetForCurrentView();
dataTransferManager.DataRequested += DataTransferManager_DataRequested;
The body of its event containing this code
DataPackage dataPackage = args.Request.Data;
dataPackage.Properties.Title = "App Name";
dataPackage.Properties.Description = "My description";
dataPackage.SetBitmap();
On the share button click event, i am calling showshareUI like this
DataTransferManager.ShowShareUI();
I am trying to share image using fourth line above that is SetBitmap method, but the problem here is this method want RandomAccessStreamReference value and I have a filtered image of type writeablebitmap. How can I get this thing done?
You can write your WriteableBitmap to an InMemoryRandomAccessStream
I don't have access to my dev machine so I can't test it but here's a quick sample:
private async Task<IRandomAccessStream> Convert(WriteableBitmap writeableBitmap)
{
var stream = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
Stream pixelStream = writeableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[pixelStream.Length];
await pixelStream.ReadAsync(pixels, 0, pixels.Length);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)writeableBitmap.PixelWidth, (uint)writeableBitmap.PixelHeight, 96.0, 96.0, pixels);
await encoder.FlushAsync();
return stream;
}
Since dataPackage.SetBitmap() expects a RandomAccessStreamReference object you will need to obtain one based on the IRandomAccessStream that the above method returns. Fortunately that is pretty easy:
var streamRef = RandomAccessStreamReference.CreateFromStream(stream)
Hope that works.

Categories

Resources