Monday, November 30, 2009

Convert, Encode And Decode Silverlight WriteableBitmap Data

In the comments of my Silverlight 4 EdgeCam Shots blog post "marcb" asked me how to convert the WriteableBitmap to a byte array to save the snapshot in a database.
I thought the answer might be also useful for others. Furthermore I will provide ready to use code for JPEG encoding and decoding of the WriteableBitmap.





Byte array conversion

Copy WriteableBitmap to ARGB byte array
public static byte[] ToByteArray(this WriteableBitmap bmp)
{
   int[] p = bmp.Pixels;
   int len = p.Length * 4;
   byte[] result = new byte[len]; // ARGB
   Buffer.BlockCopy(p, 0, result, 0, len);
   return result;
}

Copy ARGB byte array into WriteableBitmap
public static void FromByteArray(this WriteableBitmap bmp, byte[] buffer)
{
   Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
}

Usage
// Render UIElement into WriteableBitmap
WriteableBitmap bmp = new WriteableBitmap(UIElement, null);

// Copy WriteableBitmap.Pixels into byte array (format ARGB)
byte[] buffer = bmp.ToByteArray();


// Create a new WriteableBitmap with the size of the original image
WriteableBitmap bmp = new WriteableBitmap(width, height);

// Fill WriteableBitmap from byte array (format ARGB)
bmp.FromByteArray(buffer);

I will include the two methods in my WriteableBitmap extensions that I'm going to put up on Codeplex soon.


JPEG encoding and decoding
If you want to store many images or transport them over a network the needed storage size could quickly become a big problem. For example an image with the size 512 x 512 needs 1 Megabyte storage space and a 1024 x 768 image even 3 MB. A solution could be image compression using JPEG encoding and decoding. To accomplish this I've used the open source FJCore JPEG library which is distributed under the MIT License and works nicely with Silverlight.

Encode WriteableBitmap as JPEG stream
public static void EncodeJpeg(WriteableBitmap bmp, Stream destinationStream)
{
   // Init buffer in FluxJpeg format
   int w = bmp.PixelWidth;
   int h = bmp.PixelHeight;
   int[] p = bmp.Pixels;
   byte[][,] pixelsForJpeg = new byte[3][,]; // RGB colors
   pixelsForJpeg[0] = new byte[w, h];
   pixelsForJpeg[1] = new byte[w, h];
   pixelsForJpeg[2] = new byte[w, h];

   // Copy WriteableBitmap data into buffer for FluxJpeg
   int i = 0;
   for (int y = 0; y < h; y++)
   {
      for (int x = 0; x < w; x++)
      {
         int color = p[i++];
         pixelsForJpeg[0][x, y] = (byte)(color >> 16); // R
         pixelsForJpeg[1][x, y] = (byte)(color >> 8);  // G
         pixelsForJpeg[2][x, y] = (byte)(color);       // B
      }
   }

   // Encode Image as JPEG using the FluxJpeg library
   // and write to destination stream
   ColorModel cm = new ColorModel { colorspace = ColorSpace.RGB };
   FluxJpeg.Core.Image jpegImage = new FluxJpeg.Core.Image(cm, pixelsForJpeg);
   JpegEncoder encoder = new JpegEncoder(jpegImage, 95, destinationStream);
   encoder.Encode();
}

Decode WriteableBitmap from JPEG stream
public static WriteableBitmap DecodeJpeg(Stream sourceStream)
{
   // Decode JPEG from stream
   var decoder = new FluxJpeg.Core.Decoder.JpegDecoder(sourceStream);
   var jpegDecoded = decoder.Decode();
   var img = jpegDecoded.Image;
   img.ChangeColorSpace(ColorSpace.RGB);

   // Init Buffer
   int w = img.Width;
   int h = img.Height;
   var result = new WriteableBitmap(w, h);
   int[] p = result.Pixels;
   byte[][,] pixelsFromJpeg = img.Raster;

   // Copy FluxJpeg buffer into WriteableBitmap
   int i = 0;
   for (int y = 0; y < h; y++)
   {
      for (int x = 0; x < w; x++)
      {
         p[i++] = (0xFF << 24)                    // A
                | (pixelsFromJpeg[0][x, y] << 16) // R
                | (pixelsFromJpeg[1][x, y] << 8)  // G
                |  pixelsFromJpeg[2][x, y];       // B
      }
   }

   return result;
}

Usage
// Save rendered UIElement as JPEG file
private void BtnSaveFile_Click(object sender, RoutedEventArgs e)
{   
   if (saveFileDlg.ShowDialog().Value)
   {
      using (Stream dstStream = saveFileDlg.OpenFile())
      {
         // Render to WriteableBitmap
         WriteableBitmap bmp = new WriteableBitmap(UIElement, null);
         
         // Encode JPEG and write to FileStream
         EncodeJpeg(bmp, dstStream);
      }
   }
}


// Open JPEG file and read into a WriteableBitmap
private void BtnLoadFile_Click(object sender, RoutedEventArgs e)
{   
   if (openFileDialog.ShowDialog().Value)
   {
      using (System.IO.Stream srcStream = openFileDialog.File.OpenRead())
      {         
         // Read JPEG file and decode it
         WriteableBitmap bmp = DecodeJpeg(srcStream);
      }
   }
}

Keep in mind that the standard JPEG format doesn't support alpha values (transparency) and that the compression is lossy. So don't encode and decode images subsequently with JPEG.
It is also possible to use the built-in Silverlight class BitmapSource and its SetSource method to decode an JPEG stream.
public static WriteableBitmap DecodeJpegWithBitmapSource(Stream sourceStream)
{
   // Decode JPEG from stream
   var bitmapSource = new BitmapSource();
   bitmapSource.SetSource(sourceStream);
   return new WriteableBitmap(bitmapSource);
}

Source code
Check out my Codeplex project WriteableBitmapEx for an up to date version of the byte array conversion methods.

52 comments:

  1. Hi Rene!
    here's a bit optimized ToByteArray():
    public static byte[] ToByteArray(this WriteableBitmap bmp)
    {
    int[] p = bmp.Pixels;
    int len = p.Length << 2;
    byte[] result = new byte[len];
    Buffer.BlockCopy(p, 0, result, 0, len);
    return result;
    }

    FromByteArray() can be done in a similar fashion. I found this nice function today :)
    I'd add it to the WriteableBitmapEx on codeplex, but have too much other stuff to do :)

    ReplyDelete
  2. Thanks for the suggestion Nikola. I recently added a fastpath to the Blitting function using Buffer.BlockCopy(), which is 3x faster even for copying of each scanline. Somehow I forgot to alter the Convert methods. I'll change it. Thanks again for the info.

    BTW, I left a comment on your blog post about the TGA save, but you haven't replied yet.

    ReplyDelete
  3. Yes, I saw the comment just now. I really have to figure out a way to manage comments better - my site hoster's email service doesn't work as expected with the .NETblogengine and also having some spamming problems.. :) Nice job on the blitting :)

    btw, I wonder which is faster: Array.Copy() or Buffer.BlockCopy()

    ReplyDelete
  4. I just checked the implementation of Array.Copy and Buffer.BlockCopy using Reflector. Both map to internal CLR methods. So I guess the difference is not that much.

    ReplyDelete
  5. THanks for the article. Very interesting indeed. I am interested in a screen sharing application with SL and I wonder if your code can be used for this purpose. Any comment is highly appreciated. Thanks

    ReplyDelete
  6. Hi,

    it could work using MJPEG compression, but the biggest problem is the latency and the sync. See the latest discussion here:
    http://forums.silverlight.net/forums/t/145729.aspx

    And you need to make an elevated trust app in order to use COM for screen capturing.
    Riu made a nice OOB chat application including screen capturing, without compression. Check out the blog post and the video:
    http://pontonetpt.com/blogs/ruimarinho/archive/2010/02/16/streaming-webcam-amp-desktop-em-silverlight.aspx

    ReplyDelete
  7. Hello,

    Maybe I just don't see it, but how could I use the FromByteArray() Function without knowing the ImageSize?

    I would do something like this:
    - Open a File with the OpenFileDialog
    - Get the ByteArray from the Stream
    - Use FromByteArray to Fill the WritableBitmap
    - Resize it
    - And save it to the DB with ToByteArray()

    But to use the FromByteArray Function I have to create a WritableBitmap with a Size which I don't know...???

    Any help would be great!

    Thanks, Markus

    ReplyDelete
  8. That's true, you have also to store the size of the image in the DB, since you need to know the width and the height to create a WriteableBitmap.


    Use this code to load an image from disk:

    OpenFileDialog openFileDlg = new OpenFileDialog();
    if (openFileDlg.ShowDialog().Value)
    {
    using (var stream = openFileDlg.File.OpenRead())
    {
    var bitmapImage = new BitmapImage();
    bitmapImage.SetSource(stream);
    var wb = new WriteableBitmap(bitmapImage);

    byte[] data = wb.ToByteArray();

    // ... Write data and wb.PixelWidth + wb.PixelHeight to the DB
    }
    }

    ...


    Load the image from the DB:

    // ... Get data and width + height from DB

    var wb = new WriteableBitmap(width, height);
    wb.FromByteArray(data);

    ReplyDelete
  9. Hello Rene,

    thanks a lot for your very quick relpy!!! Now it works for me.

    In addition I use the FJCore Library to convert the WritableBitmap to a Jepg using your function from above before saving it into the DB so I don't have to remember the Width and Height... here is the Code...

    private void DoExecuteUploadDocumentPreviewCommand() {
    var dlg = new OpenFileDialog();
    dlg.Multiselect = false;
    dlg.Filter = "Vorschau Bilder|*.jpg;*.jpeg;*.png;*.bmp";
    if ((bool)dlg.ShowDialog()) {
    var file = dlg.File;
    if (file != null) {
    using (var stream = file.OpenRead()) {
    _dokument.Thumbnail = EnshureThumbSize(stream);
    }

    //Remember Original FileName
    _dokument.ThumbnailOriginalFileName = file.Name;
    try {
    //Works only in FullTrust Mode, but we try it... (If not in full Trust Mode, we remember only the Name of the File)
    _dokument.ThumbnailOriginalFileName = file.FullName;
    } catch { }

    SubmitChanges();
    InvokePropertyChanged("DocumentThumbImageSource");
    }
    }
    }

    private byte[] EnshureThumbSize(FileStream stream) {
    //Read the Stream into a BitmapImage
    var bitmapImage = new BitmapImage();
    bitmapImage.SetSource(stream);

    //Resize the Image - keeping the aspect ratio
    var orgImage = new WriteableBitmap(bitmapImage);
    var targetSize = GetScaleSize(new Size(orgImage.PixelWidth, orgImage.PixelHeight), new Size(120, 120));
    var thumbImage = orgImage.Resize(Convert.ToInt32(targetSize.Width), Convert.ToInt32(targetSize.Height), WriteableBitmapExtensions.Interpolation.Bilinear);

    //Convert it into a jpg with FJCore
    var destStream = new MemoryStream();
    thumbImage.EncodeJpeg(destStream);

    //Return a byte[] - to be saved in DB (so you don't have to remember with and height)
    destStream.Position = 0;
    var buffer = new byte[destStream.Length];
    destStream.Read(buffer, 0, buffer.Length);
    return buffer;
    }

    private Size GetScaleSize(Size orgSize, Size maxSize) {
    var retSize = new Size(orgSize.Width, orgSize.Height);
    if (orgSize.Width > maxSize.Width || orgSize.Height > maxSize.Height) {
    double factorX = 1;
    double factorY = 1;
    if (orgSize.Width > maxSize.Width) factorX = orgSize.Width / maxSize.Width;
    if (orgSize.Height > maxSize.Height) factorY = orgSize.Height / maxSize.Height;

    double factor = 1;
    if (factorX > factorY) factor = factorX; else factor = factorY;

    retSize.Width = orgSize.Width / factor;
    retSize.Height = orgSize.Height / factor;

    }
    return retSize;
    }

    nice Greetings,
    Markus

    ReplyDelete
  10. Great. Converting the image to a JPEG is also better for the disk space. :-)
    Glad I could help you.

    ReplyDelete
  11. Can you give an example of using IValueConverter interface to implement two way binding in an Image control. I tried this for a long time but with now success. It will be a lot of help if you can do this.
    Thanks for this great article.

    ReplyDelete
  12. I guess you want to bind a WriteableBitmap to an Image control's Source property.

    The problem is, in XAML the Source value of the image control is interpreted as URI.
    "You can set this property in XAML, but in this case you are setting the property as a URI. The XAML behavior relies on underlying type conversion that processes the string as a URI, and calls the BitmapImage(Uri) constructor. This in turn potentially requests a stream from that URI and returns the image source object."
    - taken from http://msdn.microsoft.com/en-us/library/system.windows.controls.image.source(v=VS.95).aspx

    So the Image control is always calling the BitmapImage(Uri) constructor internally.

    I don't like it either, but it's easier done in code-behind using this snippet:

    myImageControl.Source = myWriteableBitmap;

    ReplyDelete
  13. Why would creating a WriteableBitmap on the UI thread using a memory stream give a Catastrophic failure? I get an Exception E_UNEXPECTED when calling WriteableBitmap.SetSource using a new MemoryStream created from a byte[]. I'm trying to compress the sample from the VideoSink using FJCore but it throws when I set the source:


    protected override void OnSample(
    long sampleTime,
    long frameDuration,
    byte[] sampleData)
    {
    ...

    System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
    WriteableBitmap bitmap = new WriteableBitmap(w, l);
    MemoryStream memStrm = new MemoryStream(sampleData);
    bitmap.SetSource(memStrm); // Throws here
    CompressData(bitmap);
    }
    });
    ...
    }

    I’ve even tried creating the bitmap from a stream that used a static byte array:

    byte[] testData = new byte[] { 0X42, 0X4D, ... 0XFF, 0X00 };

    and it still throws. What’s weird, creating the bitmap using the static array from the button’s event handler works fine. But creating it using the handler from the Dispatcher (even though they execute on the same thread) will cause the exception.

    What am I missing here?

    ReplyDelete
  14. Hi OurTie,

    I'd recommend to use this Dispatcher:
    Application.Current.RootVisual.Dispatcher

    I guess this won't change much. But why do you want to use a memory stream? You get the pixels as interleaved ARGB byte array and want to fill a WriteableBitmap that uses an ARGB int as pixel.
    You can do something like this:

    // Copy sample data to WriteableBitmap
    var h = vidFormat.PixelHeight;
    var s = vidFormat.Stride;
    var bitmap = new WriteableBitmap(w, h);
    var pixels = bitmap.Pixels;
    int offset, sp;
    if (s < 0)
    {
    for (int y = 0; y < h; y++)
    {
    sp = -s;
    offset = sp * (h - (y + 1));
    Buffer.BlockCopy(sampleData, offset, pixels, sp * y, sp);
    }
    }
    else
    {
    for (int y = 0; y < h; y++)
    {
    offset = s * y;
    Buffer.BlockCopy(sampleData, offset, pixels, offset, s);
    }
    }

    ReplyDelete
  15. Hi,
    I am trying to resized dropped file and upload it on the server. Here is my code:
    private void DisplayPanel_Drop(object sender, DragEventArgs e)
    {
    StackPanel sp = sender as StackPanel;
    object dropObjectArray = e.Data.GetData("FileDrop");
    FileInfo[] fileObj = dropObjectArray as FileInfo[];
    foreach (FileInfo fi in fileObj)
    {
    FileStream dropFile = fi.OpenRead();
    BitmapImage bitmapImage = new BitmapImage();
    bitmapImage.SetSource(dropFile);

    WriteableBitmap wb = new WriteableBitmap(bitmapImage);

    WriteableBitmap resizedWb = wb.Resize(100, 100, WriteableBitmapExtensions.Interpolation.Bilinear);
    byte[] data = resizedWb.ToByteArray();

    Image image = new Image();
    image.Source = resizedWb;
    sp.Children.Add(image);

    // upload file resized file on the server
    UploadFile(data,dropFile.Name);
    }

    }
    // I am uploading the file using DomainService
    File.WriteAllBytes(fileName, data);

    The resized image is showing up inside stackpanel, but the saved file in the server seems not in a correct format. Windows cannot read the file and says this is not a valid bitmap file.
    What is wrong?

    ReplyDelete
  16. Hi Seeed,

    you're just converting it to an ARGB byte array which is then saved. This raw data is not a common file format for windows.
    Try the WriteableBitmapEx WriteTga() method instead or use the JPEG encoding I provided above.

    ReplyDelete
  17. Hi All,

    I am new to Silverlight and using this to upload multiple Tif files but before uploading want to put condition on Width and Height. Below code is working fine for JPG but not for TIF.

    Anyhelp would be much appreciated. Thanks in advance

    //Read the Stream into a BitmapImage
    var bitmapImage = new BitmapImage();
    bitmapImage.SetSource(stream);

    here I can retrived the pixelwidth and height using bimapImage.pixelwidth but not working for TIF.


    Regards
    Puran

    ReplyDelete
  18. TIFF is not supported. Only PNG and JPG images can be used.
    See http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapimage(VS.95).aspx

    ReplyDelete
  19. Is there any work-around to deal with TIFF files

    ReplyDelete
  20. @sonu: http://lmgtfy.com/?q=silverlight+tiff

    ReplyDelete
  21. Hi Rene,

    Is there a way to convert a WriteableBitmap back to a BitmapImage?

    I'm using a WriteableBitmap to create thumbnails of user selected images; the problem is the application is an upgraded Silverlight 2 app and it makes a lot of use of BitmapImages and would take too long to fully convert.

    Thanks

    ReplyDelete
  22. The only would be to convert the WriteableBitmap data to a supported format like JPEG and use BitmapImage.SetSource with a MemoryStream.

    I would strongly recommend to change the code so it uses the base class of the WriteableBitmap, BitmapImage, the BitmapSource. See:
    http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.bitmapsource(v=VS.95).aspx

    ReplyDelete
  23. Hi Rene, many thanks for the details; I have changed the code to use the base BitmapSource class and it allows me swap between the WriteableBitmap and BitmapImage classes when displaying local and remote images.

    ReplyDelete
  24. Rene',

    Im trying to verify that the byte[] I get back from the EncodeJpeg method is a valid jpeg image.

    byte[] jpegImage;

    using (MemoryStream dstStream = new MemoryStream())
    {
    // Encode JPEG and write to FileStream
    Methods.EncodeJpeg(lastSnapShot, dstStream);

    //Return a byte[] - to be saved in DB
    dstStream.Position = 0;
    var buffer = new byte[dstStream.Length];
    dstStream.Read(buffer, 0, buffer.Length);

    jpegImage = buffer;

    MemoryStream ms = new MemoryStream();
    ms.Read(jpegImage, 0, jpegImage.Length);
    WriteableBitmap x = new WriteableBitmap(480, 640);
    x = Methods.DecodeJpeg(ms);
    this.image1.Source = x;

    The FJcore class BinaryReader keeps erroring on me telling me:
    "attempting to read past the end of the stream"

    Any suggestions?

    ReplyDelete
  25. You've written to the stream, so it's at the end. Just use Seek or Position = 0 on the stream.

    ReplyDelete
  26. Brilliant!

    It is working, but any reason the image comes out looking all crazy?

    ReplyDelete
  27. So my test was this:

    // Without any encoding the captured image (the writeable bitmap) looks perfect
    this.image1.Source = lastSnapShot;

    // After the FJcore encoding the image is corrupt

    using (MemoryStream dstStream = new MemoryStream())
    {
    // Encode JPEG and write to FileStream
    Methods.EncodeJpeg(lastSnapShot, dstStream);

    //Return a byte[] to be saved in DB
    var buffer = new byte[dstStream.Length];
    dstStream.Read(buffer, 0, buffer.Length);

    // At this point dstStream should contain the encoded jpeg

    // Next we will decode it and set it as the source of an image object to test it

    dstStream.Position = 0;
    WriteableBitmap x = new WriteableBitmap(640, 480);
    x = Methods.DecodeJpeg(dstStream);
    this.image1.Source = x;
    }

    // The image comes out like it's repeating the top 15% of the picture and has been rotated and is very skewed. It is un-viewable.

    Any ideas?

    ReplyDelete
  28. Hi,

    Using the above code in Windows Phone 7 works for the encode, but I get the following exception in the decode: System.Reflection.Emit.OpCodes.Ldarg_0 on line var jpegDecoded = decoder.Decode();

    I can't work out why. Any ideas?

    ReplyDelete
  29. Hi Anonymous,

    Reflection.Emit is not supported by the .Net Compact Framework, which is the underlying runtime on Windows Phone.
    You should use LoadJpeg and SaveJpeg to decode / encode a WriteableBitmap load / save as JPEG stream.
    http://msdn.microsoft.com/en-us/library/ff707350(v=VS.92).aspx

    ReplyDelete
  30. Thanks heaps. I knew there must have been an library for this, but I couldn't find it.

    Regards Ray (not Anonymous)

    ReplyDelete
  31. Jim,

    I have the same exact issue.

    public class ImageByteConverter : IValueConverter
    {
    ///
    /// Converts a Jpeg byte array into a WriteableBitmap
    ///
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    if (value == null)
    return null;


    byte[] val = (byte[])value;

    MemoryStream ms = new MemoryStream(val);

    return WriteableBitmapExtentions.DecodeJpeg(ms);
    }



    ///
    /// Converts a WriteableBitmap into a Jpeg byte array.
    ///
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
    if (value == null)
    return null;

    WriteableBitmap wb = (WriteableBitmap)value;
    return wb.EncodeJpeg();
    }
    }

    ReplyDelete
  32. AHA!

    You accidentally reversed the interval of x and y... it should be :

    for (int y = 0; y < h; y++)
    {
    for (int x = 0; x < w; x++)
    {

    p[i++] = (0xFF << 24) // A
    | (pixelsFromJpeg[0][x, y] << 16) // R
    | (pixelsFromJpeg[1][x, y] << 8) // G
    | pixelsFromJpeg[2][x, y]; // B

    }
    }

    ReplyDelete
  33. Good catch David. Yes, the Decode is reversed. I must admit that I always use the built-in BitmapImage class and its SetSource method for JPEG decoding.

    Another thing about the IValueConverter. Don't do this subsequently with the same image. JPEG is a lossy compression and will result in image artifacts if the encode is performed too often. You should rather save the original stream if it is already encoded.

    ReplyDelete
  34. Rene,

    I didn't think about the artifacts. THanks!

    ReplyDelete
  35. The ToByteArray method seems to be exactly what I need for my app. Unfortunately when I save the byte[] to a file, it's not creating a valid image. (Similarly if I upload the byte[] to a SharePoint document library, the image is invalid as well.)

    I've validated that the WriteableBitmap is valid. The new file does consist of 153,600 bytes, which equals the height x width x 4, which I'm guessing is right. Any suggestions on what else I may be able to do to debug? I'm assuming the methods simply works "as is" and doesn't require and changes.

    ReplyDelete
  36. Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
    This will not work ( Phone 7 ) because bmp.Pixels is a readonly property and it can not be assigned.

    ReplyDelete
  37. Nope, the Pixels property has a setter.

    ReplyDelete
  38. Hi Rene

    Love your articles, thanks they help me a lot.

    One point that I would like to request...

    I know all developers have fantastic memory and know by instinct what name-space to use for all code, but... for us lesser developer types could you add the name-space

    Many thanks

    MrBronz

    ReplyDelete
  39. Hi Rene,

    Great article.

    Could I still use this method to encode the video captured with the webcam?

    Thanks in advance.

    ReplyDelete
  40. hello Rene,
    I was working on sending a webcam-captured image to the server using WCF service & i was using this ToByteArray() method

    //CLIENT Side

    byte[] byteArray = writeableBitmapSource.ToByteArray();

    ImageGenerateServiceClient serviceClient = new ImageGenerateServiceClient();
    serviceClient.GenerateCompleted += new EventHandler(serviceClient_GenerateCompleted);
    serviceClient.GenerateAsync(byteArray);

    //SERVER Side

    MemoryStream memoryStream = new MemoryStream(byteArray);
    Image image = Image.FromStream(memoryStream);

    ...then I was using image.Save() method. But what i get is "Parameter is not valid." execption :(

    ...then for testing purpose i used the returned byte[] to provide the Source of an Image control as follows

    MemoryStream memoryStream = new MemoryStream(byteArray);
    BitmapImage bitmapImage = new BitmapImage();
    bitmapImage.SetSource(memoryStream);
    imgPreview.Source = bitmapImage;

    ...but this time i got "Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))" exception :( :(

    what did i miss? can you help?

    ReplyDelete
  41. You have to encode it on the client side to a JPEG, not just a byte array of the raw ARGB data. The Image.FromStream and BitmapImage expect a JPEG.

    ReplyDelete
  42. thanks a LOT man, it's working. my first silverlight app & my first wcf service, and it's WORKIN' :D

    ReplyDelete
  43. Hi Rene

    You say to Nero that he must encode it on the client side to a JPEG, not just a byte array of the raw ARGB .How can I do that I have the same problem.

    ReplyDelete
  44. Hey Illya,

    You can find all you need in the above blog post in the DecodeJpeg method. Just remove the part where the WriteableBitmap is created and the pixels are copied into it.

    ReplyDelete
  45. Hello Rene,

    I tried to implement the conversion of the WriteableBitmap to an ARGB byte array that you posted but I can't seem to find the Pixels property. (int[] p = bmp.Pixels;)
    Can you tell me what's the problem?

    Thank you.

    ReplyDelete
  46. Hello!
    Can I save an image to BMP format?

    ReplyDelete
  47. Sure you can, you just have to implement the BMP spec which should be rather easy. I guess there's an implementation available out there you can re-use. The WriteableBitmapEx also has a WriteTga extension method you can use. Tga is similar o bmp but supports the alpha channel.

    ReplyDelete
  48. Hello Rene!

    Using EncodeJpeg method is it possible to calculate beforehand the second argument "quality" in JpegEncoder constructor? I mean if I have a max weight for final jpeg image and if while encoding it with Encode method of JpegEncoder I get the weight bigger than required I need to resize the image again and again lowing down the quality parameter. So are there ways to figure out the quality parameter?

    Stanley

    ReplyDelete
  49. Hey Stanley,

    I'm not aware there's something like this built-in. You can probably make your own heuristics, but in the end the best result is just computing the size in the background. See how pro photo editing software handles it.

    ReplyDelete
  50. please convert this part into vb.net
    public static void EncodeJpeg(this WriteableBitmap bitmap, MemoryStream stream, int quality = 90)
    {
    int width = bitmap.PixelWidth;
    int height = bitmap.PixelHeight;
    int bands = 3;
    byte[][,] raster = new byte[bands][,];
    for (int i = 0; i < bands; i++)
    {
    raster[i] = new byte[width, height];
    }
    for (int row = 0; row < height; row++)
    {
    for (int column = 0; column < width; column++)
    {
    int pixel = bitmap.Pixels[width * row + column];
    raster[0][column, row] = (byte)(pixel >> 16); //R
    raster[1][column, row] = (byte)(pixel >> 8); //G
    raster[2][column, row] = (byte)pixel; //B
    }
    }
    ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
    FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

    JpegEncoder encoder = new JpegEncoder(img, quality, stream);
    encoder.Encode();

    stream.Seek(0, SeekOrigin.Begin);

    }

    ReplyDelete
  51. C# <-> VB.Net
    http://converter.telerik.com

    ReplyDelete