画像ダウンローダー。 トピックを閉じる

まえがき



みなさんこんにちは。 画像ダウンローダーの作成についてはすでに書いています。 まず、 flashブートローダー 、次にhtml5ブートローダー 。 概して、これらの2つのオプションで十分です。 そして、あなたが合理性のファンであり、トピック自体が特に興味がない場合、あなたはもはや読むことができません。

ここで、Silverlight 4でのブートローダーの動作例を示します: はい、ここにあります。



タスクを思い出させてください。 画像のバッチアップロード、クライアントでのサイズ変更、およびサーバー上のファイルの便利な受信(たとえば、phpの$ _FILES配列)を実装する必要があります。 もちろん、便利なインターフェイスです。

それでは、ツールキットに移りましょう。 この場合、Silverlight 4を使用します。つまり、.Net、静的タイピング(はい、知っている、動的タクシー)、およびクールな無料エディター(Microsoft Visual Web Developer 2010 Express)のパワーを備えています。



画像を操作する



だからC#。 私はこの言語が好きです(卒業証書を書いたことさえあります)が、javascript、actionscript、phpの後にはあまり馴染みがありません。 しかし、これは急速に進んでいます。

ファイルのリストを取得するには、OpenFileDialogが必要です。 ファイルのアップロードも問題ではありません。FileStreamの使用は簡単です。 Silverlightにデータがあるので、今度は画像として表示してからサイズを変更する必要があります。

驚くべきことに、ビットマップ(WriteableBitmap)でデータを表示できますが、サイズ変更のための組み込みメソッドはなく、さらにpngまたはjpegにエンコードするための組み込みメソッドはありません。 もちろん、これはすべて手動で行うことができます。 しかし、これは別の記事のトピックです。Habréを含め、このような質問は何度もかみ砕かれました。 したがって、私たちはより多くの機会を与える図書館を利用します。 ImageToolsを使用しました。 必要に応じて、必要な機能を実装するMyImageクラスを作成しました。



using System; using System.Net; using System.Windows; using System.IO; using System.Windows.Media.Imaging; using ImageTools; using ImageTools.IO.Bmp; using ImageTools.IO.Jpeg; using ImageTools.IO.Png; using ImageTools.Helpers; using ImageTools.Filtering; using ImageTools.IO; namespace Uploader.Libs { public class MyImage { private ExtendedImage im; //    ImageTools public string name { get; set; } public string extension { get; set; } public FileInfo origin { get; set; } public int originSize { get { return origin != null ? Utils.ByteToKB((int)origin.Length) : 0; //Utils -     . } } public MyImage(FileInfo fileinfo) { name = fileinfo.Name; extension = fileinfo.Extension; origin = fileinfo; WriteableBitmap bmp = new WriteableBitmap(1, 1); bmp.SetSource(origin.OpenRead()); im = bmp.ToImage(); } public MyImage(ExtendedImage im, string name, string extension) { this.name = name; this.extension = extension; this.im = im; } public MyImage resize(int width, int height) { string prefix = width.ToString() + "_" + height.ToString() + "_"; ExtendedImage rImage = ExtendedImage.Resize(im, width, height, new ImageTools.Filtering.NearestNeighborResizer()); //     return new MyImage(rImage, prefix + name, extension); } //,   public MyImage scale(int value) { double width = im.PixelWidth; double height = im.PixelHeight; double max = width > height ? width : height; double sc = max > value ? value / max : 1; int nWidth = (int)Math.Round(sc * width); int nHeight = (int)Math.Round(sc * height); return resize(nWidth, nHeight); } //     public byte[] getOrigin() { byte[] buffer; if (origin != null) { FileStream fStream = origin.OpenRead(); buffer = new byte[fStream.Length]; fStream.Read(buffer, 0, buffer.Length); } else buffer = null; return buffer; } //    byte-,      public byte[] toByte(string extension = "") { MemoryStream mStream = new MemoryStream(); string ext = extension != String.Empty ? extension : this.extension; dynamic encoder; //    ,   ,   dynamic switch (ext) { case ".png": encoder = new PngEncoder(); break; default: encoder = new JpegEncoder(); break; } encoder.Quality = 100; //    encoder.Encode(im, mStream); return mStream.ToArray(); } //base64      html   public string toBase64(byte[] data = null) { byte[] iData = data != null ? data : toByte(); return "data:image/" + extension.Substring(1) + ";base64," + Convert.ToBase64String(iData); } } }
      
      







したがって、画像があり、それらを操作するための機能があります。最後に、サーバーに送信するためのバイト[]の配列があります。



帽子



一般に、silverlight上のサーバーとのやり取りは、別の記事のトピックです(詳しくはこの記事で説明しますが、ほとんどの場合)。 サーバーに変数を渡すだけ(Webサービス、SOAPを使用せずに)はそれほど単純ではないと言うだけで十分です。 その結果、フラッシュに関しては、リクエストヘッダーを自分で作成する必要があり、フォーム送信をエミュレートします。 結果は、見出しを形成するための私のクラスの別の生まれ変わりでした:

 using System; using System.Collections.Generic; using System.Text; using System.Windows.Browser; namespace Uploader.Libs { public class FormBuilder { private string BOUND; private string ENTER = "\r\n"; private string ADDB = "--"; UTF8Encoding encoding; private List<byte> Data; public string bound { get { return BOUND; } } public FormBuilder() { BOUND = getBoundary(); Data = new List<byte>(); encoding = new UTF8Encoding(); } public void addFile(string name, byte[] buffer) { string encode_name = HttpUtility.UrlEncode(name); StringBuilder header = new StringBuilder(); header.Append(ADDB + BOUND); header.Append(ENTER); header.Append("Content-Disposition: form-data; name='" + encode_name + "'; filename='" + encode_name + "'"); header.Append(ENTER); header.Append("Content-Type: application/octet-stream"); header.Append(ENTER); header.Append(ENTER); Data.AddRange(encoding.GetBytes(header.ToString())); Data.AddRange(buffer); Data.AddRange(encoding.GetBytes(ENTER)); } public void addParam(string name, string value) { StringBuilder header = new StringBuilder(); header.Append(ADDB + BOUND); header.Append(ENTER); header.Append("Content-Disposition: form-data; name='" + name + "'"); header.Append(ENTER); header.Append(ENTER); header.Append(value); header.Append(ENTER); Data.AddRange(encoding.GetBytes(header.ToString())); } public byte[] getForm() { StringBuilder header = new StringBuilder(); header.Append(ENTER); header.Append(ENTER); header.Append(ADDB + BOUND + ADDB); Data.AddRange(encoding.GetBytes(header.ToString())); byte[] formData = new byte[Data.Count]; Data.CopyTo(formData); return formData; } private string getBoundary() { string _boundary = ""; Random rnd = new Random(); for (int i = 0; i < 0x20; i++) { _boundary += (char)(97 + rnd.NextDouble() * 25); } return _boundary; } } }
      
      







非同期性



そこで、最も興味深いことに移りました。 実際には、サーバーにデータをダウンロードして応答を受信するメカニズムに。 この場合、HttpWebRequestが必要です。 一般に、このクラスとシルバーライトでのリクエストを扱うイデオロギーはそれほど明白ではないため、一連のアクションをペイントすることは理にかなっています。



1)HttpWebRequestのインスタンスを作成し、宛先URL、送信メソッド(Post、Get)、ヘッダー(Content-type)を指定します。



2)次に、BeginGetRequestStreamメソッドを呼び出します。 このメソッドには2つのパラメーターがあります。 1つ目は、呼び出される関数のデリゲートです。 2番目は、この関数に渡されるパラメーターです。 一般に、なぜこの機能が必要なのですか? サーバーにデータを書き込むストリーム(Stream)にアクセスするために必要です。 ストリームは、EndGetRequestStreamメソッドによって取得できます。



関数が非同期で別のストリームで呼び出されることに注意してください(Streamと混同しないでください)。つまり、ユーザーインターフェイスにアクセスできません。 これに対処するには、2つの方法があります:Dispatcher.BeginInvokeの呼び出し(ユーザーインターフェイススレッドで関数を呼び出す)またはSynchronizationContextを使用して、最初にユーザーインターフェイススレッドの同期コンテキストを保存し、次にこのスレッドで非同期に実行される必要な場所で関数を呼び出します。



3)次に、Writeを使用してデータを書き込むだけです(負荷の割合をすぐに追跡できます)。



4)次に、サーバーからの応答を要求するBeginGetResponseメソッドの呼び出し。 メソッドのパラメーターはBeginGetRequestStreamの場合と同じであり、関数は非同期的に呼び出されます。



5)この関数では、HttpWebResponseのインスタンスを取得します(HttpWebRequestオブジェクトのEndGetResponseメソッドを使用)。



6)次に、応答を含むストリームを取得します(HttpWebResponseオブジェクトのGetResponseStreamメソッドを呼び出します)。 そして、ストリームからすでに(StreamReader.ReadToEndを介して)回答を取得しています。



説明が不器用であることは理解していますが、別の方法で失敗しました。 サンプルコードがより明確になることを願っています(まあ、コードにはいくつかのコメントもあります)。

  SynchronizationContext sync; //      Dictionary<string, MyImage> images; //   HtmlView mainView; //       html string script = "../upload.php"; //php-,    private void upload() { if (images.Count > 0) { MyImage im = (MyImage)(images.First().Value); MyImage mini = im.scale(300); // FormBuilder builder = new FormBuilder(); //   builder.addFile(im.name, im.getOrigin()); builder.addFile(mini.name, mini.toByte()); byte[] formData = builder.getForm(); Uri uri = new Uri(script, UriKind.Relative); HttpWebRequest request = (HttpWebRequest)WebRequestCreator.BrowserHttp.Create(uri); //    Browser Http Stack  ,      request.Method = "POST"; request.ContentType = "multipart/form-data; boundary=" + builder.bound; request.ContentLength = formData.Length; List<object> uploadState = new List<object>(); // ,      uploadState.Add(request); uploadState.Add(formData); request.BeginGetRequestStream(new AsyncCallback(GetRequestStream), uploadState); } } private void GetRequestStream(IAsyncResult result) { List<object> state = (List<object>)result.AsyncState; HttpWebRequest request = (HttpWebRequest)state[0]; byte[] data = (byte[])state[1]; int k = 0; int h = data.Length / 100; int ost = data.Length % 100; int dLength = data.Length - ost; Stream writeStream = request.EndGetRequestStream(result); //  for (int i = 0; i < dLength; i += h) { writeStream.Write(data, i, h); k++; Dispatcher.BeginInvoke(() => { mainView.setPercent(k); //  }); } if (ost > 0) writeStream.Write(data, dLength, ost); writeStream.Close(); request.BeginGetResponse(new AsyncCallback(GetResponse), request); } private void GetResponse(IAsyncResult result) { HttpWebRequest request = (HttpWebRequest)result.AsyncState; HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); sync.Post(onComplete, response); //      } private void onComplete(object state) { HttpWebResponse response = (HttpWebResponse)state; if (response.StatusCode == HttpStatusCode.OK) { StreamReader reader = new StreamReader(response.GetResponseStream()); string responseText = reader.ReadToEnd(); reader.Close(); } images.Remove(images.First().Key); //     mainView.setRowComplete(); upload(); //    }
      
      





これがダウンロードです。 しかし、サーバー上では、ありふれた使い慣れたスクリプトの使用を妨げるものは何もありません。

 foreach($_FILES as $key => $value){ $filename = substr_replace($key, '.', -4, 1); move_uploaded_file($value['tmp_name'], "upload/". urldecode($filename)); } echo 'complete';
      
      







提出



ブートローダインターフェイス(xamlまたはhtml)はどのように見えるかという疑問が残ります。 ここで誰もが彼が一番好きな方法を決定します。 htmlとして作成しました。 つまり、ファイル選択ダイアログを開くためのボタンは、もちろん(セキュリティ制限のため)silverlight上にあります。 しかし、他のすべてはhtmlにあります(画像のプレビューを含め、base64形式で表示しました)。 素晴らしいHtml Bridgeクラスがこれを助けてくれたので、Silverlightから直接DOMツリーを操作できました。



ブートローダーのデモは次のとおりです。

画像画像

ブラケットの外側



同じトピックに関する優れた記事を提供してくれたDemetros habrayuzerと、以前の記事へのコメントで既製のソリューションへのリンクを提供してくれたhabrayuzerに感謝します。 このトピックに興味がある人は、自分のタスクに合ったものを見つけることができます。



明けましておめでとうございます。あなたの仕事があなたの趣味の続きであることを願っています!



All Articles