15

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.

Neil Turner
  • 2,712
  • 2
  • 18
  • 37
Martin Suchan
  • 10,600
  • 3
  • 36
  • 66

6 Answers6

18

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

}
N_A
  • 19,799
  • 4
  • 52
  • 98
  • do u think that it would be a very fast performance solution when the code is on mouse moved ? what do you think ? Is its performance better than WriteableBitmapEX ? – Apoorv Mar 18 '15 at 11:57
  • @Apoorv I have no idea – N_A Mar 18 '15 at 14:37
12

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

Farhan Ghumra
  • 15,180
  • 6
  • 50
  • 115
  • 1
    Using this I get *very* poor image quality on the resulting tile... Any suggestions? – bc3tech Aug 17 '15 at 20:37
  • If you get poor image quality from this code, try `transform.InterpolationMode = BitmapInterpolationMode.Fant` – evilkos Mar 20 '17 at 13:05
7

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.

Community
  • 1
  • 1
Martin Suchan
  • 10,600
  • 3
  • 36
  • 66
  • how fast is this.. i need a solution which i can implement on mousemove event ..so it has to crop whereever my pointer is..on the go...is it suitable of 2K images? i am using writeablebitmapex ..its slow – Apoorv Mar 18 '15 at 17:03
  • do you have a sample code for this ? It is asking for classes for TargetJpegQuality – Apoorv Mar 19 '15 at 08:42
2

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();
}
Erkki Nokso-Koivisto
  • 1,203
  • 15
  • 19
0

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;            
    }
Stuart Smith
  • 1,931
  • 15
  • 26
0

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();
            }
        }`
  • this code is working but it is not decreasing the size of that image. so I found that we can create a thumbnail instead of resizing the image. https://code.msdn.microsoft.com/windowsapps/File-thumbnails-sample-17575959/view/SourceCode#content – Kishan.JSK Feb 03 '17 at 05:19