It turns out that System.Drawing.Bitmap cannot be serialized accross the wire, which means that you need to convert it to a byte[] on one end, and re-serialize on another. This is, of course, done using MemoryStream, but it’s very hard to get the encoding right on the sending end, so the best bet for the web service is to use the Bitmap format:

using (var ms = new MemoryStream())
{
  bmp2.Save(ms, ImageFormat.Bmp);
  using (var c = new UploadServiceSoapClient())
  {
    string result = c.UploadImage(ms.ToArray(), "secret_key");
    Clipboard.SetText(result);
    MessageBox.Show(result);
  }
}

What the above implies is that a fairly large chunk of data (width × height × 4) is sent accross the wire. But, on the other hand, you can write a more predictable image-saving function:

using (var ms = new MemoryStream(bitmapData))
using (var bmp = new Bitmap(ms))
{
  // make a filename and save
  Guid g = Guid.NewGuid();
  string filename = string.Format(@"c:\temp\{0}.jpg", g);
  SaveImageAsJpeg(bmp, filename);
  return string.Format(@"http://tempuri.org/{0}.jpg", g);
}

Anyways, I don’t know if there’s a more efficient way of doing this but, at least it works. Oh, and the encode-and-save function should make for a nice example in defensive programming:

private static void SaveImageAsJpeg(Image bmp, string filename)
{
  if (File.Exists(filename))
    File.Delete(filename);
  var codecs = ImageCodecInfo.GetImageEncoders().Where(c => c.MimeType == "image/jpeg");
  if (codecs.Any())
  {
    EncoderParameters ep = new EncoderParameters(1);
    ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
    bmp.Save(filename, codecs.First(), ep);
  }
  else
    bmp.Save(filename, ImageFormat.Jpeg);
}