Absrtact: Before doing a small game platform project, there is a "User Center" module, which involves the Avatar upload function. In the mobile image upload, the transfer is a local picture of the mobile phone, and local pictures are generally relatively large, take the current smartphone, usually shoot a lot of pictures are two or three trillion, if directly such upload, the picture is too big, if the user is mobile traffic, the picture upload is obviously not a good way. So before uploading compression processing is necessary, after looking for a lot of information on the Internet, tried a lot of methods, encountered a lot of pits, such as Android can successfully compress upload pictures, on iOS but can't upload, toss a long time to find the hole in iOS. This has been in practice proved feasible, a few megabytes of images can be compressed to our backend requirements of 200k or less! Such a feasible method must show you "PS: are some patchwork others way out of, Hee ~".
At present, the HTML5 of the new API are on the mobile side of the WebKit has been better implemented. According to view Caniuse, the FileReader, Blob, Formdata objects used in this demo have been implemented in most mobile device browsers (safari6.0+, Android 3.0+), so the image is compressed directly in the front has become a lot of mobile image upload necessary features.
Compress the images on the mobile side and upload the three H5 APIs that are used primarily for filereader, canvas, and Formdata. Logic is not difficult. The whole process is:
(1) When the user uploads a picture using input file, the user uploads the image data (base64 format) with FileReader
(2) Pass the image data to the IMG object, then draw the IMG onto the canvas, then call Canvas.todataurl to compress the picture.
(3) Obtain the compressed base64 format picture data, turn into binary plug into the formdata, and then submit formdata through XMLHttpRequest.
In such three steps, the image compression and upload are completed.
It seems very simple, in fact, there are some pits. The next step is to analyze it directly in code:
"One" Get picture data
First get the image data, that is, listen to the Change event of input file, then get to the uploaded file object files, the class array of files to a group, and then a foreach traversal.
Then determine the file type, if not the picture is not processed. If the picture is to instantiate a filereader, read the uploaded file data in base64 format, determine the length of the data, if the picture is greater than 200KB call compress method to compress, otherwise call upload method to upload.
Filechooser.onchange =function() { if(! This. files.length)return; varFiles = Array.prototype.slice.call ( This. files); if(Files.length > 9) {alert ("Up to 9 images can be uploaded at the same time"); return; } Files.foreach (function(file, i) {if(!/\/(?: jpeg|png|gif)/i.test (File.type))return; varReader =NewFileReader (); varLi = document.createelement ("li"); Li.innerhtml= ' <div class= ' progress ' ><span></span></div> '; $(". Img-list"). Append ($ (LI)); Reader.onload=function() { varresult = This. Result; varIMG =NewImage (); IMG.SRC=result; //If the image size is less than 200kb, upload it directly if(Result.length <=maxsize) {$ (LI). css ("Background-image", "url (" + result + ")"); IMG=NULL; Upload (result, File.type, $ (LI)); return; } //compress the picture after it is loaded, then upload if(Img.complete) {callback (); } Else{img.onload=callback; } functioncallback () {vardata =Compress (IMG); $ (LI). css ("Background-image", "url (" + Data + ")"); Upload (data, File.type, $ (LI)); IMG=NULL; } }; Reader.readasdataurl (file); })};
"2" Compressed picture
After getting the picture data, we can do the compress compress the image method. And compressing the picture is not directly drawing the picture to the canvas and then calling the Todataurl on the line.
In iOS, canvas draws a picture with two restrictions:
The first is the size of the picture, if the size of the picture is more than 2 million pixels, the picture can not be drawn to the canvas, the call DrawImage will not error, but you use Todataurl to obtain the image data when the image is empty data.
In addition, the size of the canvas is limited, if the size of the canvas is larger than about 5 million pixels (that is, the width and height product), not only the picture can not be drawn, and nothing else is not drawn.
The first limitation should be handled by tiles. Tile painting, that is, the picture is divided into multiple pieces to the canvas, my code is to split the image into a 1 million-pixel piece of the size, and then drawn to the canvas.
In response to the second limitation, my approach is to properly compress the width of the image, and in my code, for the sake of insurance, the upper limit is 4 million pixels, and if the image is larger than 4 million pixels, it is compressed to less than 4 million pixels. The 4 million-megapixel picture should be enough, and the width and height are 2000x2000.
This solves both limitations on iOS.
In addition to the limitations described above, there are two pits, one is the canvas of the todataurl is only compressed JPG, when the user uploads the image is a PNG, you need to turn to JPG, that is, unified with Canvas.todataurl (' Image/jpeg ', 0.1) , the type is uniformly set to JPEG, and the compression ratio is controlled by itself.
The other is that if a PNG-to-JPG is drawn to the canvas and the canvas has a transparent area, the transparent area turns black when it is turned into a JPG, because the canvas's transparency, like Somer, considers Rgba (0,0,0,0), so turning into JPG becomes rgba ( 0,0,0,1), that is, the transparent background will become black. The solution is to paint a layer of white background on the canvas.
functionCompress (img) {varInitsize =img.src.length; varwidth =Img.width; varHeight =Img.height; //if the picture is larger than 4 million pixels, calculate the compression ratio and press the size below 4 million varratio; if((ratio = width * height/4000000) > 1) {ratio=math.sqrt (ratio); Width/= ratio; Height/= ratio; }Else{ratio= 1; } canvas.width=width; Canvas.height=height; //Spread the backgroundCtx.fillstyle = "#fff"; Ctx.fillrect (0, 0, Canvas.width, canvas.height); //If the picture pixel is greater than 1 million, use tiles to draw varcount; if((count = width * height/1000000) > 1) {Count= ~ ~ (math.sqrt (count) + 1);//calculate how many tiles to divide into //Calculate the width and height of each tile varNW = ~ ~ (Width/count); varNH = ~ ~ (Height/count); Tcanvas.width=NW; Tcanvas.height=NH; for(vari = 0; I < count; i++) { for(varj = 0; J < Count; J + +) {tctx.drawimage (img, i* NW * ratio, J * nh * ratio, NW * ratio, NH * ratio, 0, 0, NW, NH); Ctx.drawimage (Tcanvas, I* NW, J *NH, NW, NH); } } } Else{ctx.drawimage (img,0, 0, width, height); } //for minimum compression varNdata = Canvas.todataurl (' Image/jpeg ', 0.1); Console.log (' Before compression: ' +initsize); Console.log (' After compression: ' +ndata.length); Console.log (' Compression ratio: ' + ~ ~ (Initsize-ndata.length/initsize) + "%"); Tcanvas.width= Tcanvas.height = Canvas.width = Canvas.height = 0; returnndata;}
"Three" image upload
After the picture is compressed, it can be plugged into the formdata to upload, first convert the Base64 data into a string, then instantiate a arraybuffer, and then pass the string in a 8-bit integer format into the Arraybuffer, Then through the Blobbuilder or Blob object, the 8-bit integer arraybuffer is turned into a binary object blob, then the Blob object append to Formdata, and then through Ajax to the backend.
XmlHttpRequest2 can not only send big data, but also out of the API, such as to get the progress of sending, my code is also a simple implementation.
//image upload, convert base64 image into binary object, plug in formdata uploadfunctionUpload (basestr, type, $li) {varText = Window.atob (Basestr.split (",") [1]); varBuffer =NewArrayBuffer (text.length); varUbuffer =Newuint8array (buffer); varpecent = 0, Loop=NULL; for(vari = 0; i < text.length; i++) {Ubuffer[i]=text.charcodeat (i); } varBuilder = window. Webkitblobbuilder | |window. Mozblobbuilder; varblob; if(Builder) {varBuilder =NewBuilder (); Builder.append (buffer); Blob=Builder.getblob (type); } Else{blob=Newwindow. Blob ([buffer], {type:type}); } varXHR =NewXMLHttpRequest (); varFormdata =NewFormData (); Formdata.append (' ImageFile ', BLOB); Xhr.open (' Post ', '/cupload '); Xhr.onreadystatechange=function() { if(Xhr.readystate = = 4 && xhr.status = 200) {Console.log (' Upload succeeded: ' +xhr.responsetext); Clearinterval (loop); //upload Complete When you receive the message$li. Find (". Progress span")). Animate ({' Width ': ' 100% '}, Pecent< 95? 200:0,function() { $( This). HTML ("Upload succeeded"); }); $(". Pic-list"). Append (' <a href= ' + xhr.responsetext + ' "> ' + xhr.responsetext + ' </a> ') } }; //Data delivery progress, top 50% show the progressXhr.upload.addEventListener (' Progress ',function(e) {if(Loop)return; Pecent= ~ ~ (e.loaded/e.total)/2; $li. Find (". Progress span"). CSS (' width ', pecent + "%")); if(Pecent = = 50) {mockprogress (); } }, false); //data after 50% with simulation progress functionmockprogress () {if(Loop)return; Loop= SetInterval (function() {pecent++; $li. Find (". Progress span"). CSS (' width ', pecent + "%")); if(Pecent = = 99) {clearinterval (loop); } }, 100)} xhr.send (Formdata);}
At this point, the entire upload of the front-end picture compression is completed, because it is used formdata submission, so the background data is the same as the normal form form submission data processing.
Mobile front-end picture compression upload