Convert a BitmapImage to byte array in UWP - c#

How can I convert a BitmapImage object to byte array in UWP ? in .Net it is easy to achieve, even in previous WinRT versions, looked all over the internet but without success, one of the solutions was to use a WriteableBitmap like mentioned in this answer, but in the current version of UWP, constructing a WriteableBitmap out of a BitmapImage isn't possible, any work around ?

Since you start from image url, the only way I can figure out is to get the stream of the image. To do this, RandomAccessStreamReference.CreateFromUri() method is really useful.
Windows.Storage.Streams.IRandomAccessStream random = await Windows.Storage.Streams.RandomAccessStreamReference.CreateFromUri(new Uri("http://...")).OpenReadAsync();
Then we have to decode the stream in order to be able to read all pixels for later usage.
Windows.Graphics.Imaging.BitmapDecoder decoder = await Windows.Graphics.Imaging.BitmapDecoder.CreateAsync(random);
Windows.Graphics.Imaging.PixelDataProvider pixelData = await decoder.GetPixelDataAsync();
Finally you can access pixel buffer in such a way.
byte[] bytes = pixelData.DetachPixelData();

Yeah, to a byte[] is not too complex, I think you can get it. I wish it were simpler, but it's not.
http://codepaste.net/ijx28i
This code actually goes a step further and converts the byte[] into a Base64 string (for serializing) but you can ignore that extra step, right?
// using System.Runtime.InteropServices.WindowsRuntime;
private async Task<string> ToBase64(Image control)
{
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(control);
return await ToBase64(bitmap);
}
private async Task<string> ToBase64(WriteableBitmap bitmap)
{
var bytes = bitmap.PixelBuffer.ToArray();
return await ToBase64(bytes, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight);
}
private async Task<string> ToBase64(StorageFile bitmap)
{
var stream = await bitmap.OpenAsync(Windows.Storage.FileAccessMode.Read);
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixels = await decoder.GetPixelDataAsync();
var bytes = pixels.DetachPixelData();
return await ToBase64(bytes, (uint)decoder.PixelWidth, (uint)decoder.PixelHeight, decoder.DpiX, decoder.DpiY);
}
private async Task<string> ToBase64(RenderTargetBitmap bitmap)
{
var bytes = (await bitmap.GetPixelsAsync()).ToArray();
return await ToBase64(bytes, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight);
}
private async Task<string> ToBase64(byte[] image, uint height, uint width, double dpiX = 96, double dpiY = 96)
{
// encode image
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);
// read bytes
var bytes = new byte[encoded.Size];
await encoded.AsStream().ReadAsync(bytes, 0, bytes.Length);
// create base64
return Convert.ToBase64String(bytes);
}
private async Task<ImageSource> FromBase64(string base64)
{
// read stream
var bytes = Convert.FromBase64String(base64);
var image = bytes.AsBuffer().AsStream().AsRandomAccessStream();
// decode image
var decoder = await BitmapDecoder.CreateAsync(image);
image.Seek(0);
// create bitmap
var output = new WriteableBitmap((int)decoder.PixelHeight, (int)decoder.PixelWidth);
await output.SetSourceAsync(image);
return output;
}
This was the important part:
var bytes = (await bitmap.GetPixelsAsync()).ToArray();
Thought you might enjoy seeing it context.
Best of luck.

Related

How to create a IDirect3DSurface from an IBuffer or byte array in UWP

I want to create a video from a few RenderTargetBitmaps in UWP. I am doing that by using MediaClips.
From RenderTargetBitmap i can get an IBuffer or byte array of pixels.
To create a MediaClip I need either an image file or an IDirect3DSurface.
Creating an image just to create a clip is very expensive, so I thought of using IDirect3DSurface.
How can I do this?
I have tried this:
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(RenderedGrid, 100, 100);
IBuffer pixels = await renderTargetBitmap.GetPixelsAsync();
var values = Enum.GetValues(typeof(DirectXPixelFormat));
CanvasBitmap bitmap=null;
foreach (DirectXPixelFormat format in values)
{
try
{
videoClip = new MediaComposition();
bitmap = CanvasBitmap.CreateFromBytes(myWidget.Device, pixels, renderTargetBitmap.PixelWidth, renderTargetBitmap.PixelHeight, format);
StorageFile video2 = await storageFolder.CreateFileAsync("video2" + ".mp4", CreationCollisionOption.ReplaceExisting);
MediaClip d = MediaClip.CreateFromSurface(bitmap, DateTime.Now - previousFrame+new TimeSpan(100));
videoClip.Clips.Add(d);
await videoClip.RenderToFileAsync(video2);
break;
}
catch(Exception e)
{
}
}
I try all the formats in DirectXPixelFormat but none works.
I have a CanvasControl named myWidget that is empty.
I create a CanvasBitmap from Ibuffer (CanvasBitmap implements IDirect3DSurface)
Create a Mediaclip from CanvasBitmap
Add it to MediaComposition.
Then I try to render to video file.When i try to save to a file it throws an error
System.Runtime.InteropServices.COMException Stream is not in a state
to handle the request.
EDIT:
I figured out where the problem is, but not why and not how to fix it.
await videoClip.SaveAsync(video2);
videoClip= await MediaComposition.LoadAsync(video2);
var x=await videoClip.RenderToFileAsync(video2);
Now with these three lines i can save the video, but using only the third line it throws the error above. I cannot make sense of it. Why does saving and loading fix the problem??
Most probable reason is that, CanvasBitmap has an underlying IDirce3DSurface object as well as image data like byte[] or something else , though I am not sure about this.
If that's true, then creating a CanvasBitmap from byte[] or IBuffer won't effect the underlying IDirect3DSurface, the image part will be constructed only. You can see that by saving that image on the disk, it gives no error.
But I think there's a workaround if you want to skip saving data on the disk:
You can contruct the underlying IDirect3DSurface if you do Offscreen Drawing to a CanvasRenderTarget.
So, you can use the CanvasBitmap to construct a CanvasRenderTarget and then use that CanvasRenderTarget to contruct a MediaClip:
CanvasRenderTarget rendertarget;
using (CanvasBitmap canvas = CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), pixels, renderTargetBitmap.PixelWidth, renderTargetBitmap.PixelHeight, format))
{
rendertarget = new CanvasRenderTarget(CanvasDevice.GetSharedDevice(), canvas.SizeInPixels.Width, canvas.SizeInPixels.Height, 96);
using (CanvasDrawingSession ds = rendertarget.CreateDrawingSession())
{
ds.Clear(Colors.Black);
ds.DrawImage(canvas);
}
}
MediaClip d = MediaClip.CreateFromSurface(renderTarget, TimeSpan.FromMilliseconds(80));
mc.Clips.Add(m);
The MediaComposition.RenderToFileAsync Method saves the composition to a video file that can be played back with standard media players. From the error info, it seems the stream content is not correct media data and can not be render into a video file directly.
So, to create a video from a few RenderTargetBitmaps in UWP, the way to use an image file should be your choice. using MediaClip.CreateFromImageFileAsync method by saving the RenderTargetBitmap into a file then using it to create a video.
private async void CreateVideoByConvertRenderBitmapToFile()
{
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("Test",
CreationCollisionOption.ReplaceExisting);
var composition = new MediaComposition();
for (int i = 0; i < 5; i++)
{
RenderTargetBitmap render = new RenderTargetBitmap();
await render.RenderAsync(RenderGrid);
MyImage.Source = render;
var pixel = await render.GetPixelsAsync();
var file = await folder.CreateFileAsync("test.png", CreationCollisionOption.GenerateUniqueName);
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)render.PixelWidth,
(uint)render.PixelHeight,
logicalDpi,
logicalDpi,
pixel.ToArray());
await encoder.FlushAsync();
stream.Dispose();
MediaClip clip = await MediaClip.CreateFromImageFileAsync(file, TimeSpan.FromSeconds(3));
composition.Clips.Add(clip);
MyText.Text = "First frame >>>" + i;
}
}
var video = await ApplicationData.Current.LocalFolder.CreateFileAsync("test.mp4",
CreationCollisionOption.ReplaceExisting);
var action = await composition.RenderToFileAsync(video, MediaTrimmingPreference.Precise);
await folder.DeleteAsync();
}

WriteableBitmap to Base64 c#

First of all,I'm sorry for my poor English.
I work on this project for few days without any solution for my problem. I try to send a picture from my UWP application to a Webservice in c#. I did this thing in a android app without any problem.
I should encode a image into a base64 string that the webservice can decode it.
I have two issues , the first is that if I try ( with a online decoder) to decode my base64 string, this gave me something like this with this code.
In the image, we can see the it have not show the whole picture.:
I write it with this code to base64:
private async void ToBase64(WriteableBitmap img)
{
var encoded = new InMemoryRandomAccessStream();
// Copy buffer to pixels
byte[] pixels;
using (var stream = img.PixelBuffer.AsStream())
{
pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
}
var encoder = await BitmapEncoder
.CreateAsync(BitmapEncoder.PngEncoderId, encoded);
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied, (uint)img.PixelWidth, (uint)img.PixelHeight
, 96, 96, pixels);
await encoder.FlushAsync();
encoded.Seek(0);
var array = new byte[encoded.Size];
await encoded.AsStream().ReadAsync(array, 0, array.Length);
Base64String = Convert.ToBase64String(array);
}
My second issue is that if I try to send this Base64 to my webservices and decode it with FromBase64String, the webservice return an error "The base64 string format is not correct". I don't understand it because as we can see, online decoder can decode it and i don't have this issue with android app.
If you have any ideas about this issues.
I tried multiple things that i saw on internet
Thank you per advance.
EDIT 1
This is my decode method. This method work with Bitmap base64 send with android app.
[WebMethod]
public string uploadPhoto(string image)
{
byte[] bytes = Convert.FromBase64String(image);
using (var imageFile = new FileStream("directory+filename", FileMode.Create))
{
imageFile.Write(bytes, 0, bytes.Length);
imageFile.Flush();
}
return number;
}
EDIT 2
It works with this code :
public async Task<String> SaveToBytesAsync(ImageSource imageSource)
{
byte[] imageBuffer;
var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
var file = await localFolder.CreateFileAsync("temp.jpg", CreationCollisionOption.ReplaceExisting);
using (var ras = await file.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.None))
{
WriteableBitmap bitmap = imageSource as WriteableBitmap;
var stream = bitmap.PixelBuffer.AsStream();
byte[] buffer = new byte[stream.Length];
await stream.ReadAsync(buffer, 0, buffer.Length);
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, ras);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96.0, 96.0, buffer);
await encoder.FlushAsync();
var imageStream = ras.AsStream();
imageStream.Seek(0, SeekOrigin.Begin);
imageBuffer = new byte[imageStream.Length];
var re = await imageStream.ReadAsync(imageBuffer, 0, imageBuffer.Length);
}
await file.DeleteAsync(StorageDeleteOption.Default);
return Convert.ToBase64String(imageBuffer);
}
the decoding on the server give a full image.
Thanks.
is there a reason to encode the image?, try this.
string base64String = await ToBase64Async(bitmap);
public async Task<string> ToBase64Async(WriteableBitmap bitmap)
{
using (Stream stream = bitmap.PixelBuffer.AsStream())
{
stream.Position = 0;
var reader = new DataReader(stream.AsInputStream());
var bytes = new byte[stream.Length];
await reader.LoadAsync((uint)stream.Length);
reader.ReadBytes(bytes);
return Convert.ToBase64String(bytes);
}
}
Try use the code to base64
private async Task<string> ToBase64(Image control)
{
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(control);
return await ToBase64(bitmap);
}
And if you have a WriteableBitmap ,try use the code:
private async Task<string> ToBase64(WriteableBitmap bitmap)
{
var bytes = bitmap.PixelBuffer.ToArray();
return await ToBase64(bytes, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight);
}
If your image is a SotrageFile,you can use
private async Task<string> ToBase64(StorageFile bitmap)
{
var stream = await bitmap.OpenAsync(Windows.Storage.FileAccessMode.Read);
var decoder = await BitmapDecoder.CreateAsync(stream);
var pixels = await decoder.GetPixelDataAsync();
var bytes = pixels.DetachPixelData();
return await ToBase64(bytes, (uint)decoder.PixelWidth, (uint)decoder.PixelHeight, decoder.DpiX, decoder.DpiY);
}
If your picture is RenderTargetBitmap
private async Task<string> ToBase64(RenderTargetBitmap bitmap)
{
var bytes = (await bitmap.GetPixelsAsync()).ToArray();
return await ToBase64(bytes, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight);
}
See https://codepaste.net/ijx28i
http://lindexi.oschina.io/lindexi/post/win10-uwp-%E8%AF%BB%E5%8F%96%E4%BF%9D%E5%AD%98WriteableBitmap-BitmapImage/

Storing a BitmapImage in LocalFolder - UWP

I'm trying to store a BitmapImage to the filesystem using C# on UWP. The image is downloaded from Facebook using the graph api and returned as a BitmapImage. That part works, and to retrieve the image (once I can store it, tested with pictures just dropped in the local folder) I'm using the following code:
public static async Task<BitmapImage> GetProfilePicture(string userId){
BitmapImage profilePicture = new BitmapImage();
StorageFolder pictureFolder = await
ApplicationData.Current.LocalFolder.GetFolderAsync("ProfilePictures");
StorageFile pictureFile = await pictureFolder.GetFileAsync(userId + ".jpg");
IRandomAccessStream stream = await pictureFile.OpenAsync(FileAccessMode.Read);
profilePicture.SetSource(stream);
return profilePicture;
This works as well, so what I would want is to simply do the opposite. The preferred result would look like this:
public static async void SaveBitmapToFile(BitmapImage image, userId){
StorageFolder pictureFolder = await
ApplicationData.Current.LocalFolder.CreateFolderAsync(
"ProfilePictures",CreationCollisionOption.OpenIfExists);
//save bitmap to pictureFolder with name userId.jpg
}
I've searched far and wide trying to find a solution, but I can't seem to find any for the UWP platform. How would a go about saving the Bitmap to file? The extension doesn't have to be .jpg if it would be easier to use another extension.
It would be easier if you used a WriteableBitmap. For example, the first method would then be:
public static async Task<WriteableBitmap> GetProfilePictureAsync(string userId)
{
StorageFolder pictureFolder = await ApplicationData.Current.LocalFolder.GetFolderAsync("ProfilePictures");
StorageFile pictureFile = await pictureFolder.GetFileAsync(userId + ".jpg");
using (IRandomAccessStream stream = await pictureFile .OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
WriteableBitmap bmp = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
await bmp.SetSourceAsync(stream);
return bmp;
}
}
Then you could do:
public static async Task SaveBitmapToFileAsync(WriteableBitmap image, userId)
{
StorageFolder pictureFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("ProfilePictures",CreationCollisionOption.OpenIfExists);
var file = await pictureFolder.CreateFileAsync(userId + ".jpg", CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenStreamForWriteAsync())
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream.AsRandomAccessStream());
var pixelStream = image.PixelBuffer.AsStream();
byte[] pixels = new byte[image.PixelBuffer.Length];
await pixelStream.ReadAsync(pixels, 0, pixels.Length);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)image.PixelWidth, (uint)image.PixelHeight, 96, 96, pixels);
await encoder.FlushAsync();
}
}

Sharing screenshot on Windows Phone 8.1 WinRT does not attach image on sharing apps

I want to share the screenshot of my app. The screenshot is saved into the phone's picture library then it's shared as StorageFile
The problem is, the image is not attached to the sharing apps. I've verified that the screenshot was saved successfully in the phone's picture library.
Here's my code. What am I missing?
private async void askFacebook()
{
// Render some UI to a RenderTargetBitmap
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(this.gridRoot, (int)this.gridRoot.ActualWidth, (int)this.gridRoot.ActualHeight);
// Get the pixel buffer and copy it into a WriteableBitmap
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var width = renderTargetBitmap.PixelWidth;
var height = renderTargetBitmap.PixelHeight;
var wbmp = await new WriteableBitmap(1, 1).FromPixelBuffer(pixelBuffer, width, height);
imageToShare = await saveWriteableBitmapAsJpeg(wbmp, string.Format("{0}.jpg", getAppTitle()));
DataTransferManager.ShowShareUI();
}
private string getAppTitle()
{
// Get the assembly with Reflection:
Assembly assembly = typeof(App).GetTypeInfo().Assembly;
// Get the custom attribute informations:
var titleAttribute = assembly.CustomAttributes.Where(ca => ca.AttributeType == typeof(AssemblyTitleAttribute)).FirstOrDefault();
// Now get the string value contained in the constructor:
return titleAttribute.ConstructorArguments[0].Value.ToString();
}
private async Task<StorageFile> saveWriteableBitmapAsJpeg(WriteableBitmap bmp, string fileName)
{
// Create file in Pictures library and write jpeg to it
var outputFile = await KnownFolders.PicturesLibrary.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
using (var writeStream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
await encodeWriteableBitmap(bmp, writeStream, BitmapEncoder.JpegEncoderId);
}
return outputFile;
}
private async Task encodeWriteableBitmap(WriteableBitmap bmp, IRandomAccessStream writeStream, Guid encoderId)
{
// Copy buffer to pixels
byte[] pixels;
using (var stream = bmp.PixelBuffer.AsStream())
{
pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
}
// Encode pixels into stream
var encoder = await BitmapEncoder.CreateAsync(encoderId, writeStream);
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)bmp.PixelWidth,
(uint)bmp.PixelHeight,
logicalDpi,
logicalDpi,
pixels);
await encoder.FlushAsync();
}
private void ShareImageHandler(DataTransferManager sender, DataRequestedEventArgs e)
{
DataRequest request = e.Request;
request.Data.Properties.Title = "Ask Social Media";
request.Data.Properties.Description = "Do you know the answer to this question?";
// Because we are making async calls in the DataRequested event handler,
// we need to get the deferral first.
DataRequestDeferral deferral = request.GetDeferral();
// Make sure we always call Complete on the deferral.
try
{
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(imageToShare));
}
finally
{
deferral.Complete();
}
}
Apparently for Windows Phone, the image needs to be a StorageItem as the SetBitmap methods only works with Windows 8.x
So for Windows phone, instead of
request.Data.SetBitmap(RandomAccessStreamReference.CreateFromFile(imageToShare));
I created a storage item and use SetStorageItems to share it. It works with native Windows Phone apps such as emails, OneNote but I haven't tested it for sharing on Facebook, Twitter etc.
var imageItems = new List<IStorageItem>();
imageItems.Add(imageToShare);
request.Data.SetStorageItems(imageItems);

How to resize Image in C# WinRT/winmd?

I've got simple question, but so far I've found no answer: how to resize jpeg image in C# WinRT/WinMD project and save it as new jpeg?
I'm developing Windows 8 Metro application for downloading daily image form certain site and displaying it on a Live Tile. The problem is the image must be smaller than 1024x1024 and smaller than 200kB, otherwise it won't show on the tile:
http://msdn.microsoft.com/en-us/library/windows/apps/hh465403.aspx
If I got larger image, how to resize it to be fit for the Live Tile? I'm thinking just about simple resize like width/2 and height/2 with keeping the aspect ration.
The specific requirement here is that the code must run as Windows Runtime Component, so WriteableBitmapEx library won't work here - it's only available for regular WinRT projects. There is even a branch for WriteableBitmapEx as winmd project, but it's far from ready.
Example of how to scale and crop taken from here:
async private void BitmapTransformTest()
{
// hard coded image location
string filePath = "C:\\Users\\Public\\Pictures\\Sample Pictures\\fantasy-dragons-wallpaper.jpg";
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
if (file == null)
return;
// create a stream from the file and decode the image
var fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
// create a new stream and encoder for the new image
InMemoryRandomAccessStream ras = new InMemoryRandomAccessStream();
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(ras, decoder);
// convert the entire bitmap to a 100px by 100px bitmap
enc.BitmapTransform.ScaledHeight = 100;
enc.BitmapTransform.ScaledWidth = 100;
BitmapBounds bounds = new BitmapBounds();
bounds.Height = 50;
bounds.Width = 50;
bounds.X = 50;
bounds.Y = 50;
enc.BitmapTransform.Bounds = bounds;
// write out to the stream
try
{
await enc.FlushAsync();
}
catch (Exception ex)
{
string s = ex.ToString();
}
// render the stream to the screen
BitmapImage bImg = new BitmapImage();
bImg.SetSource(ras);
img.Source = bImg; // image element in xaml
}
More simpler code to re-size the image, not crop. The below code re-size the image as 80x80
using (var sourceStream = await sourceFile.OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);
BitmapTransform transform = new BitmapTransform() { ScaledHeight = 80, ScaledWidth = 80 };
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
using (var destinationStream = await destinationFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, destinationStream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied, 80, 80, 96, 96, pixelData.DetachPixelData());
await encoder.FlushAsync();
}
}
Source
So here is my solution I came with after lot of googling and trial/error coding:
The goal here was to find out, how to manipulate images in WinRT, specifically in Background Tasks. Background Tasks are even more limited than just regular WinRT projects, because they must be of type Windows Runtime Component. 99% of available libraries on NuGet targeting WinRT are targeting only the default WinRT projects, therefore they cannot be used in Windows Runtime Component projects.
At first I tried to use the well-known WriteableBitmapEx library - porting the necessary code to my winmd project. There is even branch of the WBE project targeting winmd, but it is unfinished. I made it compile after adding [ReadOnlyArray], [WriteOnlyArray] attributes to method parameters of type array and also after changing the project namespace to something not starting with "Windows" - winmd project limitation.
Even though I was able to use this library in my Background Task project it wasn't working, because, as I discovered, WriteableBitmap must be instantiated in UI thread and this is not possible as far as I know in Background Task.
In the meantime I have also found this MSDN article about Image manipulation in WinRT. Most of samples there are only in the JavaScript section, so I had to convert it to C# first. I've also found this helpful article on StackOverflow about image manipulation in WinRT.
internal static async Task LoadTileImageInternalAsync(string imagePath)
{
string tileName = imagePath.GetHashedTileName();
StorageFile origFile = await ApplicationData.Current.LocalFolder.GetFileAsync(imagePath);
// open file for the new tile image file
StorageFile tileFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(tileName, CreationCollisionOption.ReplaceExisting);
using (IRandomAccessStream tileStream = await tileFile.OpenAsync(FileAccessMode.ReadWrite))
{
// get width and height from the original image
IRandomAccessStreamWithContentType stream = await origFile.OpenReadAsync();
ImageProperties properties = await origFile.Properties.GetImagePropertiesAsync();
uint width = properties.Width;
uint height = properties.Height;
// get proper decoder for the input file - jpg/png/gif
BitmapDecoder decoder = await GetProperDecoder(stream, imagePath);
if (decoder == null) return; // should not happen
// get byte array of actual decoded image
PixelDataProvider data = await decoder.GetPixelDataAsync();
byte[] bytes = data.DetachPixelData();
// create encoder for saving the tile image
BitmapPropertySet propertySet = new BitmapPropertySet();
// create class representing target jpeg quality - a bit obscure, but it works
BitmapTypedValue qualityValue = new BitmapTypedValue(TargetJpegQuality, PropertyType.Single);
propertySet.Add("ImageQuality", qualityValue);
// create the target jpeg decoder
BitmapEncoder be = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, tileStream, propertySet);
be.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, width, height, 96.0, 96.0, bytes);
// crop the image, if it's too big
if (width > MaxImageWidth || height > MaxImageHeight)
{
BitmapBounds bounds = new BitmapBounds();
if (width > MaxImageWidth)
{
bounds.Width = MaxImageWidth;
bounds.X = (width - MaxImageWidth) / 2;
}
else bounds.Width = width;
if (height > MaxImageHeight)
{
bounds.Height = MaxImageHeight;
bounds.Y = (height - MaxImageHeight) / 2;
}
else bounds.Height = height;
be.BitmapTransform.Bounds = bounds;
}
// save the target jpg to the file
await be.FlushAsync();
}
}
private static async Task<BitmapDecoder> GetProperDecoder(IRandomAccessStreamWithContentType stream, string imagePath)
{
string ext = Path.GetExtension(imagePath);
switch (ext)
{
case ".jpg":
case ".jpeg":
return await BitmapDecoder.CreateAsync(BitmapDecoder.JpegDecoderId, stream);
case ".png":
return await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, stream);
case ".gif":
return await BitmapDecoder.CreateAsync(BitmapDecoder.GifDecoderId, stream);
}
return null;
}
In this sample we open one file, decode it into byte array, and encode it back into new file with different size/format/quality.
The result is fully working image manipulation even in Windows Runtime Component Class and without WriteableBitmapEx library.
Here is even shorter version, without overhead of accessing pixel data.
using (var sourceFileStream = await sourceFile.OpenAsync(Windows.Storage.FileAccessMode.Read))
using (var destFileStream = await destinationFile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceFileStream);
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(destFileStream, decoder);
enc.BitmapTransform.ScaledWidth = newWidth;
enc.BitmapTransform.ScaledHeight = newHeight;
await enc.FlushAsync();
await destFileStream.FlushAsync();
}
I just spent the last hour and half trying to figure this one out, I have a byte array that is a JPG and tried the answer given... I could not get it to work so I am putting up a new answer... Hopefully this will help someone else out... I am converting the JPG to 250/250 pixels
private async Task<BitmapImage> ByteArrayToBitmapImage(byte[] byteArray)
{
BitmapImage image = new BitmapImage();
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes((byte[])byteArray);
writer.StoreAsync().GetResults();
}
image.SetSource(stream);
}
image.DecodePixelHeight = 250;
image.DecodePixelWidth = 250;
return image;
}
if you want quality image then add
InterpolationMode = BitmapInterpolationMode.Fant in BitmapTransform ,
here is example
`
public static async Task ResizeImage(Windows.Storage.StorageFile imgeTOBytes, int maxWidth, int maxHeight)
{
using (var sourceStream = await imgeTOBytes.OpenAsync(FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);
double widthRatio = (double)maxWidth / decoder.OrientedPixelWidth;
double heightRatio = (double)maxHeight / decoder.OrientedPixelHeight;
double scaleRatio = Math.Min(widthRatio, heightRatio);
uint aspectHeight = (uint)Math.Floor((double)decoder.OrientedPixelHeight * scaleRatio);
uint aspectWidth = (uint)Math.Floor((double)decoder.OrientedPixelWidth * scaleRatio);
BitmapTransform transform = new BitmapTransform() { InterpolationMode = BitmapInterpolationMode.Fant, ScaledHeight = aspectHeight, ScaledWidth = aspectWidth };
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Premultiplied,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
using (var destinationStream = await imgeTOBytes.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, destinationStream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, aspectWidth, aspectHeight, 96, 96, pixelData.DetachPixelData());
await encoder.FlushAsync();
}
}`

Categories