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.
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.
- 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.
- when you start Cropper, you can right-click on the Options menu to:
- Then you switch Cropper’s output to the SendToTwitPic plugin:
- Now you can capture an image. If the post is successful, a message box will confirm that and indicate the destination url:
So enjoy this tool!