UWP - scaled image is damaged - c#

I have a problem to place an image with down-scale. Image is rasterized and I don't know find way to place it correctly.
Is it possible to place picture without losing quality ?
Code:
public async Task<BitmapImage> BitmapTransform(string filePath, uint width)
{
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
if (file == null)
return null;
// 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);
double ration =
enc.BitmapTransform.ScaledWidth = width;
enc.BitmapTransform.ScaledHeight = (uint)(((double)decoder.PixelHeight / (double)decoder.PixelWidth) * (double)width);
// 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);
return bImg;
}

You can choose the way of resizing - Nearest Neighbor, Linear, Cubic, Fant - by BitmapTransform.InterporationMode property.
Have you tried it?
https://msdn.microsoft.com/en-us/library/windows/apps/windows.graphics.imaging.bitmaptransform.interpolationmode
double ration =
enc.BitmapTransform.ScaledWidth = width;
enc.BitmapTransform.ScaledHeight = (uint)(((double)decoder.PixelHeight / (double)decoder.PixelWidth) * (double)width);
enc.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Cubic; // <----- Set the Interporation mode here
But the quality of result is vary. It's depend on the original image.

Related

How can set Image and PDF size in UWP C#

I am trying to generate a pdf in my project where I converted Image into Pdf and Image generated from Layout. Generated image size is very large and also There are not received good quality.
Please Help me to solve this.
I am using this code in program.
RenderTargetBitmap renderTargetBitmapimg = new RenderTargetBitmap();
var mainlayoutHeight = pageStackPanel.ActualHeight;
var mainlayoutWidth = pageStackPanel.ActualWidth;
int mlHeight = Convert.ToInt32(mainlayoutHeight);
int mlWidth = Convert.ToInt32(mainlayoutWidth);
await renderTargetBitmapimg.RenderAsync(pageStackPanel, mlWidth, mlHeight);
///SAVE IMAGE IN FOLDER
var pixelBufferimg = await renderTargetBitmapimg.GetPixelsAsync();
var pixels = pixelBufferimg.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
var storageFolder = await KnownFolders.GetFolderForUserAsync(null, KnownFolderId.PicturesLibrary);
StorageFolder projectFolder = await storageFolder.CreateFolderAsync("TestImage", CreationCollisionOption.OpenIfExists);
var file = await projectFolder.CreateFileAsync(jpgFilename, CreationCollisionOption.GenerateUniqueName);
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)renderTargetBitmapimg.PixelWidth,
(uint)renderTargetBitmapimg.PixelHeight,
200, 200,
pixels);
await encoder.FlushAsync();
StorageFile prescriptionJpgFile = await projectFolder.GetFileAsync(jpgFilename);
await stream.FlushAsync();
stream.Seek(0);
stream.Dispose();
renderTargetBitmapimg = null;
The answer is no, you can't set the size of the generated image file. But you could try to resize it to reduce its physical size by giving a specific width and height. You could use BitmapEncoder class to encode the original image, change the BitmapInterpolationMode of the image. The code looks like this:
//open file as stream
using (IRandomAccessStream fileStream = await imagefile.OpenAsync(FileAccessMode.ReadWrite))
{
var decoder = await BitmapDecoder.CreateAsync(fileStream);
var resizedStream = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(resizedStream, decoder);
double widthRatio = (double)reqWidth / decoder.PixelWidth;
double heightRatio = (double)reqHeight / decoder.PixelHeight;
double scaleRatio = Math.Min(widthRatio, heightRatio);
if (reqWidth == 0)
scaleRatio = heightRatio;
if (reqHeight == 0)
scaleRatio = widthRatio;
uint aspectHeight = (uint)Math.Floor(decoder.PixelHeight * scaleRatio);
uint aspectWidth = (uint)Math.Floor(decoder.PixelWidth * scaleRatio);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Linear;
encoder.BitmapTransform.ScaledHeight = aspectHeight;
encoder.BitmapTransform.ScaledWidth = aspectWidth;
await encoder.FlushAsync();
resizedStream.Seek(0);
var outBuffer = new byte[resizedStream.Size];
await resizedStream.ReadAsync(outBuffer.AsBuffer(), (uint)resizedStream.Size, InputStreamOptions.None);
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
Debug.WriteLine(storageFolder.Path);
StorageFile sampleFile = await storageFolder.CreateFileAsync("testfile.pdf", CreationCollisionOption.ReplaceExisting);
await FileIO.WriteBytesAsync(sampleFile, outBuffer);
}
For more information, you could check this document:How to Compress image and change its Size.
Another suggestion is that you could just use the UWP print API to generate the PDF file. You could take a look at the Print Helper of Windows Community Toolkit. You could choose not to print but save it as a PDF file.

How to get a subimage from CapturedPhoto UWP

I want to save a section of an image taken with a webcam (using Windows.Media.Capture).
Here is what I got so far:
[...]
MediaCapture mediaCapture = new MediaCapture();
await mediaCapture.InitializeAsync();
await mediaCapture.StartPreviewAsync();
public async void takePhoto(){
var lowLagCapture = await mediaCapture.PrepareLowLagPhotoCaptureAsync(ImageEncodingProperties.CreateUncompressed(MediaPixelFormat.Bgra8));
var capturedPhoto = await lowLagCapture.CaptureAsync();
await lowLagCapture.FinishAsync();
await CapturePhotoWithOrientationAsync();
}
private async Task CapturePhotoWithOrientationAsync() {
var captureStream = new InMemoryRandomAccessStream();
try {
await mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream);
} catch (Exception ex) {
Debug.WriteLine("Exception when taking a photo: {0}", ex.ToString());
return;
}
var decoder = await BitmapDecoder.CreateAsync(captureStream);
var file = await storageFolder.CreateFileAsync("test.jpeg", CreationCollisionOption.ReplaceExisting);
using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite)) {
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(Windows.Devices.Sensors.SimpleOrientation.Rotated270DegreesCounterclockwise);
var properties = new BitmapPropertySet {
{ "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } };
await encoder.BitmapProperties.SetPropertiesAsync(properties);
await encoder.FlushAsync();
}
}
[...]
This way I can save the whole image. But how can I only save a section of the image?
But how can I only save a section of the image?
With a start point and size you could define a crop bound, and then create a BitmapTransform, with this transform you can get the cropped image pixies by GetPixelDataAsync() method. BitmapEncoder can SetPixelData. Details for how to do please reference How to crop bitmap in a Windows Store app (C#) sample and this tutorial.
For example, based on your code snippet:
//Inside CapturePhotoWithOrientationAsync method
...
Point startPoint = new Point(0, 0);
Size corpSize = new Size(250, 250);
// Convert start point and size to integer.
uint startPointX = (uint)Math.Floor(startPoint.X);
uint startPointY = (uint)Math.Floor(startPoint.Y);
uint height = (uint)Math.Floor(corpSize.Height);
uint width = (uint)Math.Floor(corpSize.Width);
// Refine the start point and the size.
if (startPointX + width > decoder.PixelWidth)
{
startPointX = decoder.PixelWidth - width;
}
if (startPointY + height > decoder.PixelHeight)
{
startPointY = decoder.PixelHeight - height;
}
// Create cropping BitmapTransform to define the bounds.
BitmapTransform transform = new BitmapTransform();
BitmapBounds bounds = new BitmapBounds();
bounds.X = startPointX;
bounds.Y = startPointY;
bounds.Height = height;
bounds.Width = width;
transform.Bounds = bounds;
// Get the cropped pixels within the the bounds of transform.
PixelDataProvider pix = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.ColorManageToSRgb);
byte[] pixels = pix.DetachPixelData();
StorageFolder storageFolder = KnownFolders.PicturesLibrary;
var file = await storageFolder.CreateFileAsync("test.jpeg", CreationCollisionOption.ReplaceExisting);
using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
// Set the pixel data to the cropped image.
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
width,
height,
decoder.DpiX,
decoder.DpiY,
pixels);
// Flush the data to file.
await encoder.FlushAsync();
}

Send InkCanvas signature using WCF call

I have a Windows 10 UWP app that will run on Windows 10 Mobile. A requirement I have is to capture a signature from the user. So far, I am simply using an InkCanvas in XAML and have it wired up to my code behind.
I then have a button that when clicked, will take the signature on the InkCanvas and send it to the server via a WCF call. The server and WCF service is already existing. It takes in the signature image as a base64 serialized string.
I know how to get the base64 once I have either an image or a byte array. However, in my many hours of reading, I am finding that articles/examples were either written for WPF or Windows 8.1 and do not work on Windows 10 UWP. Also, of the examples that I have found that will work, it seems my only option is to save the signature to file as a GIF.
I see that I can call GetStrokes() like this
var strokeCollection = cvsSignature.InkPresenter.StrokeContainer.GetStrokes();
Which will return me a read only list of InkStroke. I guess I can iterate that list and build a byte array? How would I do that? It seems this is not efficient?
Otherwise, I thought I could just change the stream from a file stream to a memory stream but I guess either this is not possible or I am missing something. I am trying this
using (var inkMemStream = new MemoryStream())
{
await cvsSignature.InkPresenter.StrokeContainer.SaveAsync(inkMemStream);
}
But with this type of approach I get an exception that I cannot convert from System.IO.MemoryStream to Windows.Storage.Streams.IOutputStream
Thanks!
I found a way, if you don't want to save your InkCanvas to a file or as a GIF, to manipulate it in memory. I actually have three options below. Note, for some of this, you will need to add a reference to Win2D.uwp, which can be found on NuGet by searching that exact name. It is provided by Microsoft.
Convert the InkCanvas to a byte array:
private byte[] ConvertInkCanvasToByteArray()
{
//First, we need to get all of the strokes in the canvas
var canvasStrokes = myCanvas.InkPresenter.StrokeContainer.GetStrokes();
//Just as a check, make sure to only do work if there are actually strokes (ie not empty)
if (canvasStrokes.Count > 0)
{
var width = (int)myCanvas.ActualWidth;
var height = (int)myCanvas.ActualHeight;
var device = CanvasDevice.GetSharedDevice();
//Create a new renderTarget with the same width and height as myCanvas at 96dpi
var renderTarget = new CanvasRenderTarget(device, width,
height, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
//This will clear the renderTarget with a clean slate of a white background.
ds.Clear(Windows.UI.Colors.White);
//Here is where we actually take the strokes from the canvas and draw them on the render target.
ds.DrawInk(myCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
//Finally, this will return the render target as a byte array.
return renderTarget.GetPixelBytes();
}
else
{
return null;
}
}
If all you need is a byte array of the pixels, you are done. However, if you now want to generate an image, you use the WriteableBitmap to do so. Here are sync and async methods that can be called to do this.
private WriteableBitmap GetSignatureBitmapFull()
{
var bytes = ConvertInkCanvasToByteArray();
if (bytes != null)
{
var width = (int)cvsSignature.ActualWidth;
var height = (int)cvsSignature.ActualHeight;
var bmp = new WriteableBitmap(width, height);
using (var stream = bmp.PixelBuffer.AsStream())
{
stream.Write(bytes, 0, bytes.Length);
return bmp;
}
}
else
return null;
}
private async Task<WriteableBitmap> GetSignatureBitmapFullAsync()
{
var bytes = ConvertInkCanvasToByteArray();
if (bytes != null)
{
var width = (int)cvsSignature.ActualWidth;
var height = (int)cvsSignature.ActualHeight;
var bmp = new WriteableBitmap(width, height);
using (var stream = bmp.PixelBuffer.AsStream())
{
await stream.WriteAsync(bytes, 0, bytes.Length);
return bmp;
}
}
else
return null;
}
Finally, here is an async method that I am using to be able to do a crop on the bitmap if you want to do that. The key thing with this method is notice how with this approach, you don't have to get the pixel bytes as an array first. You just get the strokes from the canvas and then it is saved directly into a memory stream
private async Task<WriteableBitmap> GetSignatureBitmapCropped()
{
try
{
var canvasStrokes = cvsSignature.InkPresenter.StrokeContainer.GetStrokes();
if (canvasStrokes.Count > 0)
{
var bounds = cvsSignature.InkPresenter.StrokeContainer.BoundingRect;
var xOffset = (uint)Math.Round(bounds.X);
var yOffset = (uint)Math.Round(bounds.Y);
var pixelWidth = (int)Math.Round(bounds.Width);
var pixelHeight = (int)Math.Round(bounds.Height);
using (var memStream = new InMemoryRandomAccessStream())
{
await cvsSignature.InkPresenter.StrokeContainer.SaveAsync(memStream);
var decoder = await BitmapDecoder.CreateAsync(memStream);
var transform = new BitmapTransform();
var newBounds = new BitmapBounds();
newBounds.X = 0;
newBounds.Y = 0;
newBounds.Width = (uint)pixelWidth;
newBounds.Height = (uint)pixelHeight;
transform.Bounds = newBounds;
var pdp = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.DoNotColorManage);
var pixels = pdp.DetachPixelData();
var cropBmp = new WriteableBitmap(pixelWidth, pixelHeight);
using (var stream = cropBmp.PixelBuffer.AsStream())
{
await stream.WriteAsync(pixels, 0, pixels.Length);
}
return cropBmp;
}
}
else
{
return null;
}
}
catch (Exception ex)
{
return null;
}
}
Hope this helps provide some alternatives when using InkCanvas in Windows 10 Universal.
it seems my only option is to save the signature to file as a GIF.
GIF is not the only choice. The official sample just show GIF, but you can also save the InkStokes collection to JPG and PNG. Code as follows:
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
savePicker.FileTypeChoices.Add("Gif,JPG,PNG", new System.Collections.Generic.List<string> { ".jpg" ,".gif",".png"});
Windows.Storage.StorageFile file = await savePicker.PickSaveFileAsync();
But with this type of approach I get an exception that I cannot convert from System.IO.MemoryStream to Windows.Storage.Streams.IOutputStream
As you see, it cannot convert from System.IO.MemoryStream to Windows.Storage.Streams.IOutputStream directly. You need to read the image file you just saved as ImageSource. As you known how to get the base64 once you have either an image or a byte array. So what we just need is to get an image or a byte array from the image file.
Read the image file as byte array and then a memory stream as follows
if (inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count > 0)
{
var savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.PicturesLibrary;
savePicker.FileTypeChoices.Add("Gif,JPG,PNG", new System.Collections.Generic.List<string> { ".jpg", ".gif", ".png" });
Windows.Storage.StorageFile file = await savePicker.PickSaveFileAsync();
if (null != file)
{
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await inkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
}
using (IRandomAccessStream streamforread = await file.OpenAsync(FileAccessMode.Read))
{
WriteableBitmap inkimagesource = new WriteableBitmap(50, 50);
inkimagesource.SetSource(streamforread);
byte[] imageBuffer = inkimagesource.PixelBuffer.ToArray();
MemoryStream ms = new MemoryStream(imageBuffer);
}
}
}
Read the file as image source just need to change the file reading code as follows:
using (IRandomAccessStream streamforread = await file.OpenAsync(FileAccessMode.Read)
{
var bitmap = new BitmapImage();
bitmap.SetSource(streamforread);
}
I guess I can iterate that list and build a byte array? How would I do that? It seems this is not efficient?
It seems like currently there is no API can build the InkStroke collections as byte array directly. SaveAsync() method has already help you save the InkStroke collection to a stream, you can use code above for using it.

Images in Windows Phone 8.1 livetiles aren't sharp

I create livetiles with the following code:
// wide 310x150
var tileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileWide310x150PeekImage03);
tileXml.GetElementsByTagName(textElementName).LastOrDefault().InnerText = string.Format(artist + " - " + trackname);
var image = tileXml.GetElementsByTagName(imageElementName).FirstOrDefault();
if (image != null)
{
var src = tileXml.CreateAttribute("src");
if (albumart == String.Empty)
src.Value = "Assets/onemusic_logo_wide.scale-240.png";
else
src.Value = albumart;
image.Attributes.SetNamedItem(src);
}
// square 150x150
var squaredTileXml = TileUpdateManager.GetTemplateContent(TileTemplateType.TileSquare150x150PeekImageAndText01);
squaredTileXml.GetElementsByTagName(textElementName).FirstOrDefault().InnerText = string.Format(artist + " - " + trackname);
image = squaredTileXml.GetElementsByTagName(imageElementName).LastOrDefault();
if (image != null)
{
var src = squaredTileXml.CreateAttribute("src");
if (albumart == String.Empty)
src.Value = "Assets/onemusic_logo_square.scale-240.png";
else
src.Value = albumart;
image.Attributes.SetNamedItem(src);
}
updater.Update(new TileNotification(tileXml));
updater.Update(new TileNotification(squaredTileXml));
The problem I face is that the images shown on the livetile aren't sharp (in the app they are). I think this is because of the 310x150 pixels size of the template. I looked at the templates, there aren't any higher resolution ones. Is there a way to make the images sharper?
I noticed that providing an image with a resolution of exactly 744x360 pixels solves the problem. So I wrote this function to resize my albumarts (maybe it will come in handy for someone);
private async static Task<string> CropAndSaveImage(string filePath)
{
const string croppedimage = "cropped_albumart.jpg";
// read file
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
if (file == null)
return String.Empty;
// 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
using (InMemoryRandomAccessStream writeStream = new InMemoryRandomAccessStream())
{
// create encoder
BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(writeStream, decoder);
enc.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Linear;
// convert the entire bitmap to a 744px by 744px bitmap
enc.BitmapTransform.ScaledHeight = 744;
enc.BitmapTransform.ScaledWidth = 744;
enc.BitmapTransform.Bounds = new BitmapBounds()
{
Height = 360,
Width = 744,
X = 0,
Y = 192
};
await enc.FlushAsync();
StorageFile albumartfile = await ApplicationData.Current.LocalFolder.CreateFileAsync(croppedimage, CreationCollisionOption.ReplaceExisting);
using (var stream = await albumartfile.OpenAsync(FileAccessMode.ReadWrite))
{
await RandomAccessStream.CopyAndCloseAsync(writeStream.GetInputStreamAt(0), stream.GetOutputStreamAt(0));
}
// return image path
return albumartfile.Path;
}
}

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