1

I have a method that opens a FileStream and creates a BitmapImage, by using the StreamSource property.

Somehow, in a single machine, trying to open a big image (6000x4000px) results in the method returning a 1x1px image instead.

First I thought that the image was being loaded from a shared folder on the local network, but It was stored in the downloads folder of the same computer.

I saw that the image was "blocked/locked" by Windows because it was downloaded from an unverified source, so I opened Properties and unlocked it. Trying to load the image again resulted in the same problem.

The image was fully downloaded.

Background information:

The machine with the problem:

  • Windows 7 SP1.
  • 32 bits.
  • Net Framework 4.6.2.
  • 4GB of memory, with 2.5GB being used.

My machine (it works as expected):

  • Windows 10, build 15063.413.
  • 64 bits.
  • Net Framework 4.7.
  • 8GB of memory, with 6GB being used.

Code:

public static BitmapSource SourceFrom(this string fileSource, int? size = null)
{
    using (var stream = new FileStream(fileSource, FileMode.Open, FileAccess.Read))
    {
        var bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;

        if (size.HasValue)
        {
            //It's not possible to get the size of image while opening, so get from another place.
            var refSize = fileSource.ScaledSize(); //Gets the size of the image.

            if (refSize.Height > refSize.Width)
                bitmapImage.DecodePixelHeight = size.Value;
            else
                bitmapImage.DecodePixelWidth = size.Value;
        }

        bitmapImage.StreamSource = stream;               
        bitmapImage.EndInit();
        bitmapImage.Freeze(); //Just in case you want to load the image in another thread.
        return bitmapImage;
    }
}

Usage:

var image = "C:\Image.jpg".SourceFrom(); //Without any other parameter.

Question:

  • Is there any case that my code is not treating properly, at least that explains why I'm getting a 1x1px image instead of the full size one?
  • Also, why it does not throw an Exception if it can't load the image?

Probable answer:

Working code:

using (var stream = new FileStream(fileSource, FileMode.Open, FileAccess.Read))
{
    using (var memory = new MemoryStream())
    {
        stream.CopyTo(memory);
        memory.Position = 0;
//...

Just replace this part of the code and use the memory variable instead of stream while setting the StreamSource object.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • Is that machine (on which you have a problem) 32-bit one? – Evk Jul 10 '17 at 12:35
  • @Evk 32 bits, I'll update the post with the information. – Nicke Manarin Jul 10 '17 at 12:46
  • And all other machines on which you do _not_ have this problem with the same file are 64-bit? – Evk Jul 10 '17 at 12:47
  • @Evk Yes, and a different OS too. I updated the post. – Nicke Manarin Jul 10 '17 at 12:49
  • On that particular machine, are you sure the download completed? Can you open the file in Windows Photo Viewer? – Clemens Jul 10 '17 at 12:50
  • Yes, I even copied the image to my computer before trying to load it again. – Nicke Manarin Jul 10 '17 at 12:52
  • So, if you put breakpoint after `EndInit()`, your saying bitmapImage has the height and width properties equal to 1? Also my research turned up this article: https://dlaa.me/blog/post/6129847 – Adam Vincent Jul 10 '17 at 13:10
  • Yes, the resulting `image` variable has the 1x1 image. I'm not able to debug the app on the machine that returns this faulty image. The article talks about a problem in choosing the right DecodePixel values, I managed to overcome this with my code above, but that's not the problem. :) – Nicke Manarin Jul 10 '17 at 13:20
  • 1
    About your last remark, you can check that `IsDownloading` is true to verify this? Though not sure what exactly it can "download" if you are initializing image from local file. – Evk Jul 10 '17 at 13:29
  • 1
    While the code you're showing should just work, there is perhaps one more thing you could try. Create a MemoryStream and copy the FileStream content to the MemoryStream via its CopyTo method. Then set the MemoryStream's Position to zero and load the BitmapImage from the MemoryStream. – Clemens Jul 10 '17 at 13:35
  • @Evk Yes, I believe that I can. I'll use an infinite `while` and maybe a Thread.Sleep or a Task.Wait. – Nicke Manarin Jul 10 '17 at 13:36
  • @Clemens Oh sorry, I wanted to reference the other user, typed your nickname instead. I'll try as you wrote, using a `MemoryStream`. – Nicke Manarin Jul 10 '17 at 13:43
  • 1
    @Clemens This code was built this way to avoid file usage problems (open and release the file), I remember having issues loading from an `URI`. – Nicke Manarin Jul 10 '17 at 13:50
  • @Clemens Worked, by using a `MemoryStream` and `CopyTo()`. Now, the only problem is to know why exactly this happened... :( – Nicke Manarin Jul 11 '17 at 20:57
  • 1
    No idea. According to the documentation it should work without copying to a MemoryStream. I encountered the same problem once when I tried to create a BitmapImage from the stream of a WebResponse. Apparently it couldn't be read in one go, and I had to copy it to a MemoryStream before before decoding a BitmapImage. – Clemens Jul 11 '17 at 21:37

3 Answers3

2

It seems that when the image file is very large, or for some other reason the source stream can't be read immediately, you'll have to copy the source stream to an intermediate MemoryStream, and assign that to the StreamSource property of the BitmapImage:

using (var fileStream = new FileStream(fileSource, FileMode.Open, FileAccess.Read))
using (var memoryStream = new MemoryStream())
{
    fileStream.CopyTo(memoryStream);
    memoryStream.Position = 0;

    var bitmapImage = new BitmapImage();
    bitmapImage.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad
    bitmapImage.StreamSource = memoryStream;               
    bitmapImage.EndInit();
    bitmapImage.Freeze();
    return bitmapImage;
}
Clemens
  • 123,504
  • 12
  • 155
  • 268
2

The BitmapImage creates a default image with 1x1px when decoding of the image fails. You need to register the DecodeFailed event to detect this.

Exception decodeEx = null;
using (var fileStream = new FileStream(fileSource, FileMode.Open, FileAccess.Read))
{
    var bitmapImage = new BitmapImage();
    bitmapImage.BeginInit();
    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
    bitmapImage.StreamSource = fileStream;
    bitmapImage.DecodeFailed += (_, e) => decodeEx = e.ErrorException;
    bitmapImage.EndInit();

    if (decodeEx != null)
        throw decodeEx;

    bitmapImage.Freeze();
    return bitmapImage;
}

In my case it turned out to be a OutOfMemoryException. Indeed decoding only failed when the memory usage was high and the native functionwhich is actually called by BitmapImage (using unmanaged memory) was probably unable to allocate enough memory.

0

i had the same problem, the CacheOption is not in the right place on your code!! juste add it befor the endInit();

 source.CacheOption = BitmapCacheOption.OnLoad;

like this ->

                BitmapImage source = new BitmapImage();
                source.BeginInit();
                source.CacheOption = BitmapCacheOption.OnLoad;
                source.StreamSource = fs;                  
                source.EndInit();
Bilal
  • 1,254
  • 13
  • 14