CB Media Item Photos

Back to CB Media Items...

Some media items use visual elements such as images and photos. This implies that the media item programmer needs to be able to at least:

  • import an image into their program
  • scale the image to fit into the media item tile/tooltip grande/separate view
  • compress the image (if large) so it can be sent over the network without unreasonable overhead
  • marshall the compressed image so it can be added to the shared dictionary
  • on the receiving end, unmarshall, uncompress, and display the image

This may sound daunting, but it is actually quite easy to do if you use some of the capabilities of the Collabrary, a library developed by Michael Boyle and installed as part of the Community Bar.

Example Code: The very simple case

  • Load an image from a file,
  • compress it and put it into the shared dictionary
  • from a subscription, uncompress it and present it in a picture box
//The photo will be stored in a Collabrary.Photo object
public Collabrary.Photo photo = new Collabrary.Photo ();

//This is the key in the shared dictionary where the photo will be stored
string KeyPhoto = "/some/path/photo";

//Load a photo from a file, resize it if needed, and send it to the shared dictionary
public bool NewPhotoFromFile (string filepath)
{
  // Create a Collabrary JPEG objection that we will use to compress the photo
  // JPG quality is between 1 - 100; 80 does good compression while maintaining image quality
  Collabrary.JPEG jpg = new Collabrary.JPEGClass ();
  jpg.Quality = 80

  // Load the image directly into the photo
        this.photo.Load(filepath);

  // Store it in the shared dictionary
  this.sd [this.KeyPhoto] = jpg.Compress (photo); //photo;
}

// This subscription notification is called on updates to the value in KeyPath
private void subcriptionPhoto_Notified(object sender, SubscriptionEventArgs e)
{
  // Do something only if we have a new or updated photo
  if(GroupLab.Networking.SubscriptionNotification.Add    == e.Reason ||
     GroupLab.Networking.SubscriptionNotification.Change == e.Reason)
  {
    // We need to store the compressed image in a jpg object
    // Note that we have to do a cast of the value into a 'memory' object
    Collabrary.JPEG jpg = new Collabrary.JPEGClass ();
    this.photo = (Collabrary.Photo) jpg.Decompress ((Collabrary.Memory) e.Value);

          // put the picture in the PictureBox by using its Hbitmap property
    this.pictureBox.Image = Image.FromHbitmap (photo.Hbitmap)
  }
}
 

Example Code: A more complex case

This code is almost identical to the above, except its logic now assumes:

  • You are supplied with a file path for an image that you would like to incorporate in a media item. Of course, you can change this code to get this image by many means (e.g., as a resource, as a drag and drop action, etc).
  • Because images can be huge, you decide that its maximum size is 1280 x 1024
  • You wish to store it in a shared dictionary key defined by the concatenation of KeyPath
  • You subscribe to this key path, where the notification is called subcriptionPhoto_Notified
  • When you get the notification, uncompress it
  • then resize it to best fit in a picture box located in a tile

What the Sending Code Does


//The photo will be stored in a Collabrary.Photo object
public Collabrary.Photo photo = new Collabrary.Photo ();

//This is the key in the shared dictionary where the photo will be stored
string KeyPhoto = "/some/path/photo";

//Load a photo from a file, resize it if needed, and send it to the shared dictionary
public bool NewPhotoFromFile (string filepath)
{
  try
  {
     // Create a Collabrary JPEG objection that we will use to compress the photo
     // JPG quality is between 1 - 100; 80 does good compression while maintaining image quality
    Collabrary.JPEG jpg = new Collabrary.JPEGClass ();
    jpg.Quality = 80

    this.photo.Load(filepath);

    // For large images, force a maximum size of 1280 for height or 1024 for width
    // Note how it uses the Photo.Resize method, where the 'true' argument directs it
                // to create a nice smoothed high quality image
    if ( (photo.Width > photo.Height) && photo.Width > 1024)
    {
      double w = 1024.0 / (double)  photo.Width * (double) photo.Height;
      photo.Resize (1024, (int) w, true);
    }
    else if ( photo.Height > 1280)
    {
      double h = 1280.0 / (double) photo.Height  * (double) photo.Width;
      photo.Resize ( (int) h, 1280, true);
    }
    // Now we 'send' the photo to the shared dictionary.
   // Add the compressed image to the key path in the shared dictionary.
   // Note that marshalling is automatic: you just assign it as the key's value
    this.sd [this.KeyPhoto] = jpg.Compress (photo); //photo;

    return (true);
  }
  catch (Exception exc)
  {
    return (false);
  }
}

What the Receiving Code Does

// This subscription notification is called on updates to the value in KeyPhoto
private void subcriptionPhoto_Notified(object sender, SubscriptionEventArgs e)
{
  // Do something only if we have a new or updated photo
  if(GroupLab.Networking.SubscriptionNotification.Add    == e.Reason ||
     GroupLab.Networking.SubscriptionNotification.Change == e.Reason)
  {
    // This really shouldn't be in a try /catch, but I had a cast error message
    // raised one time and I don't know why...
    // Anyways, it may not do anything when there is a bug, but
    // the consequence of not showing the photo will be small.
    // Perhaps instead I should make an 'error' photo that I display?
    try
    {
      // We need to store the compressed image in a jpg object
      // Note that we have to do a cast of the value into a 'memory' object
      Collabrary.JPEG jpg = new Collabrary.JPEGClass ();
      this.photo = (Collabrary.Photo) jpg.Decompress ((Collabrary.Memory) e.Value);

      this.tile.TileUpdate (this.photo);
      foreach(Transient t in this.transients)
      {
        t.TransientUpdate (this.photo); //Not shown
      }
      if (this.ms != null)
        ms.SeparateUpdate (this.photo); //Not shown
    }
    catch (Exception exc)
    {
    }
  }
}

//Use this image to update the tile (using a picture box called pb).
// Note that we will have to resize the image to fit the picture box...
public void TileUpdate (Collabrary.Photo p)
{
        // Make a copy of the photo, which we will resize.
  Collabrary.Photo tmpPhoto  = (Collabrary.Photo) p.Copy(0,0,0,0,Collabrary.PhotoCopyStyle.Pixels);

        // Get the best size that will resize the picture to fit the picture box
  System.Drawing.Point size = main.GetNewPhotoSize (p.Width, p.Height, pbPhoto.Width, pbPhoto.Height);

        // Now resize it
  tmpPhoto.Resize (size.X, size.Y, true);

        // Finally, put the picture in the PictureBox by using its Hbitmap property
  this.pbPhoto.Image = Image.FromHbitmap (tmpPhoto.Hbitmap)
}

// Given the size of a picture and its container,
// calculate a new size so the picture fits as best as it can in the container
public System.Drawing.Point GetNewPhotoSize (int picWidth, int picHeight, int contWidth, int contHeight)
{
  System.Drawing.Point pt = new System.Drawing.Point (0,0);

  float photoRatio = (float)picHeight/(float)picWidth;
  // try to make the image match the width
  pt.Y = (int)(contWidth*photoRatio);
  if(pt.Y < contHeight)
  {
    // it worked
    pt.X = contWidth;
  }
  else
  {
    pt.X = (int) ((float)contHeight*(1/photoRatio));
    pt.Y = contHeight;
  }
  //System.Windows.Forms.MessageBox.Show(pt.X + "," + pt.Y);
  return pt;

}

Links