none
Saving & Loading Bitmaps using Bitmap.Save and BitmapFromStream RRS feed

  • Question

  • Hi everyone,

    I'm working on a project that involves some image manipulation and I've just gotten started. However, I've recently run into a situation where I am getting a runtime error.

    In my code, I am saving a collection of bitmaps to a single file:

    foreach
    
     ( Image bitmap in
     bitmapCollection.Images )
    {
        tile.Save( outputFile, System.Drawing.Imaging.ImageFormat.Bmp);
    }
    

    I have also saved the number of them I have wirrten to the file, so when I go to load them I know how many. However, when I go to read them using the code:
    byte
    
    [] buffer = null
    ;
    Image tempBitmap = null
    ;
    
    for ( int i = 0; i < bitmapCount; i++ )
    { buffer = new byte [bitmapCount]; inputFile.Read( buffer, 0, (int )datafileHeader.tileSize[0] ); tempBitmap = Bitmap.FromStream( tempStream ); bitmapCollection.Images.Add( tempBitmap ); }

    I receive:

    A first chance exception of type 'System.ArgumentException'
     occurred in
     System.Drawing.dll
    An unhandled exception of type 'System.ArgumentException'
     occurred in
     System.Drawing.dll
    
    Additional information: Parameter is
     not valid.
    

    Any suggestions for resolving this, or a better method of loading?
    Monday, August 24, 2009 1:54 AM

Answers

  • There may be a use for proprietary formats.  Using a TIFF to store multiple images with different frame dimensions becomes complex quickly.  Try this:

    using System;
    using System.IO;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
          MS.Parent = this;
          MS.Items.Add(Combine);
          MS.Items.Add(Split);
          PB.Parent = this;
          PB.Dock = DockStyle.Fill;
          PB.SizeMode = PictureBoxSizeMode.Zoom;
          Combine.Click += Combine_Click;
          Split.Click += Split_Click;
        }
        MenuStrip MS = new MenuStrip();
        ToolStripLabel Combine = new ToolStripLabel("Combine");
        ToolStripLabel Split = new ToolStripLabel("Split");
        PictureBox PB = new PictureBox();
        void Combine_Click(object sender, EventArgs e)
        {
          string[] PictureFiles  = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg", SearchOption.TopDirectoryOnly);
          using (FileStream FS = new FileStream("CombinedPictures.MyMultiplePicture", FileMode.Create))
          {
            foreach (string PictureFile in PictureFiles)
            {
              PB.Image = new Bitmap(PictureFile);
              PB.Refresh();
              using (MemoryStream MS = new MemoryStream())
              {
                PB.Image.Save(MS, ImageFormat.Jpeg);
                FS.Write(BitConverter.GetBytes((int)MS.Length), 0, 4);
                FS.Write(MS.GetBuffer(), 0, (int)MS.Length);
                PB.Image.Dispose();
              }
            }
          }
        }
        void Split_Click(object sender, EventArgs e)
        {
          using (FileStream FS = new FileStream("CombinedPictures.MyMultiplePicture", FileMode.Open))
          {
            while (FS.Position < FS.Length)
            {
              byte[] BLength = new byte[4];
              FS.Read(BLength, 0, 4);
              byte[] B = new byte[BitConverter.ToInt32(BLength, 0)];
              FS.Read(B, 0, B.Length);
              using (MemoryStream MS = new MemoryStream(B))
              {
                PB.Image = new Bitmap(MS);
                PB.Refresh();
                PB.Image.Dispose();
              }
            }
          }
        }
      }
    }
    
    Wednesday, August 26, 2009 6:42 AM

All replies

  • Save your bitmaps in a TIFF container.  Why do you want to save multiple uncompressed bitmaps to a single file?
    Monday, August 24, 2009 4:36 AM
  • I'll worry about file types and compression later on, for the moment I'm just trying to work out how to do some of the things I'd like. Did you have any resources you could point me to?
    Monday, August 24, 2009 6:02 AM
  • I'll worry about file types and compression later on, for the moment I'm just trying to work out how to do some of the things I'd like. Did you have any resources you could point me to?

    What is it you are trying to do?  If you want to save multiple images in a single file, use the TIFF container which is designed for that purpose.  The MSDN library is the best resource for programing using the .NET framework.
    Monday, August 24, 2009 6:13 AM
  •         buffer = new byte[bitmapCount];
            inputFile.Read(buffer, 0, (int)datafileHeader.tileSize[0]);

    What does that do? 

    You are counting on Image.FromStream() to leave the stream positioned at the next image.  FromStream() makes no such guarantees.  In fact, it uses Stream.Seek() to pick up various parts of the image data.  This is not likely to work out well if the stream in fact contains multiple images.  Well, it doesn't as the exception tells you.

    To avoid this, you could read the bytes of the image yourself into a MemoryStream, then load the image from that.  This requires you to know the number of bytes in the image data, you'll have to record this when you save the images.  Custom file formats are not a very good idea.
    Hans Passant.
    Monday, August 24, 2009 12:47 PM
    Moderator
  • Ah, oops, I think I had a typo when pasting that.

    It actually reads:

    for ( int i = 0; i <bitmapCount; i++ )
    {
        buffer = new byte[bitmapSize[i]];
        binaryReader.Read( buffer, 0, (int)bitmapSize[i] );
        MemoryStream tempStream = new MemoryStream( buffer );
        tempBitmap = Bitmap.FromStream( tempStream );
        bitmapCollection.Images.Add( tempBitmap );
    }

    So earlier I have stored and read in the sizes of each bitmap inside the file. So I was doing it just how you said, nobugz. it reads the bytes of the image into the buffer, creates a memory stream containing aforementioned bytes, and then tries to create a bitmap from that image.  I have done custom files plenty in the past, and using lower level routines, such as the C headers, so I'm not unfamiliar with them. However, this exception is somewhat puzzling to me. I may try saving out the memory stream again as a bitmap file, if anything to verify visually that the data being read into the buffer is correct.
    Tuesday, August 25, 2009 4:35 AM
  • Okay, that makes more sense.  An obvious failure mode is getting the size wrong.  Forgetting about the metadata, the color table, the stride.  I don't see you record the size anywhere in your code that calls Image.Save().

    Hans Passant.
    Tuesday, August 25, 2009 4:58 AM
    Moderator
  • What's the point.  Why do you need a custom container?  You can encrypt a standard container.  Seems like reinventing the wheel to me.
    Tuesday, August 25, 2009 6:46 AM
  • There may be a use for proprietary formats.  Using a TIFF to store multiple images with different frame dimensions becomes complex quickly.  Try this:

    using System;
    using System.IO;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
          MS.Parent = this;
          MS.Items.Add(Combine);
          MS.Items.Add(Split);
          PB.Parent = this;
          PB.Dock = DockStyle.Fill;
          PB.SizeMode = PictureBoxSizeMode.Zoom;
          Combine.Click += Combine_Click;
          Split.Click += Split_Click;
        }
        MenuStrip MS = new MenuStrip();
        ToolStripLabel Combine = new ToolStripLabel("Combine");
        ToolStripLabel Split = new ToolStripLabel("Split");
        PictureBox PB = new PictureBox();
        void Combine_Click(object sender, EventArgs e)
        {
          string[] PictureFiles  = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg", SearchOption.TopDirectoryOnly);
          using (FileStream FS = new FileStream("CombinedPictures.MyMultiplePicture", FileMode.Create))
          {
            foreach (string PictureFile in PictureFiles)
            {
              PB.Image = new Bitmap(PictureFile);
              PB.Refresh();
              using (MemoryStream MS = new MemoryStream())
              {
                PB.Image.Save(MS, ImageFormat.Jpeg);
                FS.Write(BitConverter.GetBytes((int)MS.Length), 0, 4);
                FS.Write(MS.GetBuffer(), 0, (int)MS.Length);
                PB.Image.Dispose();
              }
            }
          }
        }
        void Split_Click(object sender, EventArgs e)
        {
          using (FileStream FS = new FileStream("CombinedPictures.MyMultiplePicture", FileMode.Open))
          {
            while (FS.Position < FS.Length)
            {
              byte[] BLength = new byte[4];
              FS.Read(BLength, 0, 4);
              byte[] B = new byte[BitConverter.ToInt32(BLength, 0)];
              FS.Read(B, 0, B.Length);
              using (MemoryStream MS = new MemoryStream(B))
              {
                PB.Image = new Bitmap(MS);
                PB.Refresh();
                PB.Image.Dispose();
              }
            }
          }
        }
      }
    }
    
    Wednesday, August 26, 2009 6:42 AM
  • Thanks John, I was able to do it by modifying the code you provided. And in reply to your earlier comment, I'm not simply re-inventing the wheel, but I'll be doing additional modifications to the file structure to store more information regarding the set of images. I may use PNGs later on, but this was more of a proof-of-concept work.
    Sunday, August 30, 2009 6:51 PM