📜 ⬆️ ⬇️

Image Loader. Closing topic

Foreword


Hello. I already wrote about creating an image uploader. First - the bootloader on flash , then - on html5 . By and large, these two options are enough. And if you are a fan of rationality, and the topic itself does not cause any particular interest, then you can stop reading.
Here is a working example of a bootloader on Silverlight 4: yes, here it is .

I will remind the task. It is necessary to implement batch loading of images, resize on the client, as well as easy file retrieval on the server (for example, in the $ _FILES array in php). Well, user-friendly interface, of course.
We now turn to the toolkit. In this case, we use Silverlight 4, which means we have the power of .Net, static typing (yes, I know, dynamic rules), a cool free editor (Microsoft Visual Web Developer 2010 Express).

Work with images


So, C #. I like the language (I even wrote a diploma on it), but after javascript, actionscript and php are not very familiar. However, it quickly passes.
To get the list of files, we need OpenFileDialog. Uploading files is also not a problem: it is easy to do via FileStream. We have the data in Silverlight, now we need to present it as images and then resize it.
Surprisingly, we can present the data in Bitmap (WriteableBitmap), but there are no built-in methods for resizing and even more so for coding back to png or jpeg. All this, of course, can be done manually. But this is a topic for a separate article, especially since similar questions have been chewed many times, including on Habré. Therefore, we take the library, which will give more opportunities. I used ImageTools. For my needs I wrote the class MyImage, which implements the functionality I need:

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); } } } 

')
So, we have images, there is a functionality for working with them, as a result there is an array of byte [] for sending to the server.

Cap


In general, interaction with the server on silverlight is a topic for a separate article (I’ll figure it out and write, most likely). Suffice it to say that simply passing variables to the server (without using a web service, soap) is not so easy. As a result, as well as for flash, you will have to form the request header yourself, thus emulating the form submission. The result was another reincarnation of my class to form a cap:
 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; } } } 


Asynchrony


So we went to the most interesting. Actually, the mechanisms for downloading data to the server and receiving a response. In our case, you will need HttpWebRequest. In general, the ideology of working with this class and with requests in silverlight is not so obvious, so it makes sense to paint a sequence of actions:

1) Creating an instance of HttpWebRequest, specifying the destination url, sending method (Post, Get), header (Content-type).

2) Next, call the BeginGetRequestStream method. This method has 2 parameters. The first is the delegate of the function that will be called. The second is the parameters passed to this function. Generally, why do we need this feature? It is needed in order to get access to the stream (Stream), which writes data to the server. The stream can be obtained using the EndGetRequestStream method.

It should be noted that the function is called asynchronously and in a separate stream (not to be confused with the Stream), that is, we do not have access to the user interface. This can be overcome in two ways: via a call to Dispatcher.BeginInvoke (function call in the user interface thread) or via SynchronizationContext, first save the synchronization context for the user interface thread, and then in the required place, call a function that will be asynchronously executed in this thread.

3) Next, simply write data to Write (here you can also track the percentage of download).

4) Next, call the BeginGetResponse method, which requests a response from the server. The parameters of the method are the same as for BeginGetRequestStream, and the function is also called asynchronously.

5) In this function, we get an instance of HttpWebResponse (using the EndGetResponse method of the HttpWebRequest object).

6) Next we get the stream (Stream) with the answer (by calling the GetResponseStream method of the HttpWebResponse object). And from the stream we already get the answer itself (via StreamReader.ReadToEnd).

I understand that the explanation is clumsy, but I failed in another way. I hope the sample code will be more visual (well, some of the comments in the code are also present):
  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(); //    } 

Such is the load. But on the server, nothing prevents us from using a banal and familiar script:
 foreach($_FILES as $key => $value){ $filename = substr_replace($key, '.', -4, 1); move_uploaded_file($value['tmp_name'], "upload/". urldecode($filename)); } echo 'complete'; 


Representation


The question remains, what should the boot interface look like (xaml or html). Here everyone decides how he likes. I made in the form of html. That is, the button for calling the file selection dialog, of course, on silverlight (due to security restrictions). But everything else on html (including the preview image, I displayed them in the form of base64). Wonderful classes of the Html Bridge helped me with this, allowing me to work with the DOM tree directly from silverlight.

Here is the bootloader demo:
imageimage

Beyond the brackets


I want to say thanks to the habrayuser Demetros for the excellent articles on the same topic, as well as habrayuser, who in the comments to my previous articles gave links to ready-made solutions. Those who are interested in this topic can find something for their tasks.

Congratulations to all the New Year and wish your work was a continuation of your hobbies!

Source: https://habr.com/ru/post/135481/


All Articles