The "Tech. Arch."

Architecting Forward ::>

Sending Cropper Captures to TwitPic

Capture Screenshots

For a while my tool of choice for creating screen captures has been Cropper, a utility created by Brian Scott, and hosted on CodePlex
There is also a series of plugins that allow you to for example post captures to Flickr.
One thing I have always wanted was to send image captures to my TwitPic account so I can easily link them to my tweets.

SendToTwitPic

Building A Cropper Plugin For TwitPic

So I finally decided to build my own plugin. For that I first studied the way the other plugins were written, especially the one to send to Flickr.

Approach

A Cropper plugin consists of the following parts:

  • A settings class containing properties that will hold the plugin configuration. For example:
       public class TwitPicSettings
        {
           private string _twitPicID = "";
           private string _twitPicPassword = "";
     
           public string TwitPicID
           {
                get { return _twitPicID; }
                set { _twitPicID = value; }
           }
    
           // etc.
        }
    
  • An Option dialog class allowing users to edit the plugin configuration.SendToTwitPic Options Dialog
  • The plugin itself

The plugin needs to implement 2 interfaces:

  • IConfigurablePlugin – this is how Cropper configures and retrieves our custom settings
  • IPersistableImageFormat – this is how Cropper hands over the captured image so we can act on it.

Let’s see what the code looks like. Note: I removed all comments for brevity.

IConfigurablePlugin Implementation

This part is straightforward since it is exposing the settings and allows loading and saving of the settings to or from the dialog.

        public BaseConfigurationForm ConfigurationForm
        {
            get
            {
                if (_configurationForm == null)
                {
                    _configurationForm = new Options();
                    _configurationForm.OptionsSaved += Handle_configurationFormOptionsSaved;
                    _configurationForm.TwitPicID       = PluginSettings.TwitPicID;
                    _configurationForm.TwitPicPassword = PluginSettings.TwitPicPassword;
                    _configurationForm.ShowDebugInfo   = PluginSettings.ShowDebugInfo;
                }
                return _configurationForm;
            }
        }

        public bool HostInOptions
        {
            get { return hostInOptions; }
        }

        public object Settings
        {
            get { return PluginSettings; }
            set { PluginSettings = value as TwitPicSettings; }
        }

        public TwitPicSettings PluginSettings
        {
            get
            {
                if (_settings == null)
                    _settings = new TwitPicSettings();
                return _settings;
            }
            set { _settings = value; }
        }

        private void Handle_configurationFormOptionsSaved(object sender, EventArgs e)
        {
            PluginSettings.TwitPicID       = _configurationForm.TwitPicID;
            PluginSettings.TwitPicPassword = _configurationForm.TwitPicPassword;
            PluginSettings.ShowDebugInfo   = _configurationForm.ShowDebugInfo;
        }

IPersistableImageFormat Implementation

There are 2 types of responsibilities

  • Providing user interface elements (description, menu, etc) to the main Cropper UI:
            public string Description
            {
                get { return "Send screenshot to your TwitPic account."; }
            }
    
            public string Extension
            {
                get { return "Png"; }
            }
    
            public override string ToString()
            {
                return "Send to your TwitPic account";
            }
            public MenuItem Menu
            {
                get
                {
                    MenuItem item = new MenuItem();
                    item.RadioCheck = true;
                    item.Text = "Send to TwitPic";
                    item.Click += new EventHandler(this.MenuItemClick);
                    return item;
                }
            }
    
            private void MenuItemClick(object sender, EventArgs e)
            {
                ImageFormatEventArgs args = new ImageFormatEventArgs();
                args.ClickedMenuItem = (MenuItem)sender;
                args.ImageOutputFormat = this;
                this.OnImageFormatClick(sender, args);
            }
      
  • Implementing callbacks to handle the image capture process:
            public IPersistableImageFormat Format
            {
                get { return this; }
            }
    
            public void Connect(IPersistableOutput persistableOutput)
            {
                if (persistableOutput == null)
                {
                    throw new ArgumentNullException("persistableOutput");
                }
                _output = persistableOutput;
                _output.ImageCaptured  += new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);
                _output.ImageCapturing += new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);
            }
    
            public void Disconnect()
            {
                _output.ImageCaptured -= new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);
                _output.ImageCapturing -= new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);
            }
    
            public event ImageFormatClickEventHandler ImageFormatClick;
    
            private void OnImageFormatClick(object sender, ImageFormatEventArgs e)
            {
                if (ImageFormatClick != null)
                {
                    ImageFormatClick(sender, e);
                }
            }
    
            void persistableOutput_ImageCapturing(object sender, ImageCapturingEventArgs e)
            {
                this.LogDebugInfo("Capturing an image via Cropper-TwitPic");
            }
    
            void persistableOutput_ImageCaptured(object sender, ImageCapturedEventArgs e)
            {
              // ... 
            }
      

The persistableOutput_ImageCaptured method is where all the action will take place such as:

  • Writing the image to a memory stream and encoding it:
                try
                {
                    ImagePairNames imgNames = e.ImageNames;
                    this.FileName = e.ImageNames.FullSize;
                    this.LogDebugInfo(string.Format("Converting the captured image to .png - file name: {0}", this.FileName));
    
                    // Convert the captured image as .png and string encode it so we can upload it
                    MemoryStream imageStream = new MemoryStream();
                    e.FullSizeImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Png);
                    imageStream.Position = 0;
                    byte[] data = imageStream.ToArray();
                    this.ImageFileContents = Encoding.GetEncoding("iso-8859-1").GetString(data);
    
                    this.PostToTwitPic();
                    MessageBox.Show(string.Format("Your image is available on TwitPic atn{0}",this.PostedMediaUrl),
                                    "Cropper.SendToTwitPic");
                }
                catch (Exception ex)
                {
                  // ...
                }
     
  • Creating a web request so we can post the image to TwitPic using its upload API – see more details on TwiPic
            public void PostToTwitPic()
            {
                // Reset TwitPic output
                this.PostedMediaUrl = string.Empty;
                this.TwitPicErrorMsg = string.Empty;
    
                // Create the TwitPic request
                string formDataBoundary = DateTime.Now.Ticks.ToString("x");
                string formDataEncoding = "iso-8859-1";
    
                this.TwitPicRequest = this.CreateRequestContents(formDataBoundary);
                byte[] bytes = Encoding.GetEncoding(formDataEncoding).GetBytes(this.TwitPicRequest);
                
                // Initialize the request for TwitPic
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.TwitPicPostingUrl);
                request.Method = "POST";
                request.CookieContainer = null;
                request.Proxy.Credentials = CredentialCache.DefaultCredentials;
                request.PreAuthenticate = true;
                request.AllowWriteStreamBuffering = true;
                request.ContentType = string.Format("multipart/form-data; boundary={0}", formDataBoundary);
                request.ContentLength = bytes.Length;
    
                // Send to TwitPic
                using (Stream requestStream = request.GetRequestStream())
                {
                    // Post the data
                    this.LogDebugInfo(string.Format("Uploading the captured image to: {0}", this.TwitPicPostingUrl));
                    requestStream.Write(bytes, 0, bytes.Length);
    
                    // Get the response
                    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                    {
                        this.LogDebugInfo(string.Format("Getting the upload status from TwitPic"));
    
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            string result = reader.ReadToEnd();
                            XmlDocument docResult = new XmlDocument();
                            docResult.LoadXml(result);
                            XmlNode nodeResult = docResult.SelectSingleNode("/rsp");
                            if (nodeResult == null)
                                EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME, 
                                                    "Did not receive a valid status response from TwitPic", 
                                                    EventLogEntryType.Error, 
                                                    2);
                            else
                            {
                                string resultCode = nodeResult.Attributes["stat"].Value;
                                if ("ok" == resultCode)
                                {
                                    XmlNode nodeMediaUrl = docResult.SelectSingleNode("//mediaurl");
                                    this.PostedMediaUrl = (nodeMediaUrl != null) ? nodeMediaUrl.InnerText : "unknown media url";
                                    this.LogDebugInfo(string.Format("TwitPic media url: {0}", this.PostedMediaUrl));
                                }
                                else
                                {
                                    XmlNode nodeError = docResult.SelectSingleNode("//err");
                                    this.TwitPicErrorMsg = (nodeError != null) 
                                                            ? string.Format("Code:{0} Msg: {1}",
                                                                            nodeError.Attributes["code"].Value,
                                                                            nodeError.Attributes["msg"].Value)
                                                            : "unknown error message";
                                    EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME,
                                                        string.Format("TwitPic error: {0}", this.TwitPicErrorMsg), 
                                                        EventLogEntryType.Error, 
                                                        3);
                                }
                            }
                        }
                    }
                }
            }
    
            public string CreateRequestContents(string formDataBoundary)
            {
                string formDataHeader = string.Format("--{0}", formDataBoundary);
                string formDataFooter = string.Format("--{0}--", formDataBoundary);
    
                StringBuilder contents = new StringBuilder();
                contents.AppendLine(formDataHeader);
    
                // Add "media" field
                contents.AppendLine(String.Format("Content-Disposition: file; name="media"; filename="{0}"", this.FileName));
                contents.AppendLine("Content-Type: image/png");
                contents.AppendLine();
                contents.AppendLine(this.ImageFileContents);
    
                // Add username field
                contents.AppendLine(formDataHeader);
                contents.AppendLine("Content-Disposition: form-data; name="username"");
                contents.AppendLine();
                contents.AppendLine(this.PluginSettings.TwitPicID);
    
                // Add password field
                contents.AppendLine(formDataHeader);
                contents.AppendLine("Content-Disposition: form-data; name="password"");
                contents.AppendLine();
                contents.AppendLine(this.PluginSettings.TwitPicPassword);
    
                contents.AppendLine(formDataFooter);
    
                string stringContents = contents.ToString();
    
                this.LogDebugInfo("Formatted the TwitPic upload request",
                                    Encoding.GetEncoding("iso-8859-1").GetBytes(stringContents));
                return stringContents;
            }
      

Source Download

Until I can get the code added to the Cropper Plugins project on CodePlex you can download the full source on my blog. See the README.txt for instructions on how to set the code up.

Demo

Once the plugin code is built and deployed, you’re ready to start.

  1. when you start Cropper, you can right-click on the Options menu to:SendToTwitPic Configuration
  2. Then you switch Cropper’s output to the SendToTwitPic plugin: SendToTwitPic Selection
  3. Now you can capture an image. If the post is successful, a message box will confirm that and indicate the destination url:
    SendToTwitPic Posted Media
  4. So enjoy this tool!


    References and Resources

    Sites:
    1. Cropper
    2. Cropper Plugins
    3. Brian Scott’s blog post
    4. TwitPic API

    October 22nd, 2009 Posted by | C#, Tools | no comments