Mobile front-end-picture compression upload Practice

Source: Internet
Author: User
Tags foreach format array base64 object end string window


Previous colleagues have talked to me about the mobile end with canvas compression pictures and then upload the function, recently had a bit of free time, so I practiced a bit. Demo effect links are posted at the bottom of the article.



On the mobile side of the image upload, the user is a mobile phone local pictures, and local pictures are generally relatively large, take iphone6, usually take a lot of pictures are one or two m, if the direct upload, the picture is too large, if the user is using mobile traffic, the picture upload is obviously not a good way.



At present, all kinds of new APIs of HTML5 are implemented on the webkit of mobile end. According to the 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 on the front end, has become a lot of mobile-side image upload the necessary features.



Compress the pictures at the mobile end and upload the APIs using the three H5 of FileReader, Canvas and Formdata. Logic is not difficult. The whole process is:



(1) When users upload images using the input file, use FileReader to read the image data uploaded by the user (base64 format)



(2) The image data into the IMG object, and then the IMG drawn to the canvas, and then call Canvas.todataurl to compress the picture



(3) Obtain to compress the Base64 format picture data, turn into binary to plug into formdata, and then submit formdata through XMLHttpRequest.



So three steps to complete the image compression and upload.



It seems quite simple, but there are still some pits. The next step is to analyze the code directly:



  "One" Get picture data



First get the picture data, that is, listen to the Change event of input file, then get to the uploaded file object files, the files of the class array into a group, and then a foreach traversal.



The file type is then judged and not processed if it is not a picture. If the picture is to instantiate a filereader, read the uploaded file data in the Base64 format, judge the data length, if the picture is larger than 200KB, call the Compress method to compress, otherwise call the upload method to upload.


Filechooser.onchange = function () {
if (!this.files.length) return;
 
var files = 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 (!/\/: jpegpnggif)/i.test (File.type)) return;
 
var reader = new FileReader ();
 
var li = document.createelement ("Li");
li.innerhtml = "<div class=" Progress "><span></span></div>";
$ (". Img-list"). Append ($ (LI));
 
Reader.onload = function () {
var result = This.result;
var img = new Image ();
IMG.SRC = result;
 
If the picture size is less than 200kb, upload directly
if (result.length <= maxsize) {
$ (LI). css ("background-image", "url (" + result +) ");
img = NULL;
Upload (result, File.type, $ (LI));
 
Return
}
 
After the picture is loaded, compress it, and then upload it.
if (img.complete) {
Callback ();
} else {
Img.onload = callback;
}
 
function callback () {
var data = Compress (IMG);
 
$ (LI). css ("background-image", "url" ("+ Data +"));
 
Upload (data, File.type, $ (LI));
 
img = NULL;
}
 
};
 
Reader.readasdataurl (file);
}) };


"2" Compressed picture



After the above finished image , you can do compress compression image method. and compressed picture is not directly to draw the picture to canvas and then call the Todataurl on the line.



In iOS, there are two limitations to drawing pictures in canvas:



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 to DrawImage will not be an error, but you use Todataurl to get the picture data is the empty picture data.



In addition, the size of the canvas is limited, if the size of the canvas is greater than about 5 million pixels (that is, the width of the product), not only the picture can not be drawn out, other things are not drawn out.



The first way to deal with the limitation is that the tiles are drawn. Tile drawing, that is, the image is divided into several pieces of canvas, I code the practice is to divide the picture into 1 million pixel size, and then draw to the canvas.



To deal with the second limitation, my approach is to compress the width of the picture properly, and for insurance purposes in my code, the upper limit is 4 million pixels, and if the picture is larger than 4 million pixels, compress to less than 4 million pixels. The 4 million pixel picture should be enough, calculate the width tall all have 2000x2000.



This solves both of the limitations on iOS.



In addition to the above restrictions, there are two pits, one is canvas todataurl can only compress jpg, when the user uploaded the picture is PNG, you need to turn into a 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 if PNG jpg is drawn to the canvas, when the canvas is in a transparent area, the transparent area becomes black when it is turned into a JPG, because canvas's transparency is like Somer that Rgba (0,0,0,0), So turn into a JPG becomes rgba (0,0,0,1), that is, the transparent background will become black. The solution is to draw a layer of white background on the canvas.


Function Compress (img) {        var initsize = img.src.length;    &
nbsp;    var width = img.width;

        var height = img.height;        //If the picture is greater than 4 million pixels, calculate the compression ratio and press the size below 4 million       
  VAR ratio;         if ((ratio = width * height/4000000) >1) {     
       ratio = math.sqrt (ratio);
            width/= ratio;
            height/= ratio;        }else {          
  ratio = 1;
       }         canvas.width = width;         canVas.height = height;
        floor background         Ctx.fillstyle = "#fff";

        ctx.fillrect (0, 0, canvas.width, canvas.height);        //If picture pixel is greater than 1 million use tile to draw         var
Count         if ((count = width * height/1000000) > 1) {             count = ~ ~ (math.sqrt (count) +1); Calculate how many tiles to divide//            calculate the width and height of each tile    
         var NW = ~ ~ (width/count);

            var NH = ~ ~ (height/count);
            tcanvas.width = NW;             tcanvas.height = NH;             for (var i = 0; i < count; i++) {  &nbsp ;             for (var j = 0; J < Count, J + +) { &nbs p;                 

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);        } &NBSP;&NBSP;&NBSP;&NBSP;&NBsp;  //Minimum compression         var ndata = Canvas.todataurl ("Image/jpeg", 0.1);
        Console.log ("before compression:" + initsize);
        Console.log ("after compression:" + ndata.length);

        Console.log ("Compression rate:" + ~ ~ (initsize-ndata.length)/initsize) + "%");

        tcanvas.width = Tcanvas.height = Canvas.width = Canvas.height = 0;
        return ndata;    }


  "Three" picture upload



Finished picture compression, you can plug into the formdata to upload, first base64 data into a string, then instantiate a arraybuffer, and then the string in 8-bit integer format into the Arraybuffer, Then, by Blobbuilder or Blob object, the 8-bit integer arraybuffer is converted into a binary object blob, then the Blob object is append to Formdata, which can be sent back to the background via Ajax.



XmlHttpRequest2 not only can send large data, but also more than to get the delivery of the progress of the API, I also made a simple implementation of the code.


    image upload, turn the Base64 picture into binary object, plug in Formdata upload     function upload (basestr, type, $li) {
        var text = Window.atob (Basestr.split (",") [1]);
        var buffer = new Arraybuffer (text.length);
        var ubuffer = new Uint8array (buffer);

        var pecent = 0, loop = null;         for (var i = 0; i < text.length i++) {     &nbsp
;      Ubuffer[i] = text.charcodeat (i);        }         var Builder = window. Webkitblobbuilder window.
Mozblobbuilder;

        var blob;         if (Builder) {         
   var builder = new Builder ();             builder.append (buffer);
            blob = Builder.getblob (type);        } else {             blob = new window.
Blob ([buffer], {type:type});        }         var xhr = new XMLHttpRequest (
);
        var formdata = new Formdata ();

        formdata.append ("ImageFile", blob);

        Xhr.open ("Post", "/cupload");         Xhr.onreadystatechange = function () {             if (xhr.readystate = 4 && Xhr.status = =) {    & nbsp;           Console.Log ("Upload success:" + Xhr.responsetext);

                clearinterval (loop);                //upload complete when you receive this message                  $li. Find (". Progress span"). Animate ({"width": "100%"}, pecent < 200:0, function () {         &
nbsp;          $ (this). html ("upload succeeded");

               });                 $ (". Pic-list"). Append ("<a href=" "+ Xhr.responsetext +" ">" + xhr.responsetext + "</ A> ")            }        };        //Data delivery progress, top 50% show this progress         Xhr.upload.addEventListener ("Progress", function (e) {         

   if (loop) return;
            pecent = ~ ~ (e.loaded/e.total)/2;             $li. Find (". Progress span"). CSS ("width", pecent

+ "%");             if (pecent = =) {    
            mockprogress ();            }        }

, false);        //Data after 50% with simulated progress         function Mockprogress () {            if (loop) return;             loop = setinterval (function () {  
              pecent++;                 $li. Find (". Progress

Span "). CSS (" width ", pecent +"% ");
                if (pecent = 99) {                   
  Clearinterval (loop);                }             }        }   
      xhr.send (Formdata);    }


At this point, the entire upload of the front-end image compression is completed, because it is submitted with the Formdata, so the background of the data with the normal form of the form submitted data processing can be.



If you are interested in this demo you can see the GitHub address of this demo:



Front-end Code: https://github.com/whxaxes/node-test/blob/master/server/upload/index_2.html



By the way also posted backstage implementation (NODEJS): Https://github.com/whxaxes/node-test/blob/master/server/upload/upload_2.js



And then post a preview of the demo (if you want to see the compression effect, please use PC browser access):



Links: http://wanghx.cn:9030/uindex_2 or directly scan the following two-dimensional code to see the effect (the server's bandwidth is relatively small, the speed may be relatively slow).







Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.