📜 ⬆️ ⬇️

The experience of creating image loader

Foreword


Hello. I want to talk about creating an image uploader for my first web project. I will try to explain what solutions I have seen, and what pitfalls met on my way. Well, actually, how these stones can be circumvented. I hope my experience will help someone.

To begin, I will describe the main task: you need to create an image uploader (bmp, png, jpg), then save them on the server, as well as create copies of images of various sizes. It is also desirable to ensure that the design of the loader matches the style of the site, and a convenient user interface. And most importantly - the loader should be supported by browsers as much as possible.

Solution one. HTML4


Of course, this is the simplest and most obvious. Create a form, put the input-file there, and process the php-script on the server:

if(is_uploaded_file($_FILES["filename"]["tmp_name"])) { move_uploaded_file($_FILES["filename"]["tmp_name"], $_FILES["filename"]["name"]); } else { echo("  "); } 

Now you need to create copies of lower resolution. Here you can use the various image processing libraries. For example, Imagick. That's what I did at first. On the local host, everything worked fine. Then I chose an inexpensive hosting, put the project there. Began to test. And here came the main bummer. For images with good resolution (2500x1900), small copies were not created. Many probably guessed why. Having rummaged in logs, and also having put mental efforts, it dawned on me. When you start to process images, and work with the entire matrix, a lot of RAM is required. And my rather modest hosting tariff provided it not very much.
')
And this was already a problem. You can, of course, take a better rate. But fundamentally this situation will not change. With the additional load the same thing happens. The approach is wrong in essence. It is necessary to perform image manipulations not on the server, but on the client. And cross-browser html + javascript did not fit here.

Solution two. Flash


Of course, the solution is also not cross-browser. But 99% of flash users still stand, so it’s worth a try. Moreover, there are obvious advantages:
We will need a FileReferenceList in order to trigger a dialog and get a list of images from the local machine. Then we load each one and perform the necessary actions with it:

 var FileList:Array; var send_element; var load_element; var script_name = "../../ajax.php"; var type_filter:FileFilter = new FileFilter(" (*.jpg, *.jpeg, *.gif, *.png)","*.jpg;*.jpeg;*.gif;*.png"); var OpenFileDialog:FileReferenceList = new FileReferenceList(); OpenFileDialog.addEventListener(Event.SELECT, onSelectList); function onSelectList(e:Event){ Select_check(); } function Select_check(){ var element: FileReference = OpenFileDialog.fileList.shift(); load_element["original"] = element; FileList.push(load_element); element.addEventListener(Event.COMPLETE, onLocal_complete); element.load(); } function onLocal_complete(e:Event){ //     e.target.data load_element["original"].removeEventListener(Event.COMPLETE, onLocal_complete); if(OpenFileDialog.fileList.length > 0) Select_check(); } function open(){ OpenFileDialog.browse([type_filter]); } function save(){ var send_element = FileList.shift(); send_element["original"].addEventListener(ProgressEvent.PROGRESS, onPOST_progress); send_element["original"].addEventListener(Event.COMPLETE, onPOST_complete); send_element["original"].addEventListener(IOErrorEvent.IO_ERROR, onPOST_error); var cookie = ExternalInterface.call("function(){ var name = 'PHPSESSID'; var prefix = name + '='; var cookieStartIndex = document.cookie.indexOf(prefix); if (cookieStartIndex == -1) return null; var cookieEndIndex = document.cookie.indexOf(';', cookieStartIndex + prefix.length); if (cookieEndIndex == -1) cookieEndIndex = document.cookie.length; return unescape(document.cookie.substring(cookieStartIndex + prefix.length, cookieEndIndex));}"); var DataVartibles:URLVariables = new URLVariables(); DataVartibles.PHPSESSID = cookie; var FileRequest = new URLRequest(script_name); FileRequest.data = DataVartibles; FileRequest.method = URLRequestMethod.POST; send_element["original"].upload(FileRequest, php_file); } function onPOST_progress(e:ProgressEvent){ //  ,   e.bytesLoaded } function onPOST_error(e:IOErrorEvent){ //      if(FileList.length > 0) save(); } function onPOST_complete(e:Event){ if(FileList.length > 0) save(); } 

The browse method calls the file download dialog. There is one subtlety: the method will be executed only if the code is called in the event listener of the EVENT.CLICK of some element.

It is also worth looking at the description of the FileReference class. It is needed to upload a local image and transfer it to the server. The load method is needed for upload, upload for transfer to the server. There is another subtle point here: when uploading to the server using FileReference, cookies are only transferred from IE (thanks for the Demetros information). Therefore, if you want, for example, to work within one session, you will have to get cookies from the browser by calling the javascript function. Better to do it from the flash drive itself. ExternalInterface is suitable for this. Next, we write the cookies we need in URLVariables.

So, basic functionality is. But the question remains with the creation and sending of small copies of images. And here FileReference will not help us anymore, since it is able to send only downloaded local files.

I will not consider the issue of creating reduced copies; there are many libraries that can do this. However, to be sent to the server, this data must be represented as a ByteArray.
Now we will consider how we organize the sending of this data to the server. Using the regular variable URLVariables with the subsequent addition to the URLRequest will not do this. Therefore, you will have to create the request header yourself. URLRequest allows you to do this. As a result, we create a class that is engaged in sending data to the server:

 package { import flash.net.URLRequest; import flash.net.URLLoader; import flash.net.URLRequestMethod; import flash.net.URLLoaderDataFormat; import flash.utils.ByteArray; import flash.utils.Endian; import flash.net.URLRequestHeader; public class HTTPLoader extends URLLoader { var HTTPRequest; var BOUND:String = ""; var ENTER:String = "\r\n"; var ADDB:String = "--"; var index_file = 0; var PostData:ByteArray; public function HTTPLoader(script_name: String){ BOUND = getBoundary(); PostData = new ByteArray(); PostData.endian = Endian.BIG_ENDIAN; HTTPRequest = new URLRequest(script_name); HTTPRequest.requestHeaders.push(new URLRequestHeader('Content-type','multipart/form-data; boundary=' + BOUND)); HTTPRequest.method = URLRequestMethod.POST; } public function addVariable(param_name:String, param_value:String){ PostData.writeUTFBytes(ADDB + BOUND); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes('Content-Disposition: form-data; name="'+param_name+'"'); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes(param_value); PostData.writeUTFBytes(ENTER); } public function addFile(filename:String, filedata:ByteArray){ PostData.writeUTFBytes(ADDB + BOUND); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes('Content-Disposition: form-data; name="Filedata' + index_file + '"; filename="' + filename + '"'); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes('Content-Type: application/octet-stream'); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes(ENTER); PostData.writeBytes(filedata,0,filedata.length); PostData.writeUTFBytes(ENTER); PostData.writeUTFBytes(ENTER); index_file++; } public function send(){ PostData.writeUTFBytes(ADDB+BOUND+ADDB); HTTPRequest.data = PostData; this.load(HTTPRequest); } public function getBoundary():String { var _boundary:String = ""; for (var i:int = 0; i < 0x20; i++) { _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) ); } return _boundary; } } } 

I quote the class completely, since the formation of the correct HTTP request header took me quite a lot of time. I hope someone will help. Using the class is very simple. Here is an example:

 var POSTLoader:HTTPLoader = new HTTPLoader("../../ajax.php"); POSTLoader.addEventListener(Event.COMPLETE, POSTLoader_complete); POSTLoader.addVariable("AJAX_module_name", "pic_loader.php"); POSTLoader.addFile("pic_100", send_element["pic_100"]); POSTLoader.send(); 

Well, in the Event.COMPLETE listener, do what you need. The disadvantage of this method is that it is impossible to track the download process, so only two states are available - whether the file is loaded or not.
In php, you can easily walk through the $ _FILES array and pick up each file named “Filedata” + index, and then just save it.

 if(isset($_FILES["Filedata0"])){ for($i = 0; $i < $n; $i++){ $file_name = $_FILES["Filedata".$i]["name"]; move_uploaded_file($_FILES["Filedata".$i]['tmp_name'],$file_path.$file_name); } } 

Here is a solution. If desired, you can display preview images immediately, or transfer to javascript as base64. Well, this is someone like it.

Solution three. HTML5


I’ll say right away that I didn’t implement the loader using html5, but for relative completeness (I didn’t take java applets), this solution is worth mentioning. Most likely, in the future this will be the best way to solve this problem. However, old browsers are still running, and the new File API is not yet fully developed. Also worth mentioning is the immutable input-file. In modern browsers, you can call the click method programmatically and hide the input itself.

 <input id="im" type="file" style="position:absolute; top:-999px; visibility:hidden"/> <div id="button" style="background-color: blue; width: 100px; height:40px;"></div> 

 <script type='text/javascript'> var btn = document.querySelector("#button"); btn.onclick = function(){ var im = document.querySelector("#im"); im.click(); } </script> 

This solution works in almost all modern browsers. Except one. Guess what ...
Not guessed, Opera. In versions prior to 11.52 inclusive, it is impossible to do so. Therefore, the imposition of makeup on the input file still remains a problem.

Conclusion


Of course, in a real project it is better to use a combination of these methods so that the loader works for everyone. For example, if the html5 functions in the user's browser are not supported, then you can try using flash. If there is no flash, then we give a simple html solution. Unlikely, but it works.
I hope my article will help someone.

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


All Articles