Ali Ali - 1 month ago 23
C# Question

Converting WPF BitmapImage to and from Base64 using JpegBitmapEncoder fails for loading JPG files

I want to store a WPF BitmapImage to XML. (I know this is not usually recommended but for my case, it makes sense as I want to embed all my resource to a single XML file besides other data I have).

So Here are my extension methods:

public static string ToBase64(this BitmapImage image, string format)
{
return Convert.ToBase64String(Encode(image, format));
}

public static Stream FromBase64(this string content)
{
var bytes = Convert.FromBase64String(content);
var stream = new MemoryStream();
stream.Write(bytes, 0, bytes.Length);
return stream;
}

private static byte[] Encode(BitmapImage bitmapImage, string format)
{
byte[] data = null;
BitmapEncoder encoder = null;
switch (format.ToUpper())
{
case "PNG": encoder = new PngBitmapEncoder();
break;
case "GIF": encoder = new GifBitmapEncoder();
break;
case "BMP": encoder = new BmpBitmapEncoder();
break;
case "JPG": encoder = new JpegBitmapEncoder();
break;
}
if (encoder != null)
{
encoder.Frames.Add(BitmapFrame.Create(bitmapImage));
using (var ms = new MemoryStream())
{
encoder.Save(ms);
ms.Seek(0, SeekOrigin.Begin);
data = ms.ToArray();
}
}

return data;
}

public static BitmapImage ToBitmapImage(this Stream stream)
{
try
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
return bitmap;
}
catch (Exception ex)
{


}

return null;
}


and here is my XML logics:

public async void LoadImage(Guid imageSourceGuid)
{
var sourceElement = await GetImageSource(imageSourceGuid);
if (sourceElement != null)
{
var data = sourceElement.Element("Value").Value;
Format = sourceElement.Attribute("Format").Value.ToUpper();
if (string.IsNullOrEmpty(data) == false)
{
using (var stream = data.FromBase64())
{
SetImage(stream.ToBitmapImage());
}
}
}
}

private void SetImage(BitmapImage bitmap)
{
this.ImageShape.Source = bitmap;
}

public async Task<XElement> GetImageSource(Guid id)
{
XElement result = null;

await Task.Run(() =>
{
var settings = new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment, IgnoreWhitespace = true, IgnoreComments = true, Async = true };
using (var reader = XmlReader.Create(FilePath, settings))
{
while (reader.Read())
{
if (reader.IsStartElement() && reader.Name == "ImageSource")
{
var att = reader.GetAttribute("Id");
if (att != null && Guid.Parse(att) == id)
{
result = XNode.ReadFrom(reader) as XElement;
break;
}
}
}
}

});

return result;
}


My XML file looks like:

<?xml version="1.0" encoding="utf-8"?>
<ImageSources>
<ImageSource Id="1b1e4ebc-484c-4f63-bbed-bf33430f85f2" Format="JPG" OriginalWidth="534" OriginalHeight="338">
<Value><![CDATA[....]]<Value>
</ImageSource>
</ImageSources>
...


But when I try to create a BitmapImage using ToBitmapImage method from the XML data I saved earlier I get "The image cannot be decoded. The image header might be corrupted." exception.

This only happens for JPG files I have no issue with PNG files at all.

Answer

You should rewind the MemoryStream after writing in your FromBase64 method:

public static Stream FromBase64(this string content)
{
    var bytes = Convert.FromBase64String(content);
    var stream = new MemoryStream();
    stream.Write(bytes, 0, bytes.Length);
    stream.Seek(0, SeekOrigin.Begin); // here
    return stream;
}

Or construct it directly from the byte array:

public static Stream FromBase64(this string content)
{
    return new MemoryStream(Convert.FromBase64String(content));
}

Afaik, JpegBitmapDecoder is the only BitmapDecoder in WPF that is affected by the source stream's actual Position.