{"id":184,"date":"2009-10-22T21:35:57","date_gmt":"2009-10-23T04:35:57","guid":{"rendered":"http:\/\/blog.monnet-usa.com\/?p=184"},"modified":"2009-10-22T21:35:57","modified_gmt":"2009-10-23T04:35:57","slug":"sending-cropper-captures-to-twitpic","status":"publish","type":"post","link":"https:\/\/blog.monnet-usa.com\/?p=184","title":{"rendered":"Sending Cropper Captures to TwitPic"},"content":{"rendered":"<h3>Capture Screenshots<\/h3>\n<p>For a while my tool of choice for creating screen captures has been <a href='http:\/\/www.codeplex.com\/cropper'>Cropper<\/a>, a utility created by <a href='http:\/\/blogs.geekdojo.net\/brian\/articles\/Cropper.aspx'>Brian Scott<\/a>, and hosted on <a href='http:\/\/www.codeplex.com\/cropper'>CodePlex<\/a><br \/>\nThere is also a series of <a href='http:\/\/cropperplugins.codeplex.com\/'>plugins<\/a> that allow you to for example post captures to Flickr.<br \/>\nOne thing I have always wanted was to send image captures to my TwitPic account so I can easily link them to my tweets.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPic.png\" alt=\"SendToTwitPic\" title=\"SendToTwitPic\" width=\"253\" height=\"113\" class=\"alignnone size-full wp-image-196\" \/><\/p>\n<h3>Building A Cropper Plugin For TwitPic<\/h3>\n<p>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.<\/p>\n<h3>Approach<\/h3>\n<p>A Cropper plugin consists of the following parts:<\/p>\n<ul>\n<li>A settings class containing properties that will hold the plugin configuration. For example:\n<pre class=\"brush: csharp\">\r\n   public class TwitPicSettings\r\n    {\r\n       private string _twitPicID = \"\";\r\n       private string _twitPicPassword = \"\";\r\n \r\n       public string TwitPicID\r\n       {\r\n            get { return _twitPicID; }\r\n            set { _twitPicID = value; }\r\n       }\r\n\r\n       \/\/ etc.\r\n    }\r\n<\/pre>\n<\/li>\n<li>An Option dialog class allowing users to edit the plugin configuration.<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicOptions.Png\" alt=\"SendToTwitPic Options Dialog\" title=\"SendToTwitPic Options Dialog\" width=\"315\" height=\"144\" class=\"alignnone size-full wp-image-188\" srcset=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicOptions.Png 315w, https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicOptions-300x137.Png 300w\" sizes=\"auto, (max-width: 315px) 100vw, 315px\" \/><\/li>\n<li>The plugin itself<\/li>\n<li><\/li>\n<\/ul>\n<p>The plugin needs to implement 2 interfaces:<\/p>\n<ul>\n<li>IConfigurablePlugin &#8211; this is how Cropper configures and retrieves our custom settings<\/li>\n<li>IPersistableImageFormat &#8211; this is how Cropper hands over the captured image so we can act on it.<\/li>\n<\/ul>\n<p>Let&#8217;s see what the code looks like. Note: I removed all comments for brevity.<\/p>\n<h4>IConfigurablePlugin  Implementation<\/h4>\n<p>This part is straightforward since it is exposing the settings and allows loading and saving of the settings to or from the dialog.<\/p>\n<pre class=\"brush: csharp\">\r\n        public BaseConfigurationForm ConfigurationForm\r\n        {\r\n            get\r\n            {\r\n                if (_configurationForm == null)\r\n                {\r\n                    _configurationForm = new Options();\r\n                    _configurationForm.OptionsSaved += Handle_configurationFormOptionsSaved;\r\n                    _configurationForm.TwitPicID       = PluginSettings.TwitPicID;\r\n                    _configurationForm.TwitPicPassword = PluginSettings.TwitPicPassword;\r\n                    _configurationForm.ShowDebugInfo   = PluginSettings.ShowDebugInfo;\r\n                }\r\n                return _configurationForm;\r\n            }\r\n        }\r\n\r\n        public bool HostInOptions\r\n        {\r\n            get { return hostInOptions; }\r\n        }\r\n\r\n        public object Settings\r\n        {\r\n            get { return PluginSettings; }\r\n            set { PluginSettings = value as TwitPicSettings; }\r\n        }\r\n\r\n        public TwitPicSettings PluginSettings\r\n        {\r\n            get\r\n            {\r\n                if (_settings == null)\r\n                    _settings = new TwitPicSettings();\r\n                return _settings;\r\n            }\r\n            set { _settings = value; }\r\n        }\r\n\r\n        private void Handle_configurationFormOptionsSaved(object sender, EventArgs e)\r\n        {\r\n            PluginSettings.TwitPicID       = _configurationForm.TwitPicID;\r\n            PluginSettings.TwitPicPassword = _configurationForm.TwitPicPassword;\r\n            PluginSettings.ShowDebugInfo   = _configurationForm.ShowDebugInfo;\r\n        }\r\n<\/pre>\n<h4>IPersistableImageFormat Implementation<\/h4>\n<p>There are 2 types of responsibilities<\/p>\n<ul>\n<li>Providing user interface elements (description, menu, etc) to the main Cropper UI:\n<pre class=\"brush: csharp\">\r\n        public string Description\r\n        {\r\n            get { return \"Send screenshot to your TwitPic account.\"; }\r\n        }\r\n\r\n        public string Extension\r\n        {\r\n            get { return \"Png\"; }\r\n        }\r\n\r\n        public override string ToString()\r\n        {\r\n            return \"Send to your TwitPic account\";\r\n        }\r\n        public MenuItem Menu\r\n        {\r\n            get\r\n            {\r\n                MenuItem item = new MenuItem();\r\n                item.RadioCheck = true;\r\n                item.Text = \"Send to TwitPic\";\r\n                item.Click += new EventHandler(this.MenuItemClick);\r\n                return item;\r\n            }\r\n        }\r\n\r\n        private void MenuItemClick(object sender, EventArgs e)\r\n        {\r\n            ImageFormatEventArgs args = new ImageFormatEventArgs();\r\n            args.ClickedMenuItem = (MenuItem)sender;\r\n            args.ImageOutputFormat = this;\r\n            this.OnImageFormatClick(sender, args);\r\n        }\r\n  <\/pre>\n<\/li>\n<li>Implementing callbacks to handle the image capture process:\n<pre class=\"brush: csharp\">\r\n        public IPersistableImageFormat Format\r\n        {\r\n            get { return this; }\r\n        }\r\n\r\n        public void Connect(IPersistableOutput persistableOutput)\r\n        {\r\n            if (persistableOutput == null)\r\n            {\r\n                throw new ArgumentNullException(\"persistableOutput\");\r\n            }\r\n            _output = persistableOutput;\r\n            _output.ImageCaptured  += new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);\r\n            _output.ImageCapturing += new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);\r\n        }\r\n\r\n        public void Disconnect()\r\n        {\r\n            _output.ImageCaptured -= new ImageCapturedEventHandler(this.persistableOutput_ImageCaptured);\r\n            _output.ImageCapturing -= new ImageCapturingEventHandler(this.persistableOutput_ImageCapturing);\r\n        }\r\n\r\n        public event ImageFormatClickEventHandler ImageFormatClick;\r\n\r\n        private void OnImageFormatClick(object sender, ImageFormatEventArgs e)\r\n        {\r\n            if (ImageFormatClick != null)\r\n            {\r\n                ImageFormatClick(sender, e);\r\n            }\r\n        }\r\n\r\n        void persistableOutput_ImageCapturing(object sender, ImageCapturingEventArgs e)\r\n        {\r\n            this.LogDebugInfo(\"Capturing an image via Cropper-TwitPic\");\r\n        }\r\n\r\n        void persistableOutput_ImageCaptured(object sender, ImageCapturedEventArgs e)\r\n        {\r\n          \/\/ ... \r\n        }\r\n  <\/pre>\n<\/li>\n<\/ul>\n<p>The persistableOutput_ImageCaptured method is where all the action will take place such as:<\/p>\n<ul>\n<li>Writing the image to a memory stream and encoding it:\n<pre class=\"brush: csharp\">\r\n            try\r\n            {\r\n                ImagePairNames imgNames = e.ImageNames;\r\n                this.FileName = e.ImageNames.FullSize;\r\n                this.LogDebugInfo(string.Format(\"Converting the captured image to .png - file name: {0}\", this.FileName));\r\n\r\n                \/\/ Convert the captured image as .png and string encode it so we can upload it\r\n                MemoryStream imageStream = new MemoryStream();\r\n                e.FullSizeImage.Save(imageStream, System.Drawing.Imaging.ImageFormat.Png);\r\n                imageStream.Position = 0;\r\n                byte[] data = imageStream.ToArray();\r\n                this.ImageFileContents = Encoding.GetEncoding(\"iso-8859-1\").GetString(data);\r\n\r\n                this.PostToTwitPic();\r\n                MessageBox.Show(string.Format(\"Your image is available on TwitPic atn{0}\",this.PostedMediaUrl),\r\n                                \"Cropper.SendToTwitPic\");\r\n            }\r\n            catch (Exception ex)\r\n            {\r\n              \/\/ ...\r\n            }\r\n <\/pre>\n<\/li>\n<li>Creating a web request so we can post the image to TwitPic using its <a href='http:\/\/twitpic.com\/api.do#upload'>upload<\/a> API &#8211; see more details on <a href='http:\/\/twitpic.com\/api.do'>TwiPic<\/a>\n<pre class=\"brush: csharp\">\r\n        public void PostToTwitPic()\r\n        {\r\n            \/\/ Reset TwitPic output\r\n            this.PostedMediaUrl = string.Empty;\r\n            this.TwitPicErrorMsg = string.Empty;\r\n\r\n            \/\/ Create the TwitPic request\r\n            string formDataBoundary = DateTime.Now.Ticks.ToString(\"x\");\r\n            string formDataEncoding = \"iso-8859-1\";\r\n\r\n            this.TwitPicRequest = this.CreateRequestContents(formDataBoundary);\r\n            byte[] bytes = Encoding.GetEncoding(formDataEncoding).GetBytes(this.TwitPicRequest);\r\n            \r\n            \/\/ Initialize the request for TwitPic\r\n            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.TwitPicPostingUrl);\r\n            request.Method = \"POST\";\r\n            request.CookieContainer = null;\r\n            request.Proxy.Credentials = CredentialCache.DefaultCredentials;\r\n            request.PreAuthenticate = true;\r\n            request.AllowWriteStreamBuffering = true;\r\n            request.ContentType = string.Format(\"multipart\/form-data; boundary={0}\", formDataBoundary);\r\n            request.ContentLength = bytes.Length;\r\n\r\n            \/\/ Send to TwitPic\r\n            using (Stream requestStream = request.GetRequestStream())\r\n            {\r\n                \/\/ Post the data\r\n                this.LogDebugInfo(string.Format(\"Uploading the captured image to: {0}\", this.TwitPicPostingUrl));\r\n                requestStream.Write(bytes, 0, bytes.Length);\r\n\r\n                \/\/ Get the response\r\n                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())\r\n                {\r\n                    this.LogDebugInfo(string.Format(\"Getting the upload status from TwitPic\"));\r\n\r\n                    using (StreamReader reader = new StreamReader(response.GetResponseStream()))\r\n                    {\r\n                        string result = reader.ReadToEnd();\r\n                        XmlDocument docResult = new XmlDocument();\r\n                        docResult.LoadXml(result);\r\n                        XmlNode nodeResult = docResult.SelectSingleNode(\"\/rsp\");\r\n                        if (nodeResult == null)\r\n                            EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME, \r\n                                                \"Did not receive a valid status response from TwitPic\", \r\n                                                EventLogEntryType.Error, \r\n                                                2);\r\n                        else\r\n                        {\r\n                            string resultCode = nodeResult.Attributes[\"stat\"].Value;\r\n                            if (\"ok\" == resultCode)\r\n                            {\r\n                                XmlNode nodeMediaUrl = docResult.SelectSingleNode(\"\/\/mediaurl\");\r\n                                this.PostedMediaUrl = (nodeMediaUrl != null) ? nodeMediaUrl.InnerText : \"unknown media url\";\r\n                                this.LogDebugInfo(string.Format(\"TwitPic media url: {0}\", this.PostedMediaUrl));\r\n                            }\r\n                            else\r\n                            {\r\n                                XmlNode nodeError = docResult.SelectSingleNode(\"\/\/err\");\r\n                                this.TwitPicErrorMsg = (nodeError != null) \r\n                                                        ? string.Format(\"Code:{0} Msg: {1}\",\r\n                                                                        nodeError.Attributes[\"code\"].Value,\r\n                                                                        nodeError.Attributes[\"msg\"].Value)\r\n                                                        : \"unknown error message\";\r\n                                EventLog.WriteEntry(CROPPER_TWITPIC_EVENT_SOURCE_NAME,\r\n                                                    string.Format(\"TwitPic error: {0}\", this.TwitPicErrorMsg), \r\n                                                    EventLogEntryType.Error, \r\n                                                    3);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        public string CreateRequestContents(string formDataBoundary)\r\n        {\r\n            string formDataHeader = string.Format(\"--{0}\", formDataBoundary);\r\n            string formDataFooter = string.Format(\"--{0}--\", formDataBoundary);\r\n\r\n            StringBuilder contents = new StringBuilder();\r\n            contents.AppendLine(formDataHeader);\r\n\r\n            \/\/ Add \"media\" field\r\n            contents.AppendLine(String.Format(\"Content-Disposition: file; name=\"media\"; filename=\"{0}\"\", this.FileName));\r\n            contents.AppendLine(\"Content-Type: image\/png\");\r\n            contents.AppendLine();\r\n            contents.AppendLine(this.ImageFileContents);\r\n\r\n            \/\/ Add username field\r\n            contents.AppendLine(formDataHeader);\r\n            contents.AppendLine(\"Content-Disposition: form-data; name=\"username\"\");\r\n            contents.AppendLine();\r\n            contents.AppendLine(this.PluginSettings.TwitPicID);\r\n\r\n            \/\/ Add password field\r\n            contents.AppendLine(formDataHeader);\r\n            contents.AppendLine(\"Content-Disposition: form-data; name=\"password\"\");\r\n            contents.AppendLine();\r\n            contents.AppendLine(this.PluginSettings.TwitPicPassword);\r\n\r\n            contents.AppendLine(formDataFooter);\r\n\r\n            string stringContents = contents.ToString();\r\n\r\n            this.LogDebugInfo(\"Formatted the TwitPic upload request\",\r\n                                Encoding.GetEncoding(\"iso-8859-1\").GetBytes(stringContents));\r\n            return stringContents;\r\n        }\r\n  <\/pre>\n<\/li>\n<\/ul>\n<h3>Source Download<\/h3>\n<p>Until I can get the code added to the Cropper Plugins project on CodePlex you can <a href=\"\/wp-content\/uploads\/2009\/10\/Cropper.SendToTwitPic.Plugin.zip\">download the full source on my blog<\/a>. See the README.txt for instructions on how to set the code up.<\/p>\n<h3>Demo<\/h3>\n<p>Once the plugin code is built and deployed, you&#8217;re ready to start.<\/p>\n<ol>\n<li>when you start Cropper, you can right-click on the Options menu to:<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicConfiguration.Png\" alt=\"SendToTwitPic Configuration\" title=\"SendToTwitPic Configuration\" width=\"367\" height=\"201\" class=\"alignnone size-full wp-image-192\" srcset=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicConfiguration.Png 367w, https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicConfiguration-300x164.Png 300w\" sizes=\"auto, (max-width: 367px) 100vw, 367px\" \/><\/li>\n<li>Then you switch Cropper&#8217;s output to the SendToTwitPic plugin: <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicSelection.png\" alt=\"SendToTwitPic Selection\" title=\"SendToTwitPic Selection\" width=\"279\" height=\"249\" class=\"alignnone size-full wp-image-193\" \/><\/li>\n<li>Now you can capture an image. If the post is successful, a message box will confirm that and indicate the destination url:<br \/>\n<img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicPostedMedia.Png\" alt=\"SendToTwitPic Posted Media\" title=\"SendToTwitPic Posted Media\" width=\"419\" height=\"258\" class=\"alignnone size-full wp-image-194\" srcset=\"https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicPostedMedia.Png 419w, https:\/\/blog.monnet-usa.com\/wp-content\/uploads\/2009\/10\/SendToTwitPicPostedMedia-300x184.Png 300w\" sizes=\"auto, (max-width: 419px) 100vw, 419px\" \/>\n<\/li>\n<p>So enjoy this tool!<\/p>\n<hr\/>\n<h3>References and Resources<\/h3>\n<h5>Sites:<\/h5>\n<ol>\n<li><a href=\"http:\/\/www.codeplex.com\/cropper\">Cropper<\/a><\/li>\n<li><a href=\"http:\/\/cropperplugins.codeplex.com\/\">Cropper Plugins<\/a><\/li>\n<li><a href=\"http:\/\/blogs.geekdojo.net\/brian\/articles\/Cropper.aspx\">Brian Scott&#8217;s blog post<\/a><\/li>\n<li><a href=\"http:\/\/twitpic.com\/api.do\">TwitPic API<\/a><\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17,4],"tags":[57],"class_list":["post-184","post","type-post","status-publish","format-standard","hentry","category-c","category-tools","tag-c"],"_links":{"self":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/184","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=184"}],"version-history":[{"count":11,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/184\/revisions"}],"predecessor-version":[{"id":200,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=\/wp\/v2\/posts\/184\/revisions\/200"}],"wp:attachment":[{"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=184"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=184"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.monnet-usa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=184"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}