懒羊羊
2023-08-30 1ac2bc1590406d9babec036e154d8d08f34a6aa1
提交 | 用户 | 时间
1ac2bc 1 /*! WebUploader 0.1.2 */
2
3
4 /**
5  * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。
6  *
7  * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。
8  */
9 (function( root, factory ) {
10     var modules = {},
11
12         // 内部require, 简单不完全实现。
13         // https://github.com/amdjs/amdjs-api/wiki/require
14         _require = function( deps, callback ) {
15             var args, len, i;
16
17             // 如果deps不是数组,则直接返回指定module
18             if ( typeof deps === 'string' ) {
19                 return getModule( deps );
20             } else {
21                 args = [];
22                 for( len = deps.length, i = 0; i < len; i++ ) {
23                     args.push( getModule( deps[ i ] ) );
24                 }
25
26                 return callback.apply( null, args );
27             }
28         },
29
30         // 内部define,暂时不支持不指定id.
31         _define = function( id, deps, factory ) {
32             if ( arguments.length === 2 ) {
33                 factory = deps;
34                 deps = null;
35             }
36
37             _require( deps || [], function() {
38                 setModule( id, factory, arguments );
39             });
40         },
41
42         // 设置module, 兼容CommonJs写法。
43         setModule = function( id, factory, args ) {
44             var module = {
45                     exports: factory
46                 },
47                 returned;
48
49             if ( typeof factory === 'function' ) {
50                 args.length || (args = [ _require, module.exports, module ]);
51                 returned = factory.apply( null, args );
52                 returned !== undefined && (module.exports = returned);
53             }
54
55             modules[ id ] = module.exports;
56         },
57
58         // 根据id获取module
59         getModule = function( id ) {
60             var module = modules[ id ] || root[ id ];
61
62             if ( !module ) {
63                 throw new Error( '`' + id + '` is undefined' );
64             }
65
66             return module;
67         },
68
69         // 将所有modules,将路径ids装换成对象。
70         exportsTo = function( obj ) {
71             var key, host, parts, part, last, ucFirst;
72
73             // make the first character upper case.
74             ucFirst = function( str ) {
75                 return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));
76             };
77
78             for ( key in modules ) {
79                 host = obj;
80
81                 if ( !modules.hasOwnProperty( key ) ) {
82                     continue;
83                 }
84
85                 parts = key.split('/');
86                 last = ucFirst( parts.pop() );
87
88                 while( (part = ucFirst( parts.shift() )) ) {
89                     host[ part ] = host[ part ] || {};
90                     host = host[ part ];
91                 }
92
93                 host[ last ] = modules[ key ];
94             }
95         },
96
97         exports = factory( root, _define, _require ),
98         origin;
99
100     // exports every module.
101     exportsTo( exports );
102
103     if ( typeof module === 'object' && typeof module.exports === 'object' ) {
104
105         // For CommonJS and CommonJS-like environments where a proper window is present,
106         module.exports = exports;
107     } else if ( typeof define === 'function' && define.amd ) {
108
109         // Allow using this built library as an AMD module
110         // in another project. That other project will only
111         // see this AMD call, not the internal modules in
112         // the closure below.
113         define([], exports );
114     } else {
115
116         // Browser globals case. Just assign the
117         // result to a property on the global.
118         origin = root.WebUploader;
119         root.WebUploader = exports;
120         root.WebUploader.noConflict = function() {
121             root.WebUploader = origin;
122         };
123     }
124 })( this, function( window, define, require ) {
125
126
127     /**
128      * @fileOverview jQuery or Zepto
129      */
130     define('dollar-third',[],function() {
131         return window.jQuery || window.Zepto;
132     });
133     /**
134      * @fileOverview Dom 操作相关
135      */
136     define('dollar',[
137         'dollar-third'
138     ], function( _ ) {
139         return _;
140     });
141     /**
142      * @fileOverview 使用jQuery的Promise
143      */
144     define('promise-third',[
145         'dollar'
146     ], function( $ ) {
147         return {
148             Deferred: $.Deferred,
149             when: $.when,
150     
151             isPromise: function( anything ) {
152                 return anything && typeof anything.then === 'function';
153             }
154         };
155     });
156     /**
157      * @fileOverview Promise/A+
158      */
159     define('promise',[
160         'promise-third'
161     ], function( _ ) {
162         return _;
163     });
164     /**
165      * @fileOverview 基础类方法。
166      */
167     
168     /**
169      * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。
170      *
171      * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.
172      * 默认module id该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如:
173      *
174      * * module `base`:WebUploader.Base
175      * * module `file`: WebUploader.File
176      * * module `lib/dnd`: WebUploader.Lib.Dnd
177      * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd
178      *
179      *
180      * 以下文档将可能省略`WebUploader`前缀。
181      * @module WebUploader
182      * @title WebUploader API文档
183      */
184     define('base',[
185         'dollar',
186         'promise'
187     ], function( $, promise ) {
188     
189         var noop = function() {},
190             call = Function.call;
191     
192         // http://jsperf.com/uncurrythis
193         // 反科里化
194         function uncurryThis( fn ) {
195             return function() {
196                 return call.apply( fn, arguments );
197             };
198         }
199     
200         function bindFn( fn, context ) {
201             return function() {
202                 return fn.apply( context, arguments );
203             };
204         }
205     
206         function createObject( proto ) {
207             var f;
208     
209             if ( Object.create ) {
210                 return Object.create( proto );
211             } else {
212                 f = function() {};
213                 f.prototype = proto;
214                 return new f();
215             }
216         }
217     
218     
219         /**
220          * 基础类,提供一些简单常用的方法。
221          * @class Base
222          */
223         return {
224     
225             /**
226              * @property {String} version 当前版本号。
227              */
228             version: '0.1.2',
229     
230             /**
231              * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。
232              */
233             $: $,
234     
235             Deferred: promise.Deferred,
236     
237             isPromise: promise.isPromise,
238     
239             when: promise.when,
240     
241             /**
242              * @description  简单的浏览器检查结果。
243              *
244              * * `webkit`  webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。
245              * * `chrome`  chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。
246              * * `ie`  ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+**
247              * * `firefox`  firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。
248              * * `safari`  safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。
249              * * `opera`  opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。
250              *
251              * @property {Object} [browser]
252              */
253             browser: (function( ua ) {
254                 var ret = {},
255                     webkit = ua.match( /WebKit\/([\d.]+)/ ),
256                     chrome = ua.match( /Chrome\/([\d.]+)/ ) ||
257                         ua.match( /CriOS\/([\d.]+)/ ),
258     
259                     ie = ua.match( /MSIE\s([\d\.]+)/ ) ||
260                         ua.match(/(?:trident)(?:.*rv:([\w.]+))?/i),
261                     firefox = ua.match( /Firefox\/([\d.]+)/ ),
262                     safari = ua.match( /Safari\/([\d.]+)/ ),
263                     opera = ua.match( /OPR\/([\d.]+)/ );
264     
265                 webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));
266                 chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));
267                 ie && (ret.ie = parseFloat( ie[ 1 ] ));
268                 firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));
269                 safari && (ret.safari = parseFloat( safari[ 1 ] ));
270                 opera && (ret.opera = parseFloat( opera[ 1 ] ));
271     
272                 return ret;
273             })( navigator.userAgent ),
274     
275             /**
276              * @description  操作系统检查结果。
277              *
278              * * `android`  如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。
279              * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。
280              * @property {Object} [os]
281              */
282             os: (function( ua ) {
283                 var ret = {},
284     
285                     // osx = !!ua.match( /\(Macintosh\; Intel / ),
286                     android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ),
287                     ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ );
288     
289                 // osx && (ret.osx = true);
290                 android && (ret.android = parseFloat( android[ 1 ] ));
291                 ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));
292     
293                 return ret;
294             })( navigator.userAgent ),
295     
296             /**
297              * 实现类与类之间的继承。
298              * @method inherits
299              * @grammar Base.inherits( super ) => child
300              * @grammar Base.inherits( super, protos ) => child
301              * @grammar Base.inherits( super, protos, statics ) => child
302              * @param  {Class} super 父类
303              * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。
304              * @param  {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。
305              * @param  {Object} [statics] 静态属性或方法。
306              * @return {Class} 返回子类。
307              * @example
308              * function Person() {
309              *     console.log( 'Super' );
310              * }
311              * Person.prototype.hello = function() {
312              *     console.log( 'hello' );
313              * };
314              *
315              * var Manager = Base.inherits( Person, {
316              *     world: function() {
317              *         console.log( 'World' );
318              *     }
319              * });
320              *
321              * // 因为没有指定构造器,父类的构造器将会执行。
322              * var instance = new Manager();    // => Super
323              *
324              * // 继承子父类的方法
325              * instance.hello();    // => hello
326              * instance.world();    // => World
327              *
328              * // 子类的__super__属性指向父类
329              * console.log( Manager.__super__ === Person );    // => true
330              */
331             inherits: function( Super, protos, staticProtos ) {
332                 var child;
333     
334                 if ( typeof protos === 'function' ) {
335                     child = protos;
336                     protos = null;
337                 } else if ( protos && protos.hasOwnProperty('constructor') ) {
338                     child = protos.constructor;
339                 } else {
340                     child = function() {
341                         return Super.apply( this, arguments );
342                     };
343                 }
344     
345                 // 复制静态方法
346                 $.extend( true, child, Super, staticProtos || {} );
347     
348                 /* jshint camelcase: false */
349     
350                 // 让子类的__super__属性指向父类。
351                 child.__super__ = Super.prototype;
352     
353                 // 构建原型,添加原型方法或属性。
354                 // 暂时用Object.create实现。
355                 child.prototype = createObject( Super.prototype );
356                 protos && $.extend( true, child.prototype, protos );
357     
358                 return child;
359             },
360     
361             /**
362              * 一个不做任何事情的方法。可以用来赋值给默认的callback.
363              * @method noop
364              */
365             noop: noop,
366     
367             /**
368              * 返回一个新的方法,此方法将已指定的`context`来执行。
369              * @grammar Base.bindFn( fn, context ) => Function
370              * @method bindFn
371              * @example
372              * var doSomething = function() {
373              *         console.log( this.name );
374              *     },
375              *     obj = {
376              *         name: 'Object Name'
377              *     },
378              *     aliasFn = Base.bind( doSomething, obj );
379              *
380              *  aliasFn();    // => Object Name
381              *
382              */
383             bindFn: bindFn,
384     
385             /**
386              * 引用Console.log如果存在的话,否则引用一个[空函数loop](#WebUploader:Base.log)。
387              * @grammar Base.log( args... ) => undefined
388              * @method log
389              */
390             log: (function() {
391                 if ( window.console ) {
392                     return bindFn( console.log, console );
393                 }
394                 return noop;
395             })(),
396     
397             nextTick: (function() {
398     
399                 return function( cb ) {
400                     setTimeout( cb, 1 );
401                 };
402     
403                 // @bug 当浏览器不在当前窗口时就停了。
404                 // var next = window.requestAnimationFrame ||
405                 //     window.webkitRequestAnimationFrame ||
406                 //     window.mozRequestAnimationFrame ||
407                 //     function( cb ) {
408                 //         window.setTimeout( cb, 1000 / 60 );
409                 //     };
410     
411                 // // fix: Uncaught TypeError: Illegal invocation
412                 // return bindFn( next, window );
413             })(),
414     
415             /**
416              * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。
417              * 将用来将非数组对象转化成数组对象。
418              * @grammar Base.slice( target, start[, end] ) => Array
419              * @method slice
420              * @example
421              * function doSomthing() {
422              *     var args = Base.slice( arguments, 1 );
423              *     console.log( args );
424              * }
425              *
426              * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array ["arg2", "arg3"]
427              */
428             slice: uncurryThis( [].slice ),
429     
430             /**
431              * 生成唯一的ID
432              * @method guid
433              * @grammar Base.guid() => String
434              * @grammar Base.guid( prefx ) => String
435              */
436             guid: (function() {
437                 var counter = 0;
438     
439                 return function( prefix ) {
440                     var guid = (+new Date()).toString( 32 ),
441                         i = 0;
442     
443                     for ( ; i < 5; i++ ) {
444                         guid += Math.floor( Math.random() * 65535 ).toString( 32 );
445                     }
446     
447                     return (prefix || 'wu_') + guid + (counter++).toString( 32 );
448                 };
449             })(),
450     
451             /**
452              * 格式化文件大小, 输出成带单位的字符串
453              * @method formatSize
454              * @grammar Base.formatSize( size ) => String
455              * @grammar Base.formatSize( size, pointLength ) => String
456              * @grammar Base.formatSize( size, pointLength, units ) => String
457              * @param {Number} size 文件大小
458              * @param {Number} [pointLength=2] 精确到的小数点数。
459              * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K.
460              * @example
461              * console.log( Base.formatSize( 100 ) );    // => 100B
462              * console.log( Base.formatSize( 1024 ) );    // => 1.00K
463              * console.log( Base.formatSize( 1024, 0 ) );    // => 1K
464              * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M
465              * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G
466              * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB
467              */
468             formatSize: function( size, pointLength, units ) {
469                 var unit;
470     
471                 units = units || [ 'B', 'K', 'M', 'G', 'TB' ];
472     
473                 while ( (unit = units.shift()) && size > 1024 ) {
474                     size = size / 1024;
475                 }
476     
477                 return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +
478                         unit;
479             }
480         };
481     });
482     /**
483      * 事件处理类,可以独立使用,也可以扩展给对象使用。
484      * @fileOverview Mediator
485      */
486     define('mediator',[
487         'base'
488     ], function( Base ) {
489         var $ = Base.$,
490             slice = [].slice,
491             separator = /\s+/,
492             protos;
493     
494         // 根据条件过滤出事件handlers.
495         function findHandlers( arr, name, callback, context ) {
496             return $.grep( arr, function( handler ) {
497                 return handler &&
498                         (!name || handler.e === name) &&
499                         (!callback || handler.cb === callback ||
500                         handler.cb._cb === callback) &&
501                         (!context || handler.ctx === context);
502             });
503         }
504     
505         function eachEvent( events, callback, iterator ) {
506             // 不支持对象,只支持多个event用空格隔开
507             $.each( (events || '').split( separator ), function( _, key ) {
508                 iterator( key, callback );
509             });
510         }
511     
512         function triggerHanders( events, args ) {
513             var stoped = false,
514                 i = -1,
515                 len = events.length,
516                 handler;
517     
518             while ( ++i < len ) {
519                 handler = events[ i ];
520     
521                 if ( handler.cb.apply( handler.ctx2, args ) === false ) {
522                     stoped = true;
523                     break;
524                 }
525             }
526     
527             return !stoped;
528         }
529     
530         protos = {
531     
532             /**
533              * 绑定事件。
534              *
535              * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如
536              * ```javascript
537              * var obj = {};
538              *
539              * // 使得obj有事件行为
540              * Mediator.installTo( obj );
541              *
542              * obj.on( 'testa', function( arg1, arg2 ) {
543              *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'
544              * });
545              *
546              * obj.trigger( 'testa', 'arg1', 'arg2' );
547              * ```
548              *
549              * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。
550              * 切会影响到`trigger`方法的返回值,为`false`。
551              *
552              * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处,
553              * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。
554              * ```javascript
555              * obj.on( 'all', function( type, arg1, arg2 ) {
556              *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'
557              * });
558              * ```
559              *
560              * @method on
561              * @grammar on( name, callback[, context] ) => self
562              * @param  {String}   name     事件名,支持多个事件用空格隔开
563              * @param  {Function} callback 事件处理器
564              * @param  {Object}   [context]  事件处理器的上下文。
565              * @return {self} 返回自身,方便链式
566              * @chainable
567              * @class Mediator
568              */
569             on: function( name, callback, context ) {
570                 var me = this,
571                     set;
572     
573                 if ( !callback ) {
574                     return this;
575                 }
576     
577                 set = this._events || (this._events = []);
578     
579                 eachEvent( name, callback, function( name, callback ) {
580                     var handler = { e: name };
581     
582                     handler.cb = callback;
583                     handler.ctx = context;
584                     handler.ctx2 = context || me;
585                     handler.id = set.length;
586     
587                     set.push( handler );
588                 });
589     
590                 return this;
591             },
592     
593             /**
594              * 绑定事件,且当handler执行完后,自动解除绑定。
595              * @method once
596              * @grammar once( name, callback[, context] ) => self
597              * @param  {String}   name     事件名
598              * @param  {Function} callback 事件处理器
599              * @param  {Object}   [context]  事件处理器的上下文。
600              * @return {self} 返回自身,方便链式
601              * @chainable
602              */
603             once: function( name, callback, context ) {
604                 var me = this;
605     
606                 if ( !callback ) {
607                     return me;
608                 }
609     
610                 eachEvent( name, callback, function( name, callback ) {
611                     var once = function() {
612                             me.off( name, once );
613                             return callback.apply( context || me, arguments );
614                         };
615     
616                     once._cb = callback;
617                     me.on( name, once, context );
618                 });
619     
620                 return me;
621             },
622     
623             /**
624              * 解除事件绑定
625              * @method off
626              * @grammar off( [name[, callback[, context] ] ] ) => self
627              * @param  {String}   [name]     事件名
628              * @param  {Function} [callback] 事件处理器
629              * @param  {Object}   [context]  事件处理器的上下文。
630              * @return {self} 返回自身,方便链式
631              * @chainable
632              */
633             off: function( name, cb, ctx ) {
634                 var events = this._events;
635     
636                 if ( !events ) {
637                     return this;
638                 }
639     
640                 if ( !name && !cb && !ctx ) {
641                     this._events = [];
642                     return this;
643                 }
644     
645                 eachEvent( name, cb, function( name, cb ) {
646                     $.each( findHandlers( events, name, cb, ctx ), function() {
647                         delete events[ this.id ];
648                     });
649                 });
650     
651                 return this;
652             },
653     
654             /**
655              * 触发事件
656              * @method trigger
657              * @grammar trigger( name[, args...] ) => self
658              * @param  {String}   type     事件名
659              * @param  {*} [...] 任意参数
660              * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true
661              */
662             trigger: function( type ) {
663                 var args, events, allEvents;
664     
665                 if ( !this._events || !type ) {
666                     return this;
667                 }
668     
669                 args = slice.call( arguments, 1 );
670                 events = findHandlers( this._events, type );
671                 allEvents = findHandlers( this._events, 'all' );
672     
673                 return triggerHanders( events, args ) &&
674                         triggerHanders( allEvents, arguments );
675             }
676         };
677     
678         /**
679          * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。
680          * 主要目的是负责模块与模块之间的合作,降低耦合度。
681          *
682          * @class Mediator
683          */
684         return $.extend({
685     
686             /**
687              * 可以通过这个接口,使任何对象具备事件功能。
688              * @method installTo
689              * @param  {Object} obj 需要具备事件行为的对象。
690              * @return {Object} 返回obj.
691              */
692             installTo: function( obj ) {
693                 return $.extend( obj, protos );
694             }
695     
696         }, protos );
697     });
698     /**
699      * @fileOverview Uploader上传类
700      */
701     define('uploader',[
702         'base',
703         'mediator'
704     ], function( Base, Mediator ) {
705     
706         var $ = Base.$;
707     
708         /**
709          * 上传入口类。
710          * @class Uploader
711          * @constructor
712          * @grammar new Uploader( opts ) => Uploader
713          * @example
714          * var uploader = WebUploader.Uploader({
715          *     swf: 'path_of_swf/Uploader.swf',
716          *
717          *     // 开起分片上传。
718          *     chunked: true
719          * });
720          */
721         function Uploader( opts ) {
722             this.options = $.extend( true, {}, Uploader.options, opts );
723             this._init( this.options );
724         }
725     
726         // default Options
727         // widgets中有相应扩展
728         Uploader.options = {};
729         Mediator.installTo( Uploader.prototype );
730     
731         // 批量添加纯命令式方法。
732         $.each({
733             upload: 'start-upload',
734             stop: 'stop-upload',
735             getFile: 'get-file',
736             getFiles: 'get-files',
737             addFile: 'add-file',
738             addFiles: 'add-file',
739             sort: 'sort-files',
740             removeFile: 'remove-file',
741             skipFile: 'skip-file',
742             retry: 'retry',
743             isInProgress: 'is-in-progress',
744             makeThumb: 'make-thumb',
745             getDimension: 'get-dimension',
746             addButton: 'add-btn',
747             getRuntimeType: 'get-runtime-type',
748             refresh: 'refresh',
749             disable: 'disable',
750             enable: 'enable',
751             reset: 'reset'
752         }, function( fn, command ) {
753             Uploader.prototype[ fn ] = function() {
754                 return this.request( command, arguments );
755             };
756         });
757     
758         $.extend( Uploader.prototype, {
759             state: 'pending',
760     
761             _init: function( opts ) {
762                 var me = this;
763     
764                 me.request( 'init', opts, function() {
765                     me.state = 'ready';
766                     me.trigger('ready');
767                 });
768             },
769     
770             /**
771              * 获取或者设置Uploader配置项。
772              * @method option
773              * @grammar option( key ) => *
774              * @grammar option( key, val ) => self
775              * @example
776              *
777              * // 初始状态图片上传前不会压缩
778              * var uploader = new WebUploader.Uploader({
779              *     resize: null;
780              * });
781              *
782              * // 修改后图片上传前,尝试将图片压缩到1600 * 1600
783              * uploader.options( 'resize', {
784              *     width: 1600,
785              *     height: 1600
786              * });
787              */
788             option: function( key, val ) {
789                 var opts = this.options;
790     
791                 // setter
792                 if ( arguments.length > 1 ) {
793     
794                     if ( $.isPlainObject( val ) &&
795                             $.isPlainObject( opts[ key ] ) ) {
796                         $.extend( opts[ key ], val );
797                     } else {
798                         opts[ key ] = val;
799                     }
800     
801                 } else {    // getter
802                     return key ? opts[ key ] : opts;
803                 }
804             },
805     
806             /**
807              * 获取文件统计信息。返回一个包含一下信息的对象。
808              * * `successNum` 上传成功的文件数
809              * * `uploadFailNum` 上传失败的文件数
810              * * `cancelNum` 被删除的文件数
811              * * `invalidNum` 无效的文件数
812              * * `queueNum` 还在队列中的文件数
813              * @method getStats
814              * @grammar getStats() => Object
815              */
816             getStats: function() {
817                 // return this._mgr.getStats.apply( this._mgr, arguments );
818                 var stats = this.request('get-stats');
819     
820                 return {
821                     successNum: stats.numOfSuccess,
822     
823                     // who care?
824                     // queueFailNum: 0,
825                     cancelNum: stats.numOfCancel,
826                     invalidNum: stats.numOfInvalid,
827                     uploadFailNum: stats.numOfUploadFailed,
828                     queueNum: stats.numOfQueue
829                 };
830             },
831     
832             // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
833             trigger: function( type/*, args...*/ ) {
834                 var args = [].slice.call( arguments, 1 ),
835                     opts = this.options,
836                     name = 'on' + type.substring( 0, 1 ).toUpperCase() +
837                         type.substring( 1 );
838     
839                 if (
840                         // 调用通过on方法注册的handler.
841                         Mediator.trigger.apply( this, arguments ) === false ||
842     
843                         // 调用opts.onEvent
844                         $.isFunction( opts[ name ] ) &&
845                         opts[ name ].apply( this, args ) === false ||
846     
847                         // 调用this.onEvent
848                         $.isFunction( this[ name ] ) &&
849                         this[ name ].apply( this, args ) === false ||
850     
851                         // 广播所有uploader的事件。
852                         Mediator.trigger.apply( Mediator,
853                         [ this, type ].concat( args ) ) === false ) {
854     
855                     return false;
856                 }
857     
858                 return true;
859             },
860     
861             // widgets/widget.js将补充此方法的详细文档。
862             request: Base.noop
863         });
864     
865         /**
866          * 创建Uploader实例,等同于new Uploader( opts );
867          * @method create
868          * @class Base
869          * @static
870          * @grammar Base.create( opts ) => Uploader
871          */
872         Base.create = Uploader.create = function( opts ) {
873             return new Uploader( opts );
874         };
875     
876         // 暴露Uploader,可以通过它来扩展业务逻辑。
877         Base.Uploader = Uploader;
878     
879         return Uploader;
880     });
881     /**
882      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
883      */
884     define('runtime/runtime',[
885         'base',
886         'mediator'
887     ], function( Base, Mediator ) {
888     
889         var $ = Base.$,
890             factories = {},
891     
892             // 获取对象的第一个key
893             getFirstKey = function( obj ) {
894                 for ( var key in obj ) {
895                     if ( obj.hasOwnProperty( key ) ) {
896                         return key;
897                     }
898                 }
899                 return null;
900             };
901     
902         // 接口类。
903         function Runtime( options ) {
904             this.options = $.extend({
905                 container: document.body
906             }, options );
907             this.uid = Base.guid('rt_');
908         }
909     
910         $.extend( Runtime.prototype, {
911     
912             getContainer: function() {
913                 var opts = this.options,
914                     parent, container;
915     
916                 if ( this._container ) {
917                     return this._container;
918                 }
919     
920                 parent = $( opts.container || document.body );
921                 container = $( document.createElement('div') );
922     
923                 container.attr( 'id', 'rt_' + this.uid );
924                 container.css({
925                     position: 'absolute',
926                     top: '0px',
927                     left: '0px',
928                     width: '1px',
929                     height: '1px',
930                     overflow: 'hidden'
931                 });
932     
933                 parent.append( container );
934                 parent.addClass('webuploader-container');
935                 this._container = container;
936                 return container;
937             },
938     
939             init: Base.noop,
940             exec: Base.noop,
941     
942             destroy: function() {
943                 if ( this._container ) {
944                     this._container.parentNode.removeChild( this.__container );
945                 }
946     
947                 this.off();
948             }
949         });
950     
951         Runtime.orders = 'html5,flash';
952     
953     
954         /**
955          * 添加Runtime实现。
956          * @param {String} type    类型
957          * @param {Runtime} factory 具体Runtime实现。
958          */
959         Runtime.addRuntime = function( type, factory ) {
960             factories[ type ] = factory;
961         };
962     
963         Runtime.hasRuntime = function( type ) {
964             return !!(type ? factories[ type ] : getFirstKey( factories ));
965         };
966     
967         Runtime.create = function( opts, orders ) {
968             var type, runtime;
969     
970             orders = orders || Runtime.orders;
971             $.each( orders.split( /\s*,\s*/g ), function() {
972                 if ( factories[ this ] ) {
973                     type = this;
974                     return false;
975                 }
976             });
977     
978             type = type || getFirstKey( factories );
979     
980             if ( !type ) {
981                 throw new Error('Runtime Error');
982             }
983     
984             runtime = new factories[ type ]( opts );
985             return runtime;
986         };
987     
988         Mediator.installTo( Runtime.prototype );
989         return Runtime;
990     });
991     
992     /**
993      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
994      */
995     define('runtime/client',[
996         'base',
997         'mediator',
998         'runtime/runtime'
999     ], function( Base, Mediator, Runtime ) {
1000     
1001         var cache;
1002     
1003         cache = (function() {
1004             var obj = {};
1005     
1006             return {
1007                 add: function( runtime ) {
1008                     obj[ runtime.uid ] = runtime;
1009                 },
1010     
1011                 get: function( ruid, standalone ) {
1012                     var i;
1013     
1014                     if ( ruid ) {
1015                         return obj[ ruid ];
1016                     }
1017     
1018                     for ( i in obj ) {
1019                         // 有些类型不能重用,比如filepicker.
1020                         if ( standalone && obj[ i ].__standalone ) {
1021                             continue;
1022                         }
1023     
1024                         return obj[ i ];
1025                     }
1026     
1027                     return null;
1028                 },
1029     
1030                 remove: function( runtime ) {
1031                     delete obj[ runtime.uid ];
1032                 }
1033             };
1034         })();
1035     
1036         function RuntimeClient( component, standalone ) {
1037             var deferred = Base.Deferred(),
1038                 runtime;
1039     
1040             this.uid = Base.guid('client_');
1041     
1042             // 允许runtime没有初始化之前,注册一些方法在初始化后执行。
1043             this.runtimeReady = function( cb ) {
1044                 return deferred.done( cb );
1045             };
1046     
1047             this.connectRuntime = function( opts, cb ) {
1048     
1049                 // already connected.
1050                 if ( runtime ) {
1051                     throw new Error('already connected!');
1052                 }
1053     
1054                 deferred.done( cb );
1055     
1056                 if ( typeof opts === 'string' && cache.get( opts ) ) {
1057                     runtime = cache.get( opts );
1058                 }
1059     
1060                 // 像filePicker只能独立存在,不能公用。
1061                 runtime = runtime || cache.get( null, standalone );
1062     
1063                 // 需要创建
1064                 if ( !runtime ) {
1065                     runtime = Runtime.create( opts, opts.runtimeOrder );
1066                     runtime.__promise = deferred.promise();
1067                     runtime.once( 'ready', deferred.resolve );
1068                     runtime.init();
1069                     cache.add( runtime );
1070                     runtime.__client = 1;
1071                 } else {
1072                     // 来自cache
1073                     Base.$.extend( runtime.options, opts );
1074                     runtime.__promise.then( deferred.resolve );
1075                     runtime.__client++;
1076                 }
1077     
1078                 standalone && (runtime.__standalone = standalone);
1079                 return runtime;
1080             };
1081     
1082             this.getRuntime = function() {
1083                 return runtime;
1084             };
1085     
1086             this.disconnectRuntime = function() {
1087                 if ( !runtime ) {
1088                     return;
1089                 }
1090     
1091                 runtime.__client--;
1092     
1093                 if ( runtime.__client <= 0 ) {
1094                     cache.remove( runtime );
1095                     delete runtime.__promise;
1096                     runtime.destroy();
1097                 }
1098     
1099                 runtime = null;
1100             };
1101     
1102             this.exec = function() {
1103                 if ( !runtime ) {
1104                     return;
1105                 }
1106     
1107                 var args = Base.slice( arguments );
1108                 component && args.unshift( component );
1109     
1110                 return runtime.exec.apply( this, args );
1111             };
1112     
1113             this.getRuid = function() {
1114                 return runtime && runtime.uid;
1115             };
1116     
1117             this.destroy = (function( destroy ) {
1118                 return function() {
1119                     destroy && destroy.apply( this, arguments );
1120                     this.trigger('destroy');
1121                     this.off();
1122                     this.exec('destroy');
1123                     this.disconnectRuntime();
1124                 };
1125             })( this.destroy );
1126         }
1127     
1128         Mediator.installTo( RuntimeClient.prototype );
1129         return RuntimeClient;
1130     });
1131     /**
1132      * @fileOverview Blob
1133      */
1134     define('lib/blob',[
1135         'base',
1136         'runtime/client'
1137     ], function( Base, RuntimeClient ) {
1138     
1139         function Blob( ruid, source ) {
1140             var me = this;
1141     
1142             me.source = source;
1143             me.ruid = ruid;
1144     
1145             RuntimeClient.call( me, 'Blob' );
1146     
1147             this.uid = source.uid || this.uid;
1148             this.type = source.type || '';
1149             this.size = source.size || 0;
1150     
1151             if ( ruid ) {
1152                 me.connectRuntime( ruid );
1153             }
1154         }
1155     
1156         Base.inherits( RuntimeClient, {
1157             constructor: Blob,
1158     
1159             slice: function( start, end ) {
1160                 return this.exec( 'slice', start, end );
1161             },
1162     
1163             getSource: function() {
1164                 return this.source;
1165             }
1166         });
1167     
1168         return Blob;
1169     });
1170     /**
1171      * 为了统一化Flash的File和HTML5的File而存在。
1172      * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。
1173      * @fileOverview File
1174      */
1175     define('lib/file',[
1176         'base',
1177         'lib/blob'
1178     ], function( Base, Blob ) {
1179     
1180         var uid = 1,
1181             rExt = /\.([^.]+)$/;
1182     
1183         function File( ruid, file ) {
1184             var ext;
1185     
1186             Blob.apply( this, arguments );
1187             this.name = file.name || ('untitled' + uid++);
1188             ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';
1189     
1190             // todo 支持其他类型文件的转换。
1191     
1192             // 如果有mimetype, 但是文件名里面没有找出后缀规律
1193             if ( !ext && this.type ) {
1194                 ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?
1195                         RegExp.$1.toLowerCase() : '';
1196                 this.name += '.' + ext;
1197             }
1198     
1199             // 如果没有指定mimetype, 但是知道文件后缀。
1200             if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {
1201                 this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);
1202             }
1203     
1204             this.ext = ext;
1205             this.lastModifiedDate = file.lastModifiedDate ||
1206                     (new Date()).toLocaleString();
1207         }
1208     
1209         return Base.inherits( Blob, File );
1210     });
1211     
1212     /**
1213      * @fileOverview 错误信息
1214      */
1215     define('lib/filepicker',[
1216         'base',
1217         'runtime/client',
1218         'lib/file'
1219     ], function( Base, RuntimeClent, File ) {
1220     
1221         var $ = Base.$;
1222     
1223         function FilePicker( opts ) {
1224             opts = this.options = $.extend({}, FilePicker.options, opts );
1225     
1226             opts.container = $( opts.id );
1227     
1228             if ( !opts.container.length ) {
1229                 throw new Error('按钮指定错误');
1230             }
1231     
1232             opts.innerHTML = opts.innerHTML || opts.label ||
1233                     opts.container.html() || '';
1234     
1235             opts.button = $( opts.button || document.createElement('div') );
1236             opts.button.html( opts.innerHTML );
1237             opts.container.html( opts.button );
1238     
1239             RuntimeClent.call( this, 'FilePicker', true );
1240         }
1241     
1242         FilePicker.options = {
1243             button: null,
1244             container: null,
1245             label: null,
1246             innerHTML: null,
1247             multiple: true,
1248             accept: null,
1249             name: 'file'
1250         };
1251     
1252         Base.inherits( RuntimeClent, {
1253             constructor: FilePicker,
1254     
1255             init: function() {
1256                 var me = this,
1257                     opts = me.options,
1258                     button = opts.button;
1259     
1260                 button.addClass('webuploader-pick');
1261     
1262                 me.on( 'all', function( type ) {
1263                     var files;
1264     
1265                     switch ( type ) {
1266                         case 'mouseenter':
1267                             button.addClass('webuploader-pick-hover');
1268                             break;
1269     
1270                         case 'mouseleave':
1271                             button.removeClass('webuploader-pick-hover');
1272                             break;
1273     
1274                         case 'change':
1275                             files = me.exec('getFiles');
1276                             me.trigger( 'select', $.map( files, function( file ) {
1277                                 file = new File( me.getRuid(), file );
1278     
1279                                 // 记录来源。
1280                                 file._refer = opts.container;
1281                                 return file;
1282                             }), opts.container );
1283                             break;
1284                     }
1285                 });
1286     
1287                 me.connectRuntime( opts, function() {
1288                     me.refresh();
1289                     me.exec( 'init', opts );
1290                     me.trigger('ready');
1291                 });
1292     
1293                 $( window ).on( 'resize', function() {
1294                     me.refresh();
1295                 });
1296             },
1297     
1298             refresh: function() {
1299                 var shimContainer = this.getRuntime().getContainer(),
1300                     button = this.options.button,
1301                     width = button.outerWidth ?
1302                             button.outerWidth() : button.width(),
1303     
1304                     height = button.outerHeight ?
1305                             button.outerHeight() : button.height(),
1306     
1307                     pos = button.offset();
1308     
1309                 width && height && shimContainer.css({
1310                     bottom: 'auto',
1311                     right: 'auto',
1312                     width: width + 'px',
1313                     height: height + 'px'
1314                 }).offset( pos );
1315             },
1316     
1317             enable: function() {
1318                 var btn = this.options.button;
1319     
1320                 btn.removeClass('webuploader-pick-disable');
1321                 this.refresh();
1322             },
1323     
1324             disable: function() {
1325                 var btn = this.options.button;
1326     
1327                 this.getRuntime().getContainer().css({
1328                     top: '-99999px'
1329                 });
1330     
1331                 btn.addClass('webuploader-pick-disable');
1332             },
1333     
1334             destroy: function() {
1335                 if ( this.runtime ) {
1336                     this.exec('destroy');
1337                     this.disconnectRuntime();
1338                 }
1339             }
1340         });
1341     
1342         return FilePicker;
1343     });
1344     
1345     /**
1346      * @fileOverview 组件基类。
1347      */
1348     define('widgets/widget',[
1349         'base',
1350         'uploader'
1351     ], function( Base, Uploader ) {
1352     
1353         var $ = Base.$,
1354             _init = Uploader.prototype._init,
1355             IGNORE = {},
1356             widgetClass = [];
1357     
1358         function isArrayLike( obj ) {
1359             if ( !obj ) {
1360                 return false;
1361             }
1362     
1363             var length = obj.length,
1364                 type = $.type( obj );
1365     
1366             if ( obj.nodeType === 1 && length ) {
1367                 return true;
1368             }
1369     
1370             return type === 'array' || type !== 'function' && type !== 'string' &&
1371                     (length === 0 || typeof length === 'number' && length > 0 &&
1372                     (length - 1) in obj);
1373         }
1374     
1375         function Widget( uploader ) {
1376             this.owner = uploader;
1377             this.options = uploader.options;
1378         }
1379     
1380         $.extend( Widget.prototype, {
1381     
1382             init: Base.noop,
1383     
1384             // 类Backbone的事件监听声明,监听uploader实例上的事件
1385             // widget直接无法监听事件,事件只能通过uploader来传递
1386             invoke: function( apiName, args ) {
1387     
1388                 /*
1389                     {
1390                         'make-thumb': 'makeThumb'
1391                     }
1392                  */
1393                 var map = this.responseMap;
1394     
1395                 // 如果无API响应声明则忽略
1396                 if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||
1397                         !$.isFunction( this[ map[ apiName ] ] ) ) {
1398     
1399                     return IGNORE;
1400                 }
1401     
1402                 return this[ map[ apiName ] ].apply( this, args );
1403     
1404             },
1405     
1406             /**
1407              * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。
1408              * @method request
1409              * @grammar request( command, args ) => * | Promise
1410              * @grammar request( command, args, callback ) => Promise
1411              * @for  Uploader
1412              */
1413             request: function() {
1414                 return this.owner.request.apply( this.owner, arguments );
1415             }
1416         });
1417     
1418         // 扩展Uploader.
1419         $.extend( Uploader.prototype, {
1420     
1421             // 覆写_init用来初始化widgets
1422             _init: function() {
1423                 var me = this,
1424                     widgets = me._widgets = [];
1425     
1426                 $.each( widgetClass, function( _, klass ) {
1427                     widgets.push( new klass( me ) );
1428                 });
1429     
1430                 return _init.apply( me, arguments );
1431             },
1432     
1433             request: function( apiName, args, callback ) {
1434                 var i = 0,
1435                     widgets = this._widgets,
1436                     len = widgets.length,
1437                     rlts = [],
1438                     dfds = [],
1439                     widget, rlt, promise, key;
1440     
1441                 args = isArrayLike( args ) ? args : [ args ];
1442     
1443                 for ( ; i < len; i++ ) {
1444                     widget = widgets[ i ];
1445                     rlt = widget.invoke( apiName, args );
1446     
1447                     if ( rlt !== IGNORE ) {
1448     
1449                         // Deferred对象
1450                         if ( Base.isPromise( rlt ) ) {
1451                             dfds.push( rlt );
1452                         } else {
1453                             rlts.push( rlt );
1454                         }
1455                     }
1456                 }
1457     
1458                 // 如果有callback,则用异步方式。
1459                 if ( callback || dfds.length ) {
1460                     promise = Base.when.apply( Base, dfds );
1461                     key = promise.pipe ? 'pipe' : 'then';
1462     
1463                     // 很重要不能删除。删除了会死循环。
1464                     // 保证执行顺序。让callback总是在下一个tick中执行。
1465                     return promise[ key ](function() {
1466                                 var deferred = Base.Deferred(),
1467                                     args = arguments;
1468     
1469                                 setTimeout(function() {
1470                                     deferred.resolve.apply( deferred, args );
1471                                 }, 1 );
1472     
1473                                 return deferred.promise();
1474                             })[ key ]( callback || Base.noop );
1475                 } else {
1476                     return rlts[ 0 ];
1477                 }
1478             }
1479         });
1480     
1481         /**
1482          * 添加组件
1483          * @param  {object} widgetProto 组件原型,构造函数通过constructor属性定义
1484          * @param  {object} responseMap API名称与函数实现的映射
1485          * @example
1486          *     Uploader.register( {
1487          *         init: function( options ) {},
1488          *         makeThumb: function() {}
1489          *     }, {
1490          *         'make-thumb': 'makeThumb'
1491          *     } );
1492          */
1493         Uploader.register = Widget.register = function( responseMap, widgetProto ) {
1494             var map = { init: 'init' },
1495                 klass;
1496     
1497             if ( arguments.length === 1 ) {
1498                 widgetProto = responseMap;
1499                 widgetProto.responseMap = map;
1500             } else {
1501                 widgetProto.responseMap = $.extend( map, responseMap );
1502             }
1503     
1504             klass = Base.inherits( Widget, widgetProto );
1505             widgetClass.push( klass );
1506     
1507             return klass;
1508         };
1509     
1510         return Widget;
1511     });
1512     /**
1513      * @fileOverview 文件选择相关
1514      */
1515     define('widgets/filepicker',[
1516         'base',
1517         'uploader',
1518         'lib/filepicker',
1519         'widgets/widget'
1520     ], function( Base, Uploader, FilePicker ) {
1521         var $ = Base.$;
1522     
1523         $.extend( Uploader.options, {
1524     
1525             /**
1526              * @property {Selector | Object} [pick=undefined]
1527              * @namespace options
1528              * @for Uploader
1529              * @description 指定选择文件的按钮容器,不指定则不创建按钮。
1530              *
1531              * * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。
1532              * * `label` {String} 请采用 `innerHTML` 代替
1533              * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。
1534              * * `multiple` {Boolean} 是否开起同时选择多个文件能力。
1535              */
1536             pick: null,
1537     
1538             /**
1539              * @property {Arroy} [accept=null]
1540              * @namespace options
1541              * @for Uploader
1542              * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。
1543              *
1544              * * `title` {String} 文字描述
1545              * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。
1546              * * `mimeTypes` {String} 多个用逗号分割。
1547              *
1548              * 如:
1549              *
1550              * ```
1551              * {
1552              *     title: 'Images',
1553              *     extensions: 'gif,jpg,jpeg,bmp,png',
1554              *     mimeTypes: 'image/*'
1555              * }
1556              * ```
1557              */
1558             accept: null/*{
1559                 title: 'Images',
1560                 extensions: 'gif,jpg,jpeg,bmp,png',
1561                 mimeTypes: 'image/*'
1562             }*/
1563         });
1564     
1565         return Uploader.register({
1566             'add-btn': 'addButton',
1567             refresh: 'refresh',
1568             disable: 'disable',
1569             enable: 'enable'
1570         }, {
1571     
1572             init: function( opts ) {
1573                 this.pickers = [];
1574                 return opts.pick && this.addButton( opts.pick );
1575             },
1576     
1577             refresh: function() {
1578                 $.each( this.pickers, function() {
1579                     this.refresh();
1580                 });
1581             },
1582     
1583             /**
1584              * @method addButton
1585              * @for Uploader
1586              * @grammar addButton( pick ) => Promise
1587              * @description
1588              * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。
1589              * @example
1590              * uploader.addButton({
1591              *     id: '#btnContainer',
1592              *     innerHTML: '选择文件'
1593              * });
1594              */
1595             addButton: function( pick ) {
1596                 var me = this,
1597                     opts = me.options,
1598                     accept = opts.accept,
1599                     options, picker, deferred;
1600     
1601                 if ( !pick ) {
1602                     return;
1603                 }
1604     
1605                 deferred = Base.Deferred();
1606                 $.isPlainObject( pick ) || (pick = {
1607                     id: pick
1608                 });
1609     
1610                 options = $.extend({}, pick, {
1611                     accept: $.isPlainObject( accept ) ? [ accept ] : accept,
1612                     swf: opts.swf,
1613                     runtimeOrder: opts.runtimeOrder
1614                 });
1615     
1616                 picker = new FilePicker( options );
1617     
1618                 picker.once( 'ready', deferred.resolve );
1619                 picker.on( 'select', function( files ) {
1620                     me.owner.request( 'add-file', [ files ]);
1621                 });
1622                 picker.init();
1623     
1624                 this.pickers.push( picker );
1625     
1626                 return deferred.promise();
1627             },
1628     
1629             disable: function() {
1630                 $.each( this.pickers, function() {
1631                     this.disable();
1632                 });
1633             },
1634     
1635             enable: function() {
1636                 $.each( this.pickers, function() {
1637                     this.enable();
1638                 });
1639             }
1640         });
1641     });
1642     /**
1643      * @fileOverview Image
1644      */
1645     define('lib/image',[
1646         'base',
1647         'runtime/client',
1648         'lib/blob'
1649     ], function( Base, RuntimeClient, Blob ) {
1650         var $ = Base.$;
1651     
1652         // 构造器。
1653         function Image( opts ) {
1654             this.options = $.extend({}, Image.options, opts );
1655             RuntimeClient.call( this, 'Image' );
1656     
1657             this.on( 'load', function() {
1658                 this._info = this.exec('info');
1659                 this._meta = this.exec('meta');
1660             });
1661         }
1662     
1663         // 默认选项。
1664         Image.options = {
1665     
1666             // 默认的图片处理质量
1667             quality: 90,
1668     
1669             // 是否裁剪
1670             crop: false,
1671     
1672             // 是否保留头部信息
1673             preserveHeaders: true,
1674     
1675             // 是否允许放大。
1676             allowMagnify: true
1677         };
1678     
1679         // 继承RuntimeClient.
1680         Base.inherits( RuntimeClient, {
1681             constructor: Image,
1682     
1683             info: function( val ) {
1684     
1685                 // setter
1686                 if ( val ) {
1687                     this._info = val;
1688                     return this;
1689                 }
1690     
1691                 // getter
1692                 return this._info;
1693             },
1694     
1695             meta: function( val ) {
1696     
1697                 // setter
1698                 if ( val ) {
1699                     this._meta = val;
1700                     return this;
1701                 }
1702     
1703                 // getter
1704                 return this._meta;
1705             },
1706     
1707             loadFromBlob: function( blob ) {
1708                 var me = this,
1709                     ruid = blob.getRuid();
1710     
1711                 this.connectRuntime( ruid, function() {
1712                     me.exec( 'init', me.options );
1713                     me.exec( 'loadFromBlob', blob );
1714                 });
1715             },
1716     
1717             resize: function() {
1718                 var args = Base.slice( arguments );
1719                 return this.exec.apply( this, [ 'resize' ].concat( args ) );
1720             },
1721     
1722             getAsDataUrl: function( type ) {
1723                 return this.exec( 'getAsDataUrl', type );
1724             },
1725     
1726             getAsBlob: function( type ) {
1727                 var blob = this.exec( 'getAsBlob', type );
1728     
1729                 return new Blob( this.getRuid(), blob );
1730             }
1731         });
1732     
1733         return Image;
1734     });
1735     /**
1736      * @fileOverview 图片操作, 负责预览图片和上传前压缩图片
1737      */
1738     define('widgets/image',[
1739         'base',
1740         'uploader',
1741         'lib/image',
1742         'widgets/widget'
1743     ], function( Base, Uploader, Image ) {
1744     
1745         var $ = Base.$,
1746             throttle;
1747     
1748         // 根据要处理的文件大小来节流,一次不能处理太多,会卡。
1749         throttle = (function( max ) {
1750             var occupied = 0,
1751                 waiting = [],
1752                 tick = function() {
1753                     var item;
1754     
1755                     while ( waiting.length && occupied < max ) {
1756                         item = waiting.shift();
1757                         occupied += item[ 0 ];
1758                         item[ 1 ]();
1759                     }
1760                 };
1761     
1762             return function( emiter, size, cb ) {
1763                 waiting.push([ size, cb ]);
1764                 emiter.once( 'destroy', function() {
1765                     occupied -= size;
1766                     setTimeout( tick, 1 );
1767                 });
1768                 setTimeout( tick, 1 );
1769             };
1770         })( 5 * 1024 * 1024 );
1771     
1772         $.extend( Uploader.options, {
1773     
1774             /**
1775              * @property {Object} [thumb]
1776              * @namespace options
1777              * @for Uploader
1778              * @description 配置生成缩略图的选项。
1779              *
1780              * 默认为:
1781              *
1782              * ```javascript
1783              * {
1784              *     width: 110,
1785              *     height: 110,
1786              *
1787              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
1788              *     quality: 70,
1789              *
1790              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
1791              *     allowMagnify: true,
1792              *
1793              *     // 是否允许裁剪。
1794              *     crop: true,
1795              *
1796              *     // 是否保留头部meta信息。
1797              *     preserveHeaders: false,
1798              *
1799              *     // 为空的话则保留原有图片格式。
1800              *     // 否则强制转换成指定的类型。
1801              *     type: 'image/jpeg'
1802              * }
1803              * ```
1804              */
1805             thumb: {
1806                 width: 110,
1807                 height: 110,
1808                 quality: 70,
1809                 allowMagnify: true,
1810                 crop: true,
1811                 preserveHeaders: false,
1812     
1813                 // 为空的话则保留原有图片格式。
1814                 // 否则强制转换成指定的类型。
1815                 // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可
1816                 // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg
1817                 type: 'image/jpeg'
1818             },
1819     
1820             /**
1821              * @property {Object} [compress]
1822              * @namespace options
1823              * @for Uploader
1824              * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。
1825              *
1826              * 默认为:
1827              *
1828              * ```javascript
1829              * {
1830              *     width: 1600,
1831              *     height: 1600,
1832              *
1833              *     // 图片质量,只有type为`image/jpeg`的时候才有效。
1834              *     quality: 90,
1835              *
1836              *     // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false.
1837              *     allowMagnify: false,
1838              *
1839              *     // 是否允许裁剪。
1840              *     crop: false,
1841              *
1842              *     // 是否保留头部meta信息。
1843              *     preserveHeaders: true
1844              * }
1845              * ```
1846              */
1847             compress: {
1848                 width: 1600,
1849                 height: 1600,
1850                 quality: 90,
1851                 allowMagnify: false,
1852                 crop: false,
1853                 preserveHeaders: true
1854             }
1855         });
1856     
1857         return Uploader.register({
1858             'make-thumb': 'makeThumb',
1859             'before-send-file': 'compressImage'
1860         }, {
1861     
1862     
1863             /**
1864              * 生成缩略图,此过程为异步,所以需要传入`callback`。
1865              * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。
1866              *
1867              * `callback`中可以接收到两个参数。
1868              * * 第一个为error,如果生成缩略图有错误,此error将为真。
1869              * * 第二个为ret, 缩略图的Data URL值。
1870              *
1871              * **注意**
1872              * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。
1873              *
1874              *
1875              * @method makeThumb
1876              * @grammar makeThumb( file, callback ) => undefined
1877              * @grammar makeThumb( file, callback, width, height ) => undefined
1878              * @for Uploader
1879              * @example
1880              *
1881              * uploader.on( 'fileQueued', function( file ) {
1882              *     var $li = ...;
1883              *
1884              *     uploader.makeThumb( file, function( error, ret ) {
1885              *         if ( error ) {
1886              *             $li.text('预览错误');
1887              *         } else {
1888              *             $li.append('<img alt="" src="' + ret + '" />');
1889              *         }
1890              *     });
1891              *
1892              * });
1893              */
1894             makeThumb: function( file, cb, width, height ) {
1895                 var opts, image;
1896     
1897                 file = this.request( 'get-file', file );
1898     
1899                 // 只预览图片格式。
1900                 if ( !file.type.match( /^image/ ) ) {
1901                     cb( true );
1902                     return;
1903                 }
1904     
1905                 opts = $.extend({}, this.options.thumb );
1906     
1907                 // 如果传入的是object.
1908                 if ( $.isPlainObject( width ) ) {
1909                     opts = $.extend( opts, width );
1910                     width = null;
1911                 }
1912     
1913                 width = width || opts.width;
1914                 height = height || opts.height;
1915     
1916                 image = new Image( opts );
1917     
1918                 image.once( 'load', function() {
1919                     file._info = file._info || image.info();
1920                     file._meta = file._meta || image.meta();
1921                     image.resize( width, height );
1922                 });
1923     
1924                 image.once( 'complete', function() {
1925                     cb( false, image.getAsDataUrl( opts.type ) );
1926                     image.destroy();
1927                 });
1928     
1929                 image.once( 'error', function() {
1930                     cb( true );
1931                     image.destroy();
1932                 });
1933     
1934                 throttle( image, file.source.size, function() {
1935                     file._info && image.info( file._info );
1936                     file._meta && image.meta( file._meta );
1937                     image.loadFromBlob( file.source );
1938                 });
1939             },
1940     
1941             compressImage: function( file ) {
1942                 var opts = this.options.compress || this.options.resize,
1943                     compressSize = opts && opts.compressSize || 300 * 1024,
1944                     image, deferred;
1945     
1946                 file = this.request( 'get-file', file );
1947     
1948                 // 只预览图片格式。
1949                 if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||
1950                         file.size < compressSize ||
1951                         file._compressed ) {
1952                     return;
1953                 }
1954     
1955                 opts = $.extend({}, opts );
1956                 deferred = Base.Deferred();
1957     
1958                 image = new Image( opts );
1959     
1960                 deferred.always(function() {
1961                     image.destroy();
1962                     image = null;
1963                 });
1964                 image.once( 'error', deferred.reject );
1965                 image.once( 'load', function() {
1966                     file._info = file._info || image.info();
1967                     file._meta = file._meta || image.meta();
1968                     image.resize( opts.width, opts.height );
1969                 });
1970     
1971                 image.once( 'complete', function() {
1972                     var blob, size;
1973     
1974                     // 移动端 UC / qq 浏览器的无图模式下
1975                     // ctx.getImageData 处理大图的时候会报 Exception
1976                     // INDEX_SIZE_ERR: DOM Exception 1
1977                     try {
1978                         blob = image.getAsBlob( opts.type );
1979     
1980                         size = file.size;
1981     
1982                         // 如果压缩后,比原来还大则不用压缩后的。
1983                         if ( blob.size < size ) {
1984                             // file.source.destroy && file.source.destroy();
1985                             file.source = blob;
1986                             file.size = blob.size;
1987     
1988                             file.trigger( 'resize', blob.size, size );
1989                         }
1990     
1991                         // 标记,避免重复压缩。
1992                         file._compressed = true;
1993                         deferred.resolve();
1994                     } catch ( e ) {
1995                         // 出错了直接继续,让其上传原始图片
1996                         deferred.resolve();
1997                     }
1998                 });
1999     
2000                 file._info && image.info( file._info );
2001                 file._meta && image.meta( file._meta );
2002     
2003                 image.loadFromBlob( file.source );
2004                 return deferred.promise();
2005             }
2006         });
2007     });
2008     /**
2009      * @fileOverview 文件属性封装
2010      */
2011     define('file',[
2012         'base',
2013         'mediator'
2014     ], function( Base, Mediator ) {
2015     
2016         var $ = Base.$,
2017             idPrefix = 'WU_FILE_',
2018             idSuffix = 0,
2019             rExt = /\.([^.]+)$/,
2020             statusMap = {};
2021     
2022         function gid() {
2023             return idPrefix + idSuffix++;
2024         }
2025     
2026         /**
2027          * 文件类
2028          * @class File
2029          * @constructor 构造函数
2030          * @grammar new File( source ) => File
2031          * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。
2032          */
2033         function WUFile( source ) {
2034     
2035             /**
2036              * 文件名,包括扩展名(后缀)
2037              * @property name
2038              * @type {string}
2039              */
2040             this.name = source.name || 'Untitled';
2041     
2042             /**
2043              * 文件体积(字节)
2044              * @property size
2045              * @type {uint}
2046              * @default 0
2047              */
2048             this.size = source.size || 0;
2049     
2050             /**
2051              * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)
2052              * @property type
2053              * @type {string}
2054              * @default 'application'
2055              */
2056             this.type = source.type || 'application';
2057     
2058             /**
2059              * 文件最后修改日期
2060              * @property lastModifiedDate
2061              * @type {int}
2062              * @default 当前时间戳
2063              */
2064             this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);
2065     
2066             /**
2067              * 文件ID,每个对象具有唯一ID,与文件名无关
2068              * @property id
2069              * @type {string}
2070              */
2071             this.id = gid();
2072     
2073             /**
2074              * 文件扩展名,通过文件名获取,例如test.png的扩展名为png
2075              * @property ext
2076              * @type {string}
2077              */
2078             this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';
2079     
2080     
2081             /**
2082              * 状态文字说明。在不同的status语境下有不同的用途。
2083              * @property statusText
2084              * @type {string}
2085              */
2086             this.statusText = '';
2087     
2088             // 存储文件状态,防止通过属性直接修改
2089             statusMap[ this.id ] = WUFile.Status.INITED;
2090     
2091             this.source = source;
2092             this.loaded = 0;
2093     
2094             this.on( 'error', function( msg ) {
2095                 this.setStatus( WUFile.Status.ERROR, msg );
2096             });
2097         }
2098     
2099         $.extend( WUFile.prototype, {
2100     
2101             /**
2102              * 设置状态,状态变化时会触发`change`事件。
2103              * @method setStatus
2104              * @grammar setStatus( status[, statusText] );
2105              * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)
2106              * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。
2107              */
2108             setStatus: function( status, text ) {
2109     
2110                 var prevStatus = statusMap[ this.id ];
2111     
2112                 typeof text !== 'undefined' && (this.statusText = text);
2113     
2114                 if ( status !== prevStatus ) {
2115                     statusMap[ this.id ] = status;
2116                     /**
2117                      * 文件状态变化
2118                      * @event statuschange
2119                      */
2120                     this.trigger( 'statuschange', status, prevStatus );
2121                 }
2122     
2123             },
2124     
2125             /**
2126              * 获取文件状态
2127              * @return {File.Status}
2128              * @example
2129                      文件状态具体包括以下几种类型:
2130                      {
2131                          // 初始化
2132                         INITED:     0,
2133                         // 已入队列
2134                         QUEUED:     1,
2135                         // 正在上传
2136                         PROGRESS:     2,
2137                         // 上传出错
2138                         ERROR:         3,
2139                         // 上传成功
2140                         COMPLETE:     4,
2141                         // 上传取消
2142                         CANCELLED:     5
2143                     }
2144              */
2145             getStatus: function() {
2146                 return statusMap[ this.id ];
2147             },
2148     
2149             /**
2150              * 获取文件原始信息。
2151              * @return {*}
2152              */
2153             getSource: function() {
2154                 return this.source;
2155             },
2156     
2157             destory: function() {
2158                 delete statusMap[ this.id ];
2159             }
2160         });
2161     
2162         Mediator.installTo( WUFile.prototype );
2163     
2164         /**
2165          * 文件状态值,具体包括以下几种类型:
2166          * * `inited` 初始状态
2167          * * `queued` 已经进入队列, 等待上传
2168          * * `progress` 上传中
2169          * * `complete` 上传完成。
2170          * * `error` 上传出错,可重试
2171          * * `interrupt` 上传中断,可续传。
2172          * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。
2173          * * `cancelled` 文件被移除。
2174          * @property {Object} Status
2175          * @namespace File
2176          * @class File
2177          * @static
2178          */
2179         WUFile.Status = {
2180             INITED:     'inited',    // 初始状态
2181             QUEUED:     'queued',    // 已经进入队列, 等待上传
2182             PROGRESS:   'progress',    // 上传中
2183             ERROR:      'error',    // 上传出错,可重试
2184             COMPLETE:   'complete',    // 上传完成。
2185             CANCELLED:  'cancelled',    // 上传取消。
2186             INTERRUPT:  'interrupt',    // 上传中断,可续传。
2187             INVALID:    'invalid'    // 文件不合格,不能重试上传。
2188         };
2189     
2190         return WUFile;
2191     });
2192     
2193     /**
2194      * @fileOverview 文件队列
2195      */
2196     define('queue',[
2197         'base',
2198         'mediator',
2199         'file'
2200     ], function( Base, Mediator, WUFile ) {
2201     
2202         var $ = Base.$,
2203             STATUS = WUFile.Status;
2204     
2205         /**
2206          * 文件队列, 用来存储各个状态中的文件。
2207          * @class Queue
2208          * @extends Mediator
2209          */
2210         function Queue() {
2211     
2212             /**
2213              * 统计文件数。
2214              * * `numOfQueue` 队列中的文件数。
2215              * * `numOfSuccess` 上传成功的文件数
2216              * * `numOfCancel` 被移除的文件数
2217              * * `numOfProgress` 正在上传中的文件数
2218              * * `numOfUploadFailed` 上传错误的文件数。
2219              * * `numOfInvalid` 无效的文件数。
2220              * @property {Object} stats
2221              */
2222             this.stats = {
2223                 numOfQueue: 0,
2224                 numOfSuccess: 0,
2225                 numOfCancel: 0,
2226                 numOfProgress: 0,
2227                 numOfUploadFailed: 0,
2228                 numOfInvalid: 0
2229             };
2230     
2231             // 上传队列,仅包括等待上传的文件
2232             this._queue = [];
2233     
2234             // 存储所有文件
2235             this._map = {};
2236         }
2237     
2238         $.extend( Queue.prototype, {
2239     
2240             /**
2241              * 将新文件加入对队列尾部
2242              *
2243              * @method append
2244              * @param  {File} file   文件对象
2245              */
2246             append: function( file ) {
2247                 this._queue.push( file );
2248                 this._fileAdded( file );
2249                 return this;
2250             },
2251     
2252             /**
2253              * 将新文件加入对队列头部
2254              *
2255              * @method prepend
2256              * @param  {File} file   文件对象
2257              */
2258             prepend: function( file ) {
2259                 this._queue.unshift( file );
2260                 this._fileAdded( file );
2261                 return this;
2262             },
2263     
2264             /**
2265              * 获取文件对象
2266              *
2267              * @method getFile
2268              * @param  {String} fileId   文件ID
2269              * @return {File}
2270              */
2271             getFile: function( fileId ) {
2272                 if ( typeof fileId !== 'string' ) {
2273                     return fileId;
2274                 }
2275                 return this._map[ fileId ];
2276             },
2277     
2278             /**
2279              * 从队列中取出一个指定状态的文件。
2280              * @grammar fetch( status ) => File
2281              * @method fetch
2282              * @param {String} status [文件状态值](#WebUploader:File:File.Status)
2283              * @return {File} [File](#WebUploader:File)
2284              */
2285             fetch: function( status ) {
2286                 var len = this._queue.length,
2287                     i, file;
2288     
2289                 status = status || STATUS.QUEUED;
2290     
2291                 for ( i = 0; i < len; i++ ) {
2292                     file = this._queue[ i ];
2293     
2294                     if ( status === file.getStatus() ) {
2295                         return file;
2296                     }
2297                 }
2298     
2299                 return null;
2300             },
2301     
2302             /**
2303              * 对队列进行排序,能够控制文件上传顺序。
2304              * @grammar sort( fn ) => undefined
2305              * @method sort
2306              * @param {Function} fn 排序方法
2307              */
2308             sort: function( fn ) {
2309                 if ( typeof fn === 'function' ) {
2310                     this._queue.sort( fn );
2311                 }
2312             },
2313     
2314             /**
2315              * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。
2316              * @grammar getFiles( [status1[, status2 ...]] ) => Array
2317              * @method getFiles
2318              * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)
2319              */
2320             getFiles: function() {
2321                 var sts = [].slice.call( arguments, 0 ),
2322                     ret = [],
2323                     i = 0,
2324                     len = this._queue.length,
2325                     file;
2326     
2327                 for ( ; i < len; i++ ) {
2328                     file = this._queue[ i ];
2329     
2330                     if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {
2331                         continue;
2332                     }
2333     
2334                     ret.push( file );
2335                 }
2336     
2337                 return ret;
2338             },
2339     
2340             _fileAdded: function( file ) {
2341                 var me = this,
2342                     existing = this._map[ file.id ];
2343     
2344                 if ( !existing ) {
2345                     this._map[ file.id ] = file;
2346     
2347                     file.on( 'statuschange', function( cur, pre ) {
2348                         me._onFileStatusChange( cur, pre );
2349                     });
2350                 }
2351     
2352                 file.setStatus( STATUS.QUEUED );
2353             },
2354     
2355             _onFileStatusChange: function( curStatus, preStatus ) {
2356                 var stats = this.stats;
2357     
2358                 switch ( preStatus ) {
2359                     case STATUS.PROGRESS:
2360                         stats.numOfProgress--;
2361                         break;
2362     
2363                     case STATUS.QUEUED:
2364                         stats.numOfQueue --;
2365                         break;
2366     
2367                     case STATUS.ERROR:
2368                         stats.numOfUploadFailed--;
2369                         break;
2370     
2371                     case STATUS.INVALID:
2372                         stats.numOfInvalid--;
2373                         break;
2374                 }
2375     
2376                 switch ( curStatus ) {
2377                     case STATUS.QUEUED:
2378                         stats.numOfQueue++;
2379                         break;
2380     
2381                     case STATUS.PROGRESS:
2382                         stats.numOfProgress++;
2383                         break;
2384     
2385                     case STATUS.ERROR:
2386                         stats.numOfUploadFailed++;
2387                         break;
2388     
2389                     case STATUS.COMPLETE:
2390                         stats.numOfSuccess++;
2391                         break;
2392     
2393                     case STATUS.CANCELLED:
2394                         stats.numOfCancel++;
2395                         break;
2396     
2397                     case STATUS.INVALID:
2398                         stats.numOfInvalid++;
2399                         break;
2400                 }
2401             }
2402     
2403         });
2404     
2405         Mediator.installTo( Queue.prototype );
2406     
2407         return Queue;
2408     });
2409     /**
2410      * @fileOverview 队列
2411      */
2412     define('widgets/queue',[
2413         'base',
2414         'uploader',
2415         'queue',
2416         'file',
2417         'lib/file',
2418         'runtime/client',
2419         'widgets/widget'
2420     ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {
2421     
2422         var $ = Base.$,
2423             rExt = /\.\w+$/,
2424             Status = WUFile.Status;
2425     
2426         return Uploader.register({
2427             'sort-files': 'sortFiles',
2428             'add-file': 'addFiles',
2429             'get-file': 'getFile',
2430             'fetch-file': 'fetchFile',
2431             'get-stats': 'getStats',
2432             'get-files': 'getFiles',
2433             'remove-file': 'removeFile',
2434             'retry': 'retry',
2435             'reset': 'reset',
2436             'accept-file': 'acceptFile'
2437         }, {
2438     
2439             init: function( opts ) {
2440                 var me = this,
2441                     deferred, len, i, item, arr, accept, runtime;
2442     
2443                 if ( $.isPlainObject( opts.accept ) ) {
2444                     opts.accept = [ opts.accept ];
2445                 }
2446     
2447                 // accept中的中生成匹配正则。
2448                 if ( opts.accept ) {
2449                     arr = [];
2450     
2451                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
2452                         item = opts.accept[ i ].extensions;
2453                         item && arr.push( item );
2454                     }
2455     
2456                     if ( arr.length ) {
2457                         accept = '\\.' + arr.join(',')
2458                                 .replace( /,/g, '$|\\.' )
2459                                 .replace( /\*/g, '.*' ) + '$';
2460                     }
2461     
2462                     me.accept = new RegExp( accept, 'i' );
2463                 }
2464     
2465                 me.queue = new Queue();
2466                 me.stats = me.queue.stats;
2467     
2468                 // 如果当前不是html5运行时,那就算了。
2469                 // 不执行后续操作
2470                 if ( this.request('predict-runtime-type') !== 'html5' ) {
2471                     return;
2472                 }
2473     
2474                 // 创建一个 html5 运行时的 placeholder
2475                 // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。
2476                 deferred = Base.Deferred();
2477                 runtime = new RuntimeClient('Placeholder');
2478                 runtime.connectRuntime({
2479                     runtimeOrder: 'html5'
2480                 }, function() {
2481                     me._ruid = runtime.getRuid();
2482                     deferred.resolve();
2483                 });
2484                 return deferred.promise();
2485             },
2486     
2487     
2488             // 为了支持外部直接添加一个原生File对象。
2489             _wrapFile: function( file ) {
2490                 if ( !(file instanceof WUFile) ) {
2491     
2492                     if ( !(file instanceof File) ) {
2493                         if ( !this._ruid ) {
2494                             throw new Error('Can\'t add external files.');
2495                         }
2496                         file = new File( this._ruid, file );
2497                     }
2498     
2499                     file = new WUFile( file );
2500                 }
2501     
2502                 return file;
2503             },
2504     
2505             // 判断文件是否可以被加入队列
2506             acceptFile: function( file ) {
2507                 var invalid = !file || file.size < 6 || this.accept &&
2508     
2509                         // 如果名字中有后缀,才做后缀白名单处理。
2510                         rExt.exec( file.name ) && !this.accept.test( file.name );
2511     
2512                 return !invalid;
2513             },
2514     
2515     
2516             /**
2517              * @event beforeFileQueued
2518              * @param {File} file File对象
2519              * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。
2520              * @for  Uploader
2521              */
2522     
2523             /**
2524              * @event fileQueued
2525              * @param {File} file File对象
2526              * @description 当文件被加入队列以后触发。
2527              * @for  Uploader
2528              */
2529     
2530             _addFile: function( file ) {
2531                 var me = this;
2532     
2533                 file = me._wrapFile( file );
2534     
2535                 // 不过类型判断允许不允许,先派送 `beforeFileQueued`
2536                 if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {
2537                     return;
2538                 }
2539     
2540                 // 类型不匹配,则派送错误事件,并返回。
2541                 if ( !me.acceptFile( file ) ) {
2542                     me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );
2543                     return;
2544                 }
2545     
2546                 me.queue.append( file );
2547                 me.owner.trigger( 'fileQueued', file );
2548                 return file;
2549             },
2550     
2551             getFile: function( fileId ) {
2552                 return this.queue.getFile( fileId );
2553             },
2554     
2555             /**
2556              * @event filesQueued
2557              * @param {File} files 数组,内容为原始File(lib/File)对象。
2558              * @description 当一批文件添加进队列以后触发。
2559              * @for  Uploader
2560              */
2561     
2562             /**
2563              * @method addFiles
2564              * @grammar addFiles( file ) => undefined
2565              * @grammar addFiles( [file1, file2 ...] ) => undefined
2566              * @param {Array of File or File} [files] Files 对象 数组
2567              * @description 添加文件到队列
2568              * @for  Uploader
2569              */
2570             addFiles: function( files ) {
2571                 var me = this;
2572     
2573                 if ( !files.length ) {
2574                     files = [ files ];
2575                 }
2576     
2577                 files = $.map( files, function( file ) {
2578                     return me._addFile( file );
2579                 });
2580     
2581                 me.owner.trigger( 'filesQueued', files );
2582     
2583                 if ( me.options.auto ) {
2584                     me.request('start-upload');
2585                 }
2586             },
2587     
2588             getStats: function() {
2589                 return this.stats;
2590             },
2591     
2592             /**
2593              * @event fileDequeued
2594              * @param {File} file File对象
2595              * @description 当文件被移除队列后触发。
2596              * @for  Uploader
2597              */
2598     
2599             /**
2600              * @method removeFile
2601              * @grammar removeFile( file ) => undefined
2602              * @grammar removeFile( id ) => undefined
2603              * @param {File|id} file File对象或这File对象的id
2604              * @description 移除某一文件。
2605              * @for  Uploader
2606              * @example
2607              *
2608              * $li.on('click', '.remove-this', function() {
2609              *     uploader.removeFile( file );
2610              * })
2611              */
2612             removeFile: function( file ) {
2613                 var me = this;
2614     
2615                 file = file.id ? file : me.queue.getFile( file );
2616     
2617                 file.setStatus( Status.CANCELLED );
2618                 me.owner.trigger( 'fileDequeued', file );
2619             },
2620     
2621             /**
2622              * @method getFiles
2623              * @grammar getFiles() => Array
2624              * @grammar getFiles( status1, status2, status... ) => Array
2625              * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。
2626              * @for  Uploader
2627              * @example
2628              * console.log( uploader.getFiles() );    // => all files
2629              * console.log( uploader.getFiles('error') )    // => all error files.
2630              */
2631             getFiles: function() {
2632                 return this.queue.getFiles.apply( this.queue, arguments );
2633             },
2634     
2635             fetchFile: function() {
2636                 return this.queue.fetch.apply( this.queue, arguments );
2637             },
2638     
2639             /**
2640              * @method retry
2641              * @grammar retry() => undefined
2642              * @grammar retry( file ) => undefined
2643              * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。
2644              * @for  Uploader
2645              * @example
2646              * function retry() {
2647              *     uploader.retry();
2648              * }
2649              */
2650             retry: function( file, noForceStart ) {
2651                 var me = this,
2652                     files, i, len;
2653     
2654                 if ( file ) {
2655                     file = file.id ? file : me.queue.getFile( file );
2656                     file.setStatus( Status.QUEUED );
2657                     noForceStart || me.request('start-upload');
2658                     return;
2659                 }
2660     
2661                 files = me.queue.getFiles( Status.ERROR );
2662                 i = 0;
2663                 len = files.length;
2664     
2665                 for ( ; i < len; i++ ) {
2666                     file = files[ i ];
2667                     file.setStatus( Status.QUEUED );
2668                 }
2669     
2670                 me.request('start-upload');
2671             },
2672     
2673             /**
2674              * @method sort
2675              * @grammar sort( fn ) => undefined
2676              * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。
2677              * @for  Uploader
2678              */
2679             sortFiles: function() {
2680                 return this.queue.sort.apply( this.queue, arguments );
2681             },
2682     
2683             /**
2684              * @method reset
2685              * @grammar reset() => undefined
2686              * @description 重置uploader。目前只重置了队列。
2687              * @for  Uploader
2688              * @example
2689              * uploader.reset();
2690              */
2691             reset: function() {
2692                 this.queue = new Queue();
2693                 this.stats = this.queue.stats;
2694             }
2695         });
2696     
2697     });
2698     /**
2699      * @fileOverview 添加获取Runtime相关信息的方法。
2700      */
2701     define('widgets/runtime',[
2702         'uploader',
2703         'runtime/runtime',
2704         'widgets/widget'
2705     ], function( Uploader, Runtime ) {
2706     
2707         Uploader.support = function() {
2708             return Runtime.hasRuntime.apply( Runtime, arguments );
2709         };
2710     
2711         return Uploader.register({
2712             'predict-runtime-type': 'predictRuntmeType'
2713         }, {
2714     
2715             init: function() {
2716                 if ( !this.predictRuntmeType() ) {
2717                     throw Error('Runtime Error');
2718                 }
2719             },
2720     
2721             /**
2722              * 预测Uploader将采用哪个`Runtime`
2723              * @grammar predictRuntmeType() => String
2724              * @method predictRuntmeType
2725              * @for  Uploader
2726              */
2727             predictRuntmeType: function() {
2728                 var orders = this.options.runtimeOrder || Runtime.orders,
2729                     type = this.type,
2730                     i, len;
2731     
2732                 if ( !type ) {
2733                     orders = orders.split( /\s*,\s*/g );
2734     
2735                     for ( i = 0, len = orders.length; i < len; i++ ) {
2736                         if ( Runtime.hasRuntime( orders[ i ] ) ) {
2737                             this.type = type = orders[ i ];
2738                             break;
2739                         }
2740                     }
2741                 }
2742     
2743                 return type;
2744             }
2745         });
2746     });
2747     /**
2748      * @fileOverview Transport
2749      */
2750     define('lib/transport',[
2751         'base',
2752         'runtime/client',
2753         'mediator'
2754     ], function( Base, RuntimeClient, Mediator ) {
2755     
2756         var $ = Base.$;
2757     
2758         function Transport( opts ) {
2759             var me = this;
2760     
2761             opts = me.options = $.extend( true, {}, Transport.options, opts || {} );
2762             RuntimeClient.call( this, 'Transport' );
2763     
2764             this._blob = null;
2765             this._formData = opts.formData || {};
2766             this._headers = opts.headers || {};
2767     
2768             this.on( 'progress', this._timeout );
2769             this.on( 'load error', function() {
2770                 me.trigger( 'progress', 1 );
2771                 clearTimeout( me._timer );
2772             });
2773         }
2774     
2775         Transport.options = {
2776             server: '',
2777             method: 'POST',
2778     
2779             // 跨域时,是否允许携带cookie, 只有html5 runtime才有效
2780             withCredentials: false,
2781             fileVal: 'file',
2782             timeout: 2 * 60 * 1000,    // 2分钟
2783             formData: {},
2784             headers: {},
2785             sendAsBinary: false
2786         };
2787     
2788         $.extend( Transport.prototype, {
2789     
2790             // 添加Blob, 只能添加一次,最后一次有效。
2791             appendBlob: function( key, blob, filename ) {
2792                 var me = this,
2793                     opts = me.options;
2794     
2795                 if ( me.getRuid() ) {
2796                     me.disconnectRuntime();
2797                 }
2798     
2799                 // 连接到blob归属的同一个runtime.
2800                 me.connectRuntime( blob.ruid, function() {
2801                     me.exec('init');
2802                 });
2803     
2804                 me._blob = blob;
2805                 opts.fileVal = key || opts.fileVal;
2806                 opts.filename = filename || opts.filename;
2807             },
2808     
2809             // 添加其他字段
2810             append: function( key, value ) {
2811                 if ( typeof key === 'object' ) {
2812                     $.extend( this._formData, key );
2813                 } else {
2814                     this._formData[ key ] = value;
2815                 }
2816             },
2817     
2818             setRequestHeader: function( key, value ) {
2819                 if ( typeof key === 'object' ) {
2820                     $.extend( this._headers, key );
2821                 } else {
2822                     this._headers[ key ] = value;
2823                 }
2824             },
2825     
2826             send: function( method ) {
2827                 this.exec( 'send', method );
2828                 this._timeout();
2829             },
2830     
2831             abort: function() {
2832                 clearTimeout( this._timer );
2833                 return this.exec('abort');
2834             },
2835     
2836             destroy: function() {
2837                 this.trigger('destroy');
2838                 this.off();
2839                 this.exec('destroy');
2840                 this.disconnectRuntime();
2841             },
2842     
2843             getResponse: function() {
2844                 return this.exec('getResponse');
2845             },
2846     
2847             getResponseAsJson: function() {
2848                 return this.exec('getResponseAsJson');
2849             },
2850     
2851             getStatus: function() {
2852                 return this.exec('getStatus');
2853             },
2854     
2855             _timeout: function() {
2856                 var me = this,
2857                     duration = me.options.timeout;
2858     
2859                 if ( !duration ) {
2860                     return;
2861                 }
2862     
2863                 clearTimeout( me._timer );
2864                 me._timer = setTimeout(function() {
2865                     me.abort();
2866                     me.trigger( 'error', 'timeout' );
2867                 }, duration );
2868             }
2869     
2870         });
2871     
2872         // 让Transport具备事件功能。
2873         Mediator.installTo( Transport.prototype );
2874     
2875         return Transport;
2876     });
2877     /**
2878      * @fileOverview 负责文件上传相关。
2879      */
2880     define('widgets/upload',[
2881         'base',
2882         'uploader',
2883         'file',
2884         'lib/transport',
2885         'widgets/widget'
2886     ], function( Base, Uploader, WUFile, Transport ) {
2887     
2888         var $ = Base.$,
2889             isPromise = Base.isPromise,
2890             Status = WUFile.Status;
2891     
2892         // 添加默认配置项
2893         $.extend( Uploader.options, {
2894     
2895     
2896             /**
2897              * @property {Boolean} [prepareNextFile=false]
2898              * @namespace options
2899              * @for Uploader
2900              * @description 是否允许在文件传输时提前把下一个文件准备好。
2901              * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。
2902              * 如果能提前在当前文件传输期处理,可以节省总体耗时。
2903              */
2904             prepareNextFile: false,
2905     
2906             /**
2907              * @property {Boolean} [chunked=false]
2908              * @namespace options
2909              * @for Uploader
2910              * @description 是否要分片处理大文件上传。
2911              */
2912             chunked: false,
2913     
2914             /**
2915              * @property {Boolean} [chunkSize=5242880]
2916              * @namespace options
2917              * @for Uploader
2918              * @description 如果要分片,分多大一片? 默认大小为5M.
2919              */
2920             chunkSize: 5 * 1024 * 1024,
2921     
2922             /**
2923              * @property {Boolean} [chunkRetry=2]
2924              * @namespace options
2925              * @for Uploader
2926              * @description 如果某个分片由于网络问题出错,允许自动重传多少次?
2927              */
2928             chunkRetry: 2,
2929     
2930             /**
2931              * @property {Boolean} [threads=3]
2932              * @namespace options
2933              * @for Uploader
2934              * @description 上传并发数。允许同时最大上传进程数。
2935              */
2936             threads: 3,
2937     
2938     
2939             /**
2940              * @property {Object} [formData]
2941              * @namespace options
2942              * @for Uploader
2943              * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。
2944              */
2945             formData: null
2946     
2947             /**
2948              * @property {Object} [fileVal='file']
2949              * @namespace options
2950              * @for Uploader
2951              * @description 设置文件上传域的name。
2952              */
2953     
2954             /**
2955              * @property {Object} [method='POST']
2956              * @namespace options
2957              * @for Uploader
2958              * @description 文件上传方式,`POST`或者`GET`。
2959              */
2960     
2961             /**
2962              * @property {Object} [sendAsBinary=false]
2963              * @namespace options
2964              * @for Uploader
2965              * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容,
2966              * 其他参数在$_GET数组中。
2967              */
2968         });
2969     
2970         // 负责将文件切片。
2971         function CuteFile( file, chunkSize ) {
2972             var pending = [],
2973                 blob = file.source,
2974                 total = blob.size,
2975                 chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,
2976                 start = 0,
2977                 index = 0,
2978                 len;
2979     
2980             while ( index < chunks ) {
2981                 len = Math.min( chunkSize, total - start );
2982     
2983                 pending.push({
2984                     file: file,
2985                     start: start,
2986                     end: chunkSize ? (start + len) : total,
2987                     total: total,
2988                     chunks: chunks,
2989                     chunk: index++
2990                 });
2991                 start += len;
2992             }
2993     
2994             file.blocks = pending.concat();
2995             file.remaning = pending.length;
2996     
2997             return {
2998                 file: file,
2999     
3000                 has: function() {
3001                     return !!pending.length;
3002                 },
3003     
3004                 fetch: function() {
3005                     return pending.shift();
3006                 }
3007             };
3008         }
3009     
3010         Uploader.register({
3011             'start-upload': 'start',
3012             'stop-upload': 'stop',
3013             'skip-file': 'skipFile',
3014             'is-in-progress': 'isInProgress'
3015         }, {
3016     
3017             init: function() {
3018                 var owner = this.owner;
3019     
3020                 this.runing = false;
3021     
3022                 // 记录当前正在传的数据,跟threads相关
3023                 this.pool = [];
3024     
3025                 // 缓存即将上传的文件。
3026                 this.pending = [];
3027     
3028                 // 跟踪还有多少分片没有完成上传。
3029                 this.remaning = 0;
3030                 this.__tick = Base.bindFn( this._tick, this );
3031     
3032                 owner.on( 'uploadComplete', function( file ) {
3033                     // 把其他块取消了。
3034                     file.blocks && $.each( file.blocks, function( _, v ) {
3035                         v.transport && (v.transport.abort(), v.transport.destroy());
3036                         delete v.transport;
3037                     });
3038     
3039                     delete file.blocks;
3040                     delete file.remaning;
3041                 });
3042             },
3043     
3044             /**
3045              * @event startUpload
3046              * @description 当开始上传流程时触发。
3047              * @for  Uploader
3048              */
3049     
3050             /**
3051              * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。
3052              * @grammar upload() => undefined
3053              * @method upload
3054              * @for  Uploader
3055              */
3056             start: function() {
3057                 var me = this;
3058     
3059                 // 移出invalid的文件
3060                 $.each( me.request( 'get-files', Status.INVALID ), function() {
3061                     me.request( 'remove-file', this );
3062                 });
3063     
3064                 if ( me.runing ) {
3065                     return;
3066                 }
3067     
3068                 me.runing = true;
3069     
3070                 // 如果有暂停的,则续传
3071                 $.each( me.pool, function( _, v ) {
3072                     var file = v.file;
3073     
3074                     if ( file.getStatus() === Status.INTERRUPT ) {
3075                         file.setStatus( Status.PROGRESS );
3076                         me._trigged = false;
3077                         v.transport && v.transport.send();
3078                     }
3079                 });
3080     
3081                 me._trigged = false;
3082                 me.owner.trigger('startUpload');
3083                 Base.nextTick( me.__tick );
3084             },
3085     
3086             /**
3087              * @event stopUpload
3088              * @description 当开始上传流程暂停时触发。
3089              * @for  Uploader
3090              */
3091     
3092             /**
3093              * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。
3094              * @grammar stop() => undefined
3095              * @grammar stop( true ) => undefined
3096              * @method stop
3097              * @for  Uploader
3098              */
3099             stop: function( interrupt ) {
3100                 var me = this;
3101     
3102                 if ( me.runing === false ) {
3103                     return;
3104                 }
3105     
3106                 me.runing = false;
3107     
3108                 interrupt && $.each( me.pool, function( _, v ) {
3109                     v.transport && v.transport.abort();
3110                     v.file.setStatus( Status.INTERRUPT );
3111                 });
3112     
3113                 me.owner.trigger('stopUpload');
3114             },
3115     
3116             /**
3117              * 判断`Uplaode`r是否正在上传中。
3118              * @grammar isInProgress() => Boolean
3119              * @method isInProgress
3120              * @for  Uploader
3121              */
3122             isInProgress: function() {
3123                 return !!this.runing;
3124             },
3125     
3126             getStats: function() {
3127                 return this.request('get-stats');
3128             },
3129     
3130             /**
3131              * 掉过一个文件上传,直接标记指定文件为已上传状态。
3132              * @grammar skipFile( file ) => undefined
3133              * @method skipFile
3134              * @for  Uploader
3135              */
3136             skipFile: function( file, status ) {
3137                 file = this.request( 'get-file', file );
3138     
3139                 file.setStatus( status || Status.COMPLETE );
3140                 file.skipped = true;
3141     
3142                 // 如果正在上传。
3143                 file.blocks && $.each( file.blocks, function( _, v ) {
3144                     var _tr = v.transport;
3145     
3146                     if ( _tr ) {
3147                         _tr.abort();
3148                         _tr.destroy();
3149                         delete v.transport;
3150                     }
3151                 });
3152     
3153                 this.owner.trigger( 'uploadSkip', file );
3154             },
3155     
3156             /**
3157              * @event uploadFinished
3158              * @description 当所有文件上传结束时触发。
3159              * @for  Uploader
3160              */
3161             _tick: function() {
3162                 var me = this,
3163                     opts = me.options,
3164                     fn, val;
3165     
3166                 // 上一个promise还没有结束,则等待完成后再执行。
3167                 if ( me._promise ) {
3168                     return me._promise.always( me.__tick );
3169                 }
3170     
3171                 // 还有位置,且还有文件要处理的话。
3172                 if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {
3173                     me._trigged = false;
3174     
3175                     fn = function( val ) {
3176                         me._promise = null;
3177     
3178                         // 有可能是reject过来的,所以要检测val的类型。
3179                         val && val.file && me._startSend( val );
3180                         Base.nextTick( me.__tick );
3181                     };
3182     
3183                     me._promise = isPromise( val ) ? val.always( fn ) : fn( val );
3184     
3185                 // 没有要上传的了,且没有正在传输的了。
3186                 } else if ( !me.remaning && !me.getStats().numOfQueue ) {
3187                     me.runing = false;
3188     
3189                     me._trigged || Base.nextTick(function() {
3190                         me.owner.trigger('uploadFinished');
3191                     });
3192                     me._trigged = true;
3193                 }
3194             },
3195     
3196             _nextBlock: function() {
3197                 var me = this,
3198                     act = me._act,
3199                     opts = me.options,
3200                     next, done;
3201     
3202                 // 如果当前文件还有没有需要传输的,则直接返回剩下的。
3203                 if ( act && act.has() &&
3204                         act.file.getStatus() === Status.PROGRESS ) {
3205     
3206                     // 是否提前准备下一个文件
3207                     if ( opts.prepareNextFile && !me.pending.length ) {
3208                         me._prepareNextFile();
3209                     }
3210     
3211                     return act.fetch();
3212     
3213                 // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。
3214                 } else if ( me.runing ) {
3215     
3216                     // 如果缓存中有,则直接在缓存中取,没有则去queue中取。
3217                     if ( !me.pending.length && me.getStats().numOfQueue ) {
3218                         me._prepareNextFile();
3219                     }
3220     
3221                     next = me.pending.shift();
3222                     done = function( file ) {
3223                         if ( !file ) {
3224                             return null;
3225                         }
3226     
3227                         act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );
3228                         me._act = act;
3229                         return act.fetch();
3230                     };
3231     
3232                     // 文件可能还在prepare中,也有可能已经完全准备好了。
3233                     return isPromise( next ) ?
3234                             next[ next.pipe ? 'pipe' : 'then']( done ) :
3235                             done( next );
3236                 }
3237             },
3238     
3239     
3240             /**
3241              * @event uploadStart
3242              * @param {File} file File对象
3243              * @description 某个文件开始上传前触发,一个文件只会触发一次。
3244              * @for  Uploader
3245              */
3246             _prepareNextFile: function() {
3247                 var me = this,
3248                     file = me.request('fetch-file'),
3249                     pending = me.pending,
3250                     promise;
3251     
3252                 if ( file ) {
3253                     promise = me.request( 'before-send-file', file, function() {
3254     
3255                         // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued.
3256                         if ( file.getStatus() === Status.QUEUED ) {
3257                             me.owner.trigger( 'uploadStart', file );
3258                             file.setStatus( Status.PROGRESS );
3259                             return file;
3260                         }
3261     
3262                         return me._finishFile( file );
3263                     });
3264     
3265                     // 如果还在pending中,则替换成文件本身。
3266                     promise.done(function() {
3267                         var idx = $.inArray( promise, pending );
3268     
3269                         ~idx && pending.splice( idx, 1, file );
3270                     });
3271     
3272                     // befeore-send-file的钩子就有错误发生。
3273                     promise.fail(function( reason ) {
3274                         file.setStatus( Status.ERROR, reason );
3275                         me.owner.trigger( 'uploadError', file, reason );
3276                         me.owner.trigger( 'uploadComplete', file );
3277                     });
3278     
3279                     pending.push( promise );
3280                 }
3281             },
3282     
3283             // 让出位置了,可以让其他分片开始上传
3284             _popBlock: function( block ) {
3285                 var idx = $.inArray( block, this.pool );
3286     
3287                 this.pool.splice( idx, 1 );
3288                 block.file.remaning--;
3289                 this.remaning--;
3290             },
3291     
3292             // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。
3293             _startSend: function( block ) {
3294                 var me = this,
3295                     file = block.file,
3296                     promise;
3297     
3298                 me.pool.push( block );
3299                 me.remaning++;
3300     
3301                 // 如果没有分片,则直接使用原始的。
3302                 // 不会丢失content-type信息。
3303                 block.blob = block.chunks === 1 ? file.source :
3304                         file.source.slice( block.start, block.end );
3305     
3306                 // hook, 每个分片发送之前可能要做些异步的事情。
3307                 promise = me.request( 'before-send', block, function() {
3308     
3309                     // 有可能文件已经上传出错了,所以不需要再传输了。
3310                     if ( file.getStatus() === Status.PROGRESS ) {
3311                         me._doSend( block );
3312                     } else {
3313                         me._popBlock( block );
3314                         Base.nextTick( me.__tick );
3315                     }
3316                 });
3317     
3318                 // 如果为fail了,则跳过此分片。
3319                 promise.fail(function() {
3320                     if ( file.remaning === 1 ) {
3321                         me._finishFile( file ).always(function() {
3322                             block.percentage = 1;
3323                             me._popBlock( block );
3324                             me.owner.trigger( 'uploadComplete', file );
3325                             Base.nextTick( me.__tick );
3326                         });
3327                     } else {
3328                         block.percentage = 1;
3329                         me._popBlock( block );
3330                         Base.nextTick( me.__tick );
3331                     }
3332                 });
3333             },
3334     
3335     
3336             /**
3337              * @event uploadBeforeSend
3338              * @param {Object} object
3339              * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。
3340              * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。
3341              * @for  Uploader
3342              */
3343     
3344             /**
3345              * @event uploadAccept
3346              * @param {Object} object
3347              * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。
3348              * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。
3349              * @for  Uploader
3350              */
3351     
3352             /**
3353              * @event uploadProgress
3354              * @param {File} file File对象
3355              * @param {Number} percentage 上传进度
3356              * @description 上传过程中触发,携带上传进度。
3357              * @for  Uploader
3358              */
3359     
3360     
3361             /**
3362              * @event uploadError
3363              * @param {File} file File对象
3364              * @param {String} reason 出错的code
3365              * @description 当文件上传出错时触发。
3366              * @for  Uploader
3367              */
3368     
3369             /**
3370              * @event uploadSuccess
3371              * @param {File} file File对象
3372              * @param {Object} response 服务端返回的数据
3373              * @description 当文件上传成功时触发。
3374              * @for  Uploader
3375              */
3376     
3377             /**
3378              * @event uploadComplete
3379              * @param {File} [file] File对象
3380              * @description 不管成功或者失败,文件上传完成时触发。
3381              * @for  Uploader
3382              */
3383     
3384             // 做上传操作。
3385             _doSend: function( block ) {
3386                 var me = this,
3387                     owner = me.owner,
3388                     opts = me.options,
3389                     file = block.file,
3390                     tr = new Transport( opts ),
3391                     data = $.extend({}, opts.formData ),
3392                     headers = $.extend({}, opts.headers ),
3393                     requestAccept, ret;
3394     
3395                 block.transport = tr;
3396     
3397                 tr.on( 'destroy', function() {
3398                     delete block.transport;
3399                     me._popBlock( block );
3400                     Base.nextTick( me.__tick );
3401                 });
3402     
3403                 // 广播上传进度。以文件为单位。
3404                 tr.on( 'progress', function( percentage ) {
3405                     var totalPercent = 0,
3406                         uploaded = 0;
3407     
3408                     // 可能没有abort掉,progress还是执行进来了。
3409                     // if ( !file.blocks ) {
3410                     //     return;
3411                     // }
3412     
3413                     totalPercent = block.percentage = percentage;
3414     
3415                     if ( block.chunks > 1 ) {    // 计算文件的整体速度。
3416                         $.each( file.blocks, function( _, v ) {
3417                             uploaded += (v.percentage || 0) * (v.end - v.start);
3418                         });
3419     
3420                         totalPercent = uploaded / file.size;
3421                     }
3422     
3423                     owner.trigger( 'uploadProgress', file, totalPercent || 0 );
3424                 });
3425     
3426                 // 用来询问,是否返回的结果是有错误的。
3427                 requestAccept = function( reject ) {
3428                     var fn;
3429     
3430                     ret = tr.getResponseAsJson() || {};
3431                     ret._raw = tr.getResponse();
3432                     fn = function( value ) {
3433                         reject = value;
3434                     };
3435     
3436                     // 服务端响应了,不代表成功了,询问是否响应正确。
3437                     if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {
3438                         reject = reject || 'server';
3439                     }
3440     
3441                     return reject;
3442                 };
3443     
3444                 // 尝试重试,然后广播文件上传出错。
3445                 tr.on( 'error', function( type, flag ) {
3446                     block.retried = block.retried || 0;
3447     
3448                     // 自动重试
3449                     if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&
3450                             block.retried < opts.chunkRetry ) {
3451     
3452                         block.retried++;
3453                         tr.send();
3454     
3455                     } else {
3456     
3457                         // http status 500 ~ 600
3458                         if ( !flag && type === 'server' ) {
3459                             type = requestAccept( type );
3460                         }
3461     
3462                         file.setStatus( Status.ERROR, type );
3463                         owner.trigger( 'uploadError', file, type );
3464                         owner.trigger( 'uploadComplete', file );
3465                     }
3466                 });
3467     
3468                 // 上传成功
3469                 tr.on( 'load', function() {
3470                     var reason;
3471     
3472                     // 如果非预期,转向上传出错。
3473                     if ( (reason = requestAccept()) ) {
3474                         tr.trigger( 'error', reason, true );
3475                         return;
3476                     }
3477     
3478                     // 全部上传完成。
3479                     if ( file.remaning === 1 ) {
3480                         me._finishFile( file, ret );
3481                     } else {
3482                         tr.destroy();
3483                     }
3484                 });
3485     
3486                 // 配置默认的上传字段。
3487                 data = $.extend( data, {
3488                     id: file.id,
3489                     name: file.name,
3490                     type: file.type,
3491                     lastModifiedDate: file.lastModifiedDate,
3492                     size: file.size
3493                 });
3494     
3495                 block.chunks > 1 && $.extend( data, {
3496                     chunks: block.chunks,
3497                     chunk: block.chunk
3498                 });
3499     
3500                 // 在发送之间可以添加字段什么的。。。
3501                 // 如果默认的字段不够使用,可以通过监听此事件来扩展
3502                 owner.trigger( 'uploadBeforeSend', block, data, headers );
3503     
3504                 // 开始发送。
3505                 tr.appendBlob( opts.fileVal, block.blob, file.name );
3506                 tr.append( data );
3507                 tr.setRequestHeader( headers );
3508                 tr.send();
3509             },
3510     
3511             // 完成上传。
3512             _finishFile: function( file, ret, hds ) {
3513                 var owner = this.owner;
3514     
3515                 return owner
3516                         .request( 'after-send-file', arguments, function() {
3517                             file.setStatus( Status.COMPLETE );
3518                             owner.trigger( 'uploadSuccess', file, ret, hds );
3519                         })
3520                         .fail(function( reason ) {
3521     
3522                             // 如果外部已经标记为invalid什么的,不再改状态。
3523                             if ( file.getStatus() === Status.PROGRESS ) {
3524                                 file.setStatus( Status.ERROR, reason );
3525                             }
3526     
3527                             owner.trigger( 'uploadError', file, reason );
3528                         })
3529                         .always(function() {
3530                             owner.trigger( 'uploadComplete', file );
3531                         });
3532             }
3533     
3534         });
3535     });
3536     /**
3537      * @fileOverview Runtime管理器,负责Runtime的选择, 连接
3538      */
3539     define('runtime/compbase',[],function() {
3540     
3541         function CompBase( owner, runtime ) {
3542     
3543             this.owner = owner;
3544             this.options = owner.options;
3545     
3546             this.getRuntime = function() {
3547                 return runtime;
3548             };
3549     
3550             this.getRuid = function() {
3551                 return runtime.uid;
3552             };
3553     
3554             this.trigger = function() {
3555                 return owner.trigger.apply( owner, arguments );
3556             };
3557         }
3558     
3559         return CompBase;
3560     });
3561     /**
3562      * @fileOverview Html5Runtime
3563      */
3564     define('runtime/html5/runtime',[
3565         'base',
3566         'runtime/runtime',
3567         'runtime/compbase'
3568     ], function( Base, Runtime, CompBase ) {
3569     
3570         var type = 'html5',
3571             components = {};
3572     
3573         function Html5Runtime() {
3574             var pool = {},
3575                 me = this,
3576                 destory = this.destory;
3577     
3578             Runtime.apply( me, arguments );
3579             me.type = type;
3580     
3581     
3582             // 这个方法的调用者,实际上是RuntimeClient
3583             me.exec = function( comp, fn/*, args...*/) {
3584                 var client = this,
3585                     uid = client.uid,
3586                     args = Base.slice( arguments, 2 ),
3587                     instance;
3588     
3589                 if ( components[ comp ] ) {
3590                     instance = pool[ uid ] = pool[ uid ] ||
3591                             new components[ comp ]( client, me );
3592     
3593                     if ( instance[ fn ] ) {
3594                         return instance[ fn ].apply( instance, args );
3595                     }
3596                 }
3597             };
3598     
3599             me.destory = function() {
3600                 // @todo 删除池子中的所有实例
3601                 return destory && destory.apply( this, arguments );
3602             };
3603         }
3604     
3605         Base.inherits( Runtime, {
3606             constructor: Html5Runtime,
3607     
3608             // 不需要连接其他程序,直接执行callback
3609             init: function() {
3610                 var me = this;
3611                 setTimeout(function() {
3612                     me.trigger('ready');
3613                 }, 1 );
3614             }
3615     
3616         });
3617     
3618         // 注册Components
3619         Html5Runtime.register = function( name, component ) {
3620             var klass = components[ name ] = Base.inherits( CompBase, component );
3621             return klass;
3622         };
3623     
3624         // 注册html5运行时。
3625         // 只有在支持的前提下注册。
3626         if ( window.Blob && window.FileReader && window.DataView ) {
3627             Runtime.addRuntime( type, Html5Runtime );
3628         }
3629     
3630         return Html5Runtime;
3631     });
3632     /**
3633      * @fileOverview Blob Html实现
3634      */
3635     define('runtime/html5/blob',[
3636         'runtime/html5/runtime',
3637         'lib/blob'
3638     ], function( Html5Runtime, Blob ) {
3639     
3640         return Html5Runtime.register( 'Blob', {
3641             slice: function( start, end ) {
3642                 var blob = this.owner.source,
3643                     slice = blob.slice || blob.webkitSlice || blob.mozSlice;
3644     
3645                 blob = slice.call( blob, start, end );
3646     
3647                 return new Blob( this.getRuid(), blob );
3648             }
3649         });
3650     });
3651     /**
3652      * @fileOverview FilePicker
3653      */
3654     define('runtime/html5/filepicker',[
3655         'base',
3656         'runtime/html5/runtime'
3657     ], function( Base, Html5Runtime ) {
3658     
3659         var $ = Base.$;
3660     
3661         return Html5Runtime.register( 'FilePicker', {
3662             init: function() {
3663                 var container = this.getRuntime().getContainer(),
3664                     me = this,
3665                     owner = me.owner,
3666                     opts = me.options,
3667                     lable = $( document.createElement('label') ),
3668                     input = $( document.createElement('input') ),
3669                     arr, i, len, mouseHandler;
3670     
3671                 input.attr( 'type', 'file' );
3672                 input.attr( 'name', opts.name );
3673                 input.addClass('webuploader-element-invisible');
3674     
3675                 lable.on( 'click', function() {
3676                     input.trigger('click');
3677                 });
3678     
3679                 lable.css({
3680                     opacity: 0,
3681                     width: '100%',
3682                     height: '100%',
3683                     display: 'block',
3684                     cursor: 'pointer',
3685                     background: '#ffffff'
3686                 });
3687     
3688                 if ( opts.multiple ) {
3689                     input.attr( 'multiple', 'multiple' );
3690                 }
3691     
3692                 // @todo Firefox不支持单独指定后缀
3693                 if ( opts.accept && opts.accept.length > 0 ) {
3694                     arr = [];
3695     
3696                     for ( i = 0, len = opts.accept.length; i < len; i++ ) {
3697                         arr.push( opts.accept[ i ].mimeTypes );
3698                     }
3699     
3700                     input.attr( 'accept', arr.join(',') );
3701                 }
3702     
3703                 container.append( input );
3704                 container.append( lable );
3705     
3706                 mouseHandler = function( e ) {
3707                     owner.trigger( e.type );
3708                 };
3709     
3710                 input.on( 'change', function( e ) {
3711                     var fn = arguments.callee,
3712                         clone;
3713     
3714                     me.files = e.target.files;
3715     
3716                     // reset input
3717                     clone = this.cloneNode( true );
3718                     this.parentNode.replaceChild( clone, this );
3719     
3720                     input.off();
3721                     input = $( clone ).on( 'change', fn )
3722                             .on( 'mouseenter mouseleave', mouseHandler );
3723     
3724                     owner.trigger('change');
3725                 });
3726     
3727                 lable.on( 'mouseenter mouseleave', mouseHandler );
3728     
3729             },
3730     
3731     
3732             getFiles: function() {
3733                 return this.files;
3734             },
3735     
3736             destroy: function() {
3737                 // todo
3738             }
3739         });
3740     });
3741     /**
3742      * Terms:
3743      *
3744      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
3745      * @fileOverview Image控件
3746      */
3747     define('runtime/html5/util',[
3748         'base'
3749     ], function( Base ) {
3750     
3751         var urlAPI = window.createObjectURL && window ||
3752                 window.URL && URL.revokeObjectURL && URL ||
3753                 window.webkitURL,
3754             createObjectURL = Base.noop,
3755             revokeObjectURL = createObjectURL;
3756     
3757         if ( urlAPI ) {
3758     
3759             // 更安全的方式调用,比如android里面就能把context改成其他的对象。
3760             createObjectURL = function() {
3761                 return urlAPI.createObjectURL.apply( urlAPI, arguments );
3762             };
3763     
3764             revokeObjectURL = function() {
3765                 return urlAPI.revokeObjectURL.apply( urlAPI, arguments );
3766             };
3767         }
3768     
3769         return {
3770             createObjectURL: createObjectURL,
3771             revokeObjectURL: revokeObjectURL,
3772     
3773             dataURL2Blob: function( dataURI ) {
3774                 var byteStr, intArray, ab, i, mimetype, parts;
3775     
3776                 parts = dataURI.split(',');
3777     
3778                 if ( ~parts[ 0 ].indexOf('base64') ) {
3779                     byteStr = atob( parts[ 1 ] );
3780                 } else {
3781                     byteStr = decodeURIComponent( parts[ 1 ] );
3782                 }
3783     
3784                 ab = new ArrayBuffer( byteStr.length );
3785                 intArray = new Uint8Array( ab );
3786     
3787                 for ( i = 0; i < byteStr.length; i++ ) {
3788                     intArray[ i ] = byteStr.charCodeAt( i );
3789                 }
3790     
3791                 mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];
3792     
3793                 return this.arrayBufferToBlob( ab, mimetype );
3794             },
3795     
3796             dataURL2ArrayBuffer: function( dataURI ) {
3797                 var byteStr, intArray, i, parts;
3798     
3799                 parts = dataURI.split(',');
3800     
3801                 if ( ~parts[ 0 ].indexOf('base64') ) {
3802                     byteStr = atob( parts[ 1 ] );
3803                 } else {
3804                     byteStr = decodeURIComponent( parts[ 1 ] );
3805                 }
3806     
3807                 intArray = new Uint8Array( byteStr.length );
3808     
3809                 for ( i = 0; i < byteStr.length; i++ ) {
3810                     intArray[ i ] = byteStr.charCodeAt( i );
3811                 }
3812     
3813                 return intArray.buffer;
3814             },
3815     
3816             arrayBufferToBlob: function( buffer, type ) {
3817                 var builder = window.BlobBuilder || window.WebKitBlobBuilder,
3818                     bb;
3819     
3820                 // android不支持直接new Blob, 只能借助blobbuilder.
3821                 if ( builder ) {
3822                     bb = new builder();
3823                     bb.append( buffer );
3824                     return bb.getBlob( type );
3825                 }
3826     
3827                 return new Blob([ buffer ], type ? { type: type } : {} );
3828             },
3829     
3830             // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.
3831             // 你得到的结果是png.
3832             canvasToDataUrl: function( canvas, type, quality ) {
3833                 return canvas.toDataURL( type, quality / 100 );
3834             },
3835     
3836             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
3837             parseMeta: function( blob, callback ) {
3838                 callback( false, {});
3839             },
3840     
3841             // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。
3842             updateImageHead: function( data ) {
3843                 return data;
3844             }
3845         };
3846     });
3847     /**
3848      * Terms:
3849      *
3850      * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer
3851      * @fileOverview Image控件
3852      */
3853     define('runtime/html5/imagemeta',[
3854         'runtime/html5/util'
3855     ], function( Util ) {
3856     
3857         var api;
3858     
3859         api = {
3860             parsers: {
3861                 0xffe1: []
3862             },
3863     
3864             maxMetaDataSize: 262144,
3865     
3866             parse: function( blob, cb ) {
3867                 var me = this,
3868                     fr = new FileReader();
3869     
3870                 fr.onload = function() {
3871                     cb( false, me._parse( this.result ) );
3872                     fr = fr.onload = fr.onerror = null;
3873                 };
3874     
3875                 fr.onerror = function( e ) {
3876                     cb( e.message );
3877                     fr = fr.onload = fr.onerror = null;
3878                 };
3879     
3880                 blob = blob.slice( 0, me.maxMetaDataSize );
3881                 fr.readAsArrayBuffer( blob.getSource() );
3882             },
3883     
3884             _parse: function( buffer, noParse ) {
3885                 if ( buffer.byteLength < 6 ) {
3886                     return;
3887                 }
3888     
3889                 var dataview = new DataView( buffer ),
3890                     offset = 2,
3891                     maxOffset = dataview.byteLength - 4,
3892                     headLength = offset,
3893                     ret = {},
3894                     markerBytes, markerLength, parsers, i;
3895     
3896                 if ( dataview.getUint16( 0 ) === 0xffd8 ) {
3897     
3898                     while ( offset < maxOffset ) {
3899                         markerBytes = dataview.getUint16( offset );
3900     
3901                         if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||
3902                                 markerBytes === 0xfffe ) {
3903     
3904                             markerLength = dataview.getUint16( offset + 2 ) + 2;
3905     
3906                             if ( offset + markerLength > dataview.byteLength ) {
3907                                 break;
3908                             }
3909     
3910                             parsers = api.parsers[ markerBytes ];
3911     
3912                             if ( !noParse && parsers ) {
3913                                 for ( i = 0; i < parsers.length; i += 1 ) {
3914                                     parsers[ i ].call( api, dataview, offset,
3915                                             markerLength, ret );
3916                                 }
3917                             }
3918     
3919                             offset += markerLength;
3920                             headLength = offset;
3921                         } else {
3922                             break;
3923                         }
3924                     }
3925     
3926                     if ( headLength > 6 ) {
3927                         if ( buffer.slice ) {
3928                             ret.imageHead = buffer.slice( 2, headLength );
3929                         } else {
3930                             // Workaround for IE10, which does not yet
3931                             // support ArrayBuffer.slice:
3932                             ret.imageHead = new Uint8Array( buffer )
3933                                     .subarray( 2, headLength );
3934                         }
3935                     }
3936                 }
3937     
3938                 return ret;
3939             },
3940     
3941             updateImageHead: function( buffer, head ) {
3942                 var data = this._parse( buffer, true ),
3943                     buf1, buf2, bodyoffset;
3944     
3945     
3946                 bodyoffset = 2;
3947                 if ( data.imageHead ) {
3948                     bodyoffset = 2 + data.imageHead.byteLength;
3949                 }
3950     
3951                 if ( buffer.slice ) {
3952                     buf2 = buffer.slice( bodyoffset );
3953                 } else {
3954                     buf2 = new Uint8Array( buffer ).subarray( bodyoffset );
3955                 }
3956     
3957                 buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );
3958     
3959                 buf1[ 0 ] = 0xFF;
3960                 buf1[ 1 ] = 0xD8;
3961                 buf1.set( new Uint8Array( head ), 2 );
3962                 buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );
3963     
3964                 return buf1.buffer;
3965             }
3966         };
3967     
3968         Util.parseMeta = function() {
3969             return api.parse.apply( api, arguments );
3970         };
3971     
3972         Util.updateImageHead = function() {
3973             return api.updateImageHead.apply( api, arguments );
3974         };
3975     
3976         return api;
3977     });
3978     /**
3979      * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image
3980      * 暂时项目中只用了orientation.
3981      *
3982      * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.
3983      * @fileOverview EXIF解析
3984      */
3985     
3986     // Sample
3987     // ====================================
3988     // Make : Apple
3989     // Model : iPhone 4S
3990     // Orientation : 1
3991     // XResolution : 72 [72/1]
3992     // YResolution : 72 [72/1]
3993     // ResolutionUnit : 2
3994     // Software : QuickTime 7.7.1
3995     // DateTime : 2013:09:01 22:53:55
3996     // ExifIFDPointer : 190
3997     // ExposureTime : 0.058823529411764705 [1/17]
3998     // FNumber : 2.4 [12/5]
3999     // ExposureProgram : Normal program
4000     // ISOSpeedRatings : 800
4001     // ExifVersion : 0220
4002     // DateTimeOriginal : 2013:09:01 22:52:51
4003     // DateTimeDigitized : 2013:09:01 22:52:51
4004     // ComponentsConfiguration : YCbCr
4005     // ShutterSpeedValue : 4.058893515764426
4006     // ApertureValue : 2.5260688216892597 [4845/1918]
4007     // BrightnessValue : -0.3126686601998395
4008     // MeteringMode : Pattern
4009     // Flash : Flash did not fire, compulsory flash mode
4010     // FocalLength : 4.28 [107/25]
4011     // SubjectArea : [4 values]
4012     // FlashpixVersion : 0100
4013     // ColorSpace : 1
4014     // PixelXDimension : 2448
4015     // PixelYDimension : 3264
4016     // SensingMethod : One-chip color area sensor
4017     // ExposureMode : 0
4018     // WhiteBalance : Auto white balance
4019     // FocalLengthIn35mmFilm : 35
4020     // SceneCaptureType : Standard
4021     define('runtime/html5/imagemeta/exif',[
4022         'base',
4023         'runtime/html5/imagemeta'
4024     ], function( Base, ImageMeta ) {
4025     
4026         var EXIF = {};
4027     
4028         EXIF.ExifMap = function() {
4029             return this;
4030         };
4031     
4032         EXIF.ExifMap.prototype.map = {
4033             'Orientation': 0x0112
4034         };
4035     
4036         EXIF.ExifMap.prototype.get = function( id ) {
4037             return this[ id ] || this[ this.map[ id ] ];
4038         };
4039     
4040         EXIF.exifTagTypes = {
4041             // byte, 8-bit unsigned int:
4042             1: {
4043                 getValue: function( dataView, dataOffset ) {
4044                     return dataView.getUint8( dataOffset );
4045                 },
4046                 size: 1
4047             },
4048     
4049             // ascii, 8-bit byte:
4050             2: {
4051                 getValue: function( dataView, dataOffset ) {
4052                     return String.fromCharCode( dataView.getUint8( dataOffset ) );
4053                 },
4054                 size: 1,
4055                 ascii: true
4056             },
4057     
4058             // short, 16 bit int:
4059             3: {
4060                 getValue: function( dataView, dataOffset, littleEndian ) {
4061                     return dataView.getUint16( dataOffset, littleEndian );
4062                 },
4063                 size: 2
4064             },
4065     
4066             // long, 32 bit int:
4067             4: {
4068                 getValue: function( dataView, dataOffset, littleEndian ) {
4069                     return dataView.getUint32( dataOffset, littleEndian );
4070                 },
4071                 size: 4
4072             },
4073     
4074             // rational = two long values,
4075             // first is numerator, second is denominator:
4076             5: {
4077                 getValue: function( dataView, dataOffset, littleEndian ) {
4078                     return dataView.getUint32( dataOffset, littleEndian ) /
4079                         dataView.getUint32( dataOffset + 4, littleEndian );
4080                 },
4081                 size: 8
4082             },
4083     
4084             // slong, 32 bit signed int:
4085             9: {
4086                 getValue: function( dataView, dataOffset, littleEndian ) {
4087                     return dataView.getInt32( dataOffset, littleEndian );
4088                 },
4089                 size: 4
4090             },
4091     
4092             // srational, two slongs, first is numerator, second is denominator:
4093             10: {
4094                 getValue: function( dataView, dataOffset, littleEndian ) {
4095                     return dataView.getInt32( dataOffset, littleEndian ) /
4096                         dataView.getInt32( dataOffset + 4, littleEndian );
4097                 },
4098                 size: 8
4099             }
4100         };
4101     
4102         // undefined, 8-bit byte, value depending on field:
4103         EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];
4104     
4105         EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,
4106                 littleEndian ) {
4107     
4108             var tagType = EXIF.exifTagTypes[ type ],
4109                 tagSize, dataOffset, values, i, str, c;
4110     
4111             if ( !tagType ) {
4112                 Base.log('Invalid Exif data: Invalid tag type.');
4113                 return;
4114             }
4115     
4116             tagSize = tagType.size * length;
4117     
4118             // Determine if the value is contained in the dataOffset bytes,
4119             // or if the value at the dataOffset is a pointer to the actual data:
4120             dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,
4121                     littleEndian ) : (offset + 8);
4122     
4123             if ( dataOffset + tagSize > dataView.byteLength ) {
4124                 Base.log('Invalid Exif data: Invalid data offset.');
4125                 return;
4126             }
4127     
4128             if ( length === 1 ) {
4129                 return tagType.getValue( dataView, dataOffset, littleEndian );
4130             }
4131     
4132             values = [];
4133     
4134             for ( i = 0; i < length; i += 1 ) {
4135                 values[ i ] = tagType.getValue( dataView,
4136                         dataOffset + i * tagType.size, littleEndian );
4137             }
4138     
4139             if ( tagType.ascii ) {
4140                 str = '';
4141     
4142                 // Concatenate the chars:
4143                 for ( i = 0; i < values.length; i += 1 ) {
4144                     c = values[ i ];
4145     
4146                     // Ignore the terminating NULL byte(s):
4147                     if ( c === '\u0000' ) {
4148                         break;
4149                     }
4150                     str += c;
4151                 }
4152     
4153                 return str;
4154             }
4155             return values;
4156         };
4157     
4158         EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,
4159                 data ) {
4160     
4161             var tag = dataView.getUint16( offset, littleEndian );
4162             data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,
4163                     dataView.getUint16( offset + 2, littleEndian ),    // tag type
4164                     dataView.getUint32( offset + 4, littleEndian ),    // tag length
4165                     littleEndian );
4166         };
4167     
4168         EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,
4169                 littleEndian, data ) {
4170     
4171             var tagsNumber, dirEndOffset, i;
4172     
4173             if ( dirOffset + 6 > dataView.byteLength ) {
4174                 Base.log('Invalid Exif data: Invalid directory offset.');
4175                 return;
4176             }
4177     
4178             tagsNumber = dataView.getUint16( dirOffset, littleEndian );
4179             dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
4180     
4181             if ( dirEndOffset + 4 > dataView.byteLength ) {
4182                 Base.log('Invalid Exif data: Invalid directory size.');
4183                 return;
4184             }
4185     
4186             for ( i = 0; i < tagsNumber; i += 1 ) {
4187                 this.parseExifTag( dataView, tiffOffset,
4188                         dirOffset + 2 + 12 * i,    // tag offset
4189                         littleEndian, data );
4190             }
4191     
4192             // Return the offset to the next directory:
4193             return dataView.getUint32( dirEndOffset, littleEndian );
4194         };
4195     
4196         // EXIF.getExifThumbnail = function(dataView, offset, length) {
4197         //     var hexData,
4198         //         i,
4199         //         b;
4200         //     if (!length || offset + length > dataView.byteLength) {
4201         //         Base.log('Invalid Exif data: Invalid thumbnail data.');
4202         //         return;
4203         //     }
4204         //     hexData = [];
4205         //     for (i = 0; i < length; i += 1) {
4206         //         b = dataView.getUint8(offset + i);
4207         //         hexData.push((b < 16 ? '0' : '') + b.toString(16));
4208         //     }
4209         //     return 'data:image/jpeg,%' + hexData.join('%');
4210         // };
4211     
4212         EXIF.parseExifData = function( dataView, offset, length, data ) {
4213     
4214             var tiffOffset = offset + 10,
4215                 littleEndian, dirOffset;
4216     
4217             // Check for the ASCII code for "Exif" (0x45786966):
4218             if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {
4219                 // No Exif data, might be XMP data instead
4220                 return;
4221             }
4222             if ( tiffOffset + 8 > dataView.byteLength ) {
4223                 Base.log('Invalid Exif data: Invalid segment size.');
4224                 return;
4225             }
4226     
4227             // Check for the two null bytes:
4228             if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {
4229                 Base.log('Invalid Exif data: Missing byte alignment offset.');
4230                 return;
4231             }
4232     
4233             // Check the byte alignment:
4234             switch ( dataView.getUint16( tiffOffset ) ) {
4235                 case 0x4949:
4236                     littleEndian = true;
4237                     break;
4238     
4239                 case 0x4D4D:
4240                     littleEndian = false;
4241                     break;
4242     
4243                 default:
4244                     Base.log('Invalid Exif data: Invalid byte alignment marker.');
4245                     return;
4246             }
4247     
4248             // Check for the TIFF tag marker (0x002A):
4249             if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {
4250                 Base.log('Invalid Exif data: Missing TIFF marker.');
4251                 return;
4252             }
4253     
4254             // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
4255             dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );
4256             // Create the exif object to store the tags:
4257             data.exif = new EXIF.ExifMap();
4258             // Parse the tags of the main image directory and retrieve the
4259             // offset to the next directory, usually the thumbnail directory:
4260             dirOffset = EXIF.parseExifTags( dataView, tiffOffset,
4261                     tiffOffset + dirOffset, littleEndian, data );
4262     
4263             // 尝试读取缩略图
4264             // if ( dirOffset ) {
4265             //     thumbnailData = {exif: {}};
4266             //     dirOffset = EXIF.parseExifTags(
4267             //         dataView,
4268             //         tiffOffset,
4269             //         tiffOffset + dirOffset,
4270             //         littleEndian,
4271             //         thumbnailData
4272             //     );
4273     
4274             //     // Check for JPEG Thumbnail offset:
4275             //     if (thumbnailData.exif[0x0201]) {
4276             //         data.exif.Thumbnail = EXIF.getExifThumbnail(
4277             //             dataView,
4278             //             tiffOffset + thumbnailData.exif[0x0201],
4279             //             thumbnailData.exif[0x0202] // Thumbnail data length
4280             //         );
4281             //     }
4282             // }
4283         };
4284     
4285         ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );
4286         return EXIF;
4287     });
4288     /**
4289      * @fileOverview Image
4290      */
4291     define('runtime/html5/image',[
4292         'base',
4293         'runtime/html5/runtime',
4294         'runtime/html5/util'
4295     ], function( Base, Html5Runtime, Util ) {
4296     
4297         var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';
4298     
4299         return Html5Runtime.register( 'Image', {
4300     
4301             // flag: 标记是否被修改过。
4302             modified: false,
4303     
4304             init: function() {
4305                 var me = this,
4306                     img = new Image();
4307     
4308                 img.onload = function() {
4309     
4310                     me._info = {
4311                         type: me.type,
4312                         width: this.width,
4313                         height: this.height
4314                     };
4315     
4316                     // 读取meta信息。
4317                     if ( !me._metas && 'image/jpeg' === me.type ) {
4318                         Util.parseMeta( me._blob, function( error, ret ) {
4319                             me._metas = ret;
4320                             me.owner.trigger('load');
4321                         });
4322                     } else {
4323                         me.owner.trigger('load');
4324                     }
4325                 };
4326     
4327                 img.onerror = function() {
4328                     me.owner.trigger('error');
4329                 };
4330     
4331                 me._img = img;
4332             },
4333     
4334             loadFromBlob: function( blob ) {
4335                 var me = this,
4336                     img = me._img;
4337     
4338                 me._blob = blob;
4339                 me.type = blob.type;
4340                 img.src = Util.createObjectURL( blob.getSource() );
4341                 me.owner.once( 'load', function() {
4342                     Util.revokeObjectURL( img.src );
4343                 });
4344             },
4345     
4346             resize: function( width, height ) {
4347                 var canvas = this._canvas ||
4348                         (this._canvas = document.createElement('canvas'));
4349     
4350                 this._resize( this._img, canvas, width, height );
4351                 this._blob = null;    // 没用了,可以删掉了。
4352                 this.modified = true;
4353                 this.owner.trigger('complete');
4354             },
4355     
4356             getAsBlob: function( type ) {
4357                 var blob = this._blob,
4358                     opts = this.options,
4359                     canvas;
4360     
4361                 type = type || this.type;
4362     
4363                 // blob需要重新生成。
4364                 if ( this.modified || this.type !== type ) {
4365                     canvas = this._canvas;
4366     
4367                     if ( type === 'image/jpeg' ) {
4368     
4369                         blob = Util.canvasToDataUrl( canvas, 'image/jpeg',
4370                                 opts.quality );
4371     
4372                         if ( opts.preserveHeaders && this._metas &&
4373                                 this._metas.imageHead ) {
4374     
4375                             blob = Util.dataURL2ArrayBuffer( blob );
4376                             blob = Util.updateImageHead( blob,
4377                                     this._metas.imageHead );
4378                             blob = Util.arrayBufferToBlob( blob, type );
4379                             return blob;
4380                         }
4381                     } else {
4382                         blob = Util.canvasToDataUrl( canvas, type );
4383                     }
4384     
4385                     blob = Util.dataURL2Blob( blob );
4386                 }
4387     
4388                 return blob;
4389             },
4390     
4391             getAsDataUrl: function( type ) {
4392                 var opts = this.options;
4393     
4394                 type = type || this.type;
4395     
4396                 if ( type === 'image/jpeg' ) {
4397                     return Util.canvasToDataUrl( this._canvas, type, opts.quality );
4398                 } else {
4399                     return this._canvas.toDataURL( type );
4400                 }
4401             },
4402     
4403             getOrientation: function() {
4404                 return this._metas && this._metas.exif &&
4405                         this._metas.exif.get('Orientation') || 1;
4406             },
4407     
4408             info: function( val ) {
4409     
4410                 // setter
4411                 if ( val ) {
4412                     this._info = val;
4413                     return this;
4414                 }
4415     
4416                 // getter
4417                 return this._info;
4418             },
4419     
4420             meta: function( val ) {
4421     
4422                 // setter
4423                 if ( val ) {
4424                     this._meta = val;
4425                     return this;
4426                 }
4427     
4428                 // getter
4429                 return this._meta;
4430             },
4431     
4432             destroy: function() {
4433                 var canvas = this._canvas;
4434                 this._img.onload = null;
4435     
4436                 if ( canvas ) {
4437                     canvas.getContext('2d')
4438                             .clearRect( 0, 0, canvas.width, canvas.height );
4439                     canvas.width = canvas.height = 0;
4440                     this._canvas = null;
4441                 }
4442     
4443                 // 释放内存。非常重要,否则释放不了image的内存。
4444                 this._img.src = BLANK;
4445                 this._img = this._blob = null;
4446             },
4447     
4448             _resize: function( img, cvs, width, height ) {
4449                 var opts = this.options,
4450                     naturalWidth = img.width,
4451                     naturalHeight = img.height,
4452                     orientation = this.getOrientation(),
4453                     scale, w, h, x, y;
4454     
4455                 // values that require 90 degree rotation
4456                 if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {
4457     
4458                     // 交换width, height的值。
4459                     width ^= height;
4460                     height ^= width;
4461                     width ^= height;
4462                 }
4463     
4464                 scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,
4465                         height / naturalHeight );
4466     
4467                 // 不允许放大。
4468                 opts.allowMagnify || (scale = Math.min( 1, scale ));
4469     
4470                 w = naturalWidth * scale;
4471                 h = naturalHeight * scale;
4472     
4473                 if ( opts.crop ) {
4474                     cvs.width = width;
4475                     cvs.height = height;
4476                 } else {
4477                     cvs.width = w;
4478                     cvs.height = h;
4479                 }
4480     
4481                 x = (cvs.width - w) / 2;
4482                 y = (cvs.height - h) / 2;
4483     
4484                 opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );
4485     
4486                 this._renderImageToCanvas( cvs, img, x, y, w, h );
4487             },
4488     
4489             _rotate2Orientaion: function( canvas, orientation ) {
4490                 var width = canvas.width,
4491                     height = canvas.height,
4492                     ctx = canvas.getContext('2d');
4493     
4494                 switch ( orientation ) {
4495                     case 5:
4496                     case 6:
4497                     case 7:
4498                     case 8:
4499                         canvas.width = height;
4500                         canvas.height = width;
4501                         break;
4502                 }
4503     
4504                 switch ( orientation ) {
4505                     case 2:    // horizontal flip
4506                         ctx.translate( width, 0 );
4507                         ctx.scale( -1, 1 );
4508                         break;
4509     
4510                     case 3:    // 180 rotate left
4511                         ctx.translate( width, height );
4512                         ctx.rotate( Math.PI );
4513                         break;
4514     
4515                     case 4:    // vertical flip
4516                         ctx.translate( 0, height );
4517                         ctx.scale( 1, -1 );
4518                         break;
4519     
4520                     case 5:    // vertical flip + 90 rotate right
4521                         ctx.rotate( 0.5 * Math.PI );
4522                         ctx.scale( 1, -1 );
4523                         break;
4524     
4525                     case 6:    // 90 rotate right
4526                         ctx.rotate( 0.5 * Math.PI );
4527                         ctx.translate( 0, -height );
4528                         break;
4529     
4530                     case 7:    // horizontal flip + 90 rotate right
4531                         ctx.rotate( 0.5 * Math.PI );
4532                         ctx.translate( width, -height );
4533                         ctx.scale( -1, 1 );
4534                         break;
4535     
4536                     case 8:    // 90 rotate left
4537                         ctx.rotate( -0.5 * Math.PI );
4538                         ctx.translate( -width, 0 );
4539                         break;
4540                 }
4541             },
4542     
4543             // https://github.com/stomita/ios-imagefile-megapixel/
4544             // blob/master/src/megapix-image.js
4545             _renderImageToCanvas: (function() {
4546     
4547                 // 如果不是ios, 不需要这么复杂!
4548                 if ( !Base.os.ios ) {
4549                     return function( canvas, img, x, y, w, h ) {
4550                         canvas.getContext('2d').drawImage( img, x, y, w, h );
4551                     };
4552                 }
4553     
4554                 /**
4555                  * Detecting vertical squash in loaded image.
4556                  * Fixes a bug which squash image vertically while drawing into
4557                  * canvas for some images.
4558                  */
4559                 function detectVerticalSquash( img, iw, ih ) {
4560                     var canvas = document.createElement('canvas'),
4561                         ctx = canvas.getContext('2d'),
4562                         sy = 0,
4563                         ey = ih,
4564                         py = ih,
4565                         data, alpha, ratio;
4566     
4567     
4568                     canvas.width = 1;
4569                     canvas.height = ih;
4570                     ctx.drawImage( img, 0, 0 );
4571                     data = ctx.getImageData( 0, 0, 1, ih ).data;
4572     
4573                     // search image edge pixel position in case
4574                     // it is squashed vertically.
4575                     while ( py > sy ) {
4576                         alpha = data[ (py - 1) * 4 + 3 ];
4577     
4578                         if ( alpha === 0 ) {
4579                             ey = py;
4580                         } else {
4581                             sy = py;
4582                         }
4583     
4584                         py = (ey + sy) >> 1;
4585                     }
4586     
4587                     ratio = (py / ih);
4588                     return (ratio === 0) ? 1 : ratio;
4589                 }
4590     
4591                 // fix ie7 bug
4592                 // http://stackoverflow.com/questions/11929099/
4593                 // html5-canvas-drawimage-ratio-bug-ios
4594                 if ( Base.os.ios >= 7 ) {
4595                     return function( canvas, img, x, y, w, h ) {
4596                         var iw = img.naturalWidth,
4597                             ih = img.naturalHeight,
4598                             vertSquashRatio = detectVerticalSquash( img, iw, ih );
4599     
4600                         return canvas.getContext('2d').drawImage( img, 0, 0,
4601                             iw * vertSquashRatio, ih * vertSquashRatio,
4602                             x, y, w, h );
4603                     };
4604                 }
4605     
4606                 /**
4607                  * Detect subsampling in loaded image.
4608                  * In iOS, larger images than 2M pixels may be
4609                  * subsampled in rendering.
4610                  */
4611                 function detectSubsampling( img ) {
4612                     var iw = img.naturalWidth,
4613                         ih = img.naturalHeight,
4614                         canvas, ctx;
4615     
4616                     // subsampling may happen overmegapixel image
4617                     if ( iw * ih > 1024 * 1024 ) {
4618                         canvas = document.createElement('canvas');
4619                         canvas.width = canvas.height = 1;
4620                         ctx = canvas.getContext('2d');
4621                         ctx.drawImage( img, -iw + 1, 0 );
4622     
4623                         // subsampled image becomes half smaller in rendering size.
4624                         // check alpha channel value to confirm image is covering
4625                         // edge pixel or not. if alpha value is 0
4626                         // image is not covering, hence subsampled.
4627                         return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;
4628                     } else {
4629                         return false;
4630                     }
4631                 }
4632     
4633     
4634                 return function( canvas, img, x, y, width, height ) {
4635                     var iw = img.naturalWidth,
4636                         ih = img.naturalHeight,
4637                         ctx = canvas.getContext('2d'),
4638                         subsampled = detectSubsampling( img ),
4639                         doSquash = this.type === 'image/jpeg',
4640                         d = 1024,
4641                         sy = 0,
4642                         dy = 0,
4643                         tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;
4644     
4645                     if ( subsampled ) {
4646                         iw /= 2;
4647                         ih /= 2;
4648                     }
4649     
4650                     ctx.save();
4651                     tmpCanvas = document.createElement('canvas');
4652                     tmpCanvas.width = tmpCanvas.height = d;
4653     
4654                     tmpCtx = tmpCanvas.getContext('2d');
4655                     vertSquashRatio = doSquash ?
4656                             detectVerticalSquash( img, iw, ih ) : 1;
4657     
4658                     dw = Math.ceil( d * width / iw );
4659                     dh = Math.ceil( d * height / ih / vertSquashRatio );
4660     
4661                     while ( sy < ih ) {
4662                         sx = 0;
4663                         dx = 0;
4664                         while ( sx < iw ) {
4665                             tmpCtx.clearRect( 0, 0, d, d );
4666                             tmpCtx.drawImage( img, -sx, -sy );
4667                             ctx.drawImage( tmpCanvas, 0, 0, d, d,
4668                                     x + dx, y + dy, dw, dh );
4669                             sx += d;
4670                             dx += dw;
4671                         }
4672                         sy += d;
4673                         dy += dh;
4674                     }
4675                     ctx.restore();
4676                     tmpCanvas = tmpCtx = null;
4677                 };
4678             })()
4679         });
4680     });
4681     /**
4682      * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug
4683      * android里面toDataUrl('image/jpege')得到的结果却是png.
4684      *
4685      * 所以这里没辙,只能借助这个工具
4686      * @fileOverview jpeg encoder
4687      */
4688     define('runtime/html5/jpegencoder',[], function( require, exports, module ) {
4689     
4690         /*
4691           Copyright (c) 2008, Adobe Systems Incorporated
4692           All rights reserved.
4693     
4694           Redistribution and use in source and binary forms, with or without
4695           modification, are permitted provided that the following conditions are
4696           met:
4697     
4698           * Redistributions of source code must retain the above copyright notice,
4699             this list of conditions and the following disclaimer.
4700     
4701           * Redistributions in binary form must reproduce the above copyright
4702             notice, this list of conditions and the following disclaimer in the
4703             documentation and/or other materials provided with the distribution.
4704     
4705           * Neither the name of Adobe Systems Incorporated nor the names of its
4706             contributors may be used to endorse or promote products derived from
4707             this software without specific prior written permission.
4708     
4709           THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
4710           IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
4711           THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
4712           PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
4713           CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
4714           EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
4715           PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
4716           PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
4717           LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
4718           NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
4719           SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4720         */
4721         /*
4722         JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009
4723     
4724         Basic GUI blocking jpeg encoder
4725         */
4726     
4727         function JPEGEncoder(quality) {
4728           var self = this;
4729             var fround = Math.round;
4730             var ffloor = Math.floor;
4731             var YTable = new Array(64);
4732             var UVTable = new Array(64);
4733             var fdtbl_Y = new Array(64);
4734             var fdtbl_UV = new Array(64);
4735             var YDC_HT;
4736             var UVDC_HT;
4737             var YAC_HT;
4738             var UVAC_HT;
4739     
4740             var bitcode = new Array(65535);
4741             var category = new Array(65535);
4742             var outputfDCTQuant = new Array(64);
4743             var DU = new Array(64);
4744             var byteout = [];
4745             var bytenew = 0;
4746             var bytepos = 7;
4747     
4748             var YDU = new Array(64);
4749             var UDU = new Array(64);
4750             var VDU = new Array(64);
4751             var clt = new Array(256);
4752             var RGB_YUV_TABLE = new Array(2048);
4753             var currentQuality;
4754     
4755             var ZigZag = [
4756                      0, 1, 5, 6,14,15,27,28,
4757                      2, 4, 7,13,16,26,29,42,
4758                      3, 8,12,17,25,30,41,43,
4759                      9,11,18,24,31,40,44,53,
4760                     10,19,23,32,39,45,52,54,
4761                     20,22,33,38,46,51,55,60,
4762                     21,34,37,47,50,56,59,61,
4763                     35,36,48,49,57,58,62,63
4764                 ];
4765     
4766             var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];
4767             var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
4768             var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];
4769             var std_ac_luminance_values = [
4770                     0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
4771                     0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
4772                     0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
4773                     0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
4774                     0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
4775                     0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
4776                     0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
4777                     0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
4778                     0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
4779                     0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
4780                     0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
4781                     0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
4782                     0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
4783                     0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
4784                     0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
4785                     0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
4786                     0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
4787                     0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
4788                     0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
4789                     0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
4790                     0xf9,0xfa
4791                 ];
4792     
4793             var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];
4794             var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];
4795             var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];
4796             var std_ac_chrominance_values = [
4797                     0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
4798                     0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
4799                     0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
4800                     0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
4801                     0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
4802                     0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
4803                     0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
4804                     0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
4805                     0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
4806                     0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
4807                     0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
4808                     0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
4809                     0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
4810                     0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
4811                     0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
4812                     0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
4813                     0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
4814                     0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
4815                     0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
4816                     0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
4817                     0xf9,0xfa
4818                 ];
4819     
4820             function initQuantTables(sf){
4821                     var YQT = [
4822                         16, 11, 10, 16, 24, 40, 51, 61,
4823                         12, 12, 14, 19, 26, 58, 60, 55,
4824                         14, 13, 16, 24, 40, 57, 69, 56,
4825                         14, 17, 22, 29, 51, 87, 80, 62,
4826                         18, 22, 37, 56, 68,109,103, 77,
4827                         24, 35, 55, 64, 81,104,113, 92,
4828                         49, 64, 78, 87,103,121,120,101,
4829                         72, 92, 95, 98,112,100,103, 99
4830                     ];
4831     
4832                     for (var i = 0; i < 64; i++) {
4833                         var t = ffloor((YQT[i]*sf+50)/100);
4834                         if (t < 1) {
4835                             t = 1;
4836                         } else if (t > 255) {
4837                             t = 255;
4838                         }
4839                         YTable[ZigZag[i]] = t;
4840                     }
4841                     var UVQT = [
4842                         17, 18, 24, 47, 99, 99, 99, 99,
4843                         18, 21, 26, 66, 99, 99, 99, 99,
4844                         24, 26, 56, 99, 99, 99, 99, 99,
4845                         47, 66, 99, 99, 99, 99, 99, 99,
4846                         99, 99, 99, 99, 99, 99, 99, 99,
4847                         99, 99, 99, 99, 99, 99, 99, 99,
4848                         99, 99, 99, 99, 99, 99, 99, 99,
4849                         99, 99, 99, 99, 99, 99, 99, 99
4850                     ];
4851                     for (var j = 0; j < 64; j++) {
4852                         var u = ffloor((UVQT[j]*sf+50)/100);
4853                         if (u < 1) {
4854                             u = 1;
4855                         } else if (u > 255) {
4856                             u = 255;
4857                         }
4858                         UVTable[ZigZag[j]] = u;
4859                     }
4860                     var aasf = [
4861                         1.0, 1.387039845, 1.306562965, 1.175875602,
4862                         1.0, 0.785694958, 0.541196100, 0.275899379
4863                     ];
4864                     var k = 0;
4865                     for (var row = 0; row < 8; row++)
4866                     {
4867                         for (var col = 0; col < 8; col++)
4868                         {
4869                             fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
4870                             fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));
4871                             k++;
4872                         }
4873                     }
4874                 }
4875     
4876                 function computeHuffmanTbl(nrcodes, std_table){
4877                     var codevalue = 0;
4878                     var pos_in_table = 0;
4879                     var HT = new Array();
4880                     for (var k = 1; k <= 16; k++) {
4881                         for (var j = 1; j <= nrcodes[k]; j++) {
4882                             HT[std_table[pos_in_table]] = [];
4883                             HT[std_table[pos_in_table]][0] = codevalue;
4884                             HT[std_table[pos_in_table]][1] = k;
4885                             pos_in_table++;
4886                             codevalue++;
4887                         }
4888                         codevalue*=2;
4889                     }
4890                     return HT;
4891                 }
4892     
4893                 function initHuffmanTbl()
4894                 {
4895                     YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
4896                     UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
4897                     YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
4898                     UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
4899                 }
4900     
4901                 function initCategoryNumber()
4902                 {
4903                     var nrlower = 1;
4904                     var nrupper = 2;
4905                     for (var cat = 1; cat <= 15; cat++) {
4906                         //Positive numbers
4907                         for (var nr = nrlower; nr<nrupper; nr++) {
4908                             category[32767+nr] = cat;
4909                             bitcode[32767+nr] = [];
4910                             bitcode[32767+nr][1] = cat;
4911                             bitcode[32767+nr][0] = nr;
4912                         }
4913                         //Negative numbers
4914                         for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {
4915                             category[32767+nrneg] = cat;
4916                             bitcode[32767+nrneg] = [];
4917                             bitcode[32767+nrneg][1] = cat;
4918                             bitcode[32767+nrneg][0] = nrupper-1+nrneg;
4919                         }
4920                         nrlower <<= 1;
4921                         nrupper <<= 1;
4922                     }
4923                 }
4924     
4925                 function initRGBYUVTable() {
4926                     for(var i = 0; i < 256;i++) {
4927                         RGB_YUV_TABLE[i]            =  19595 * i;
4928                         RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;
4929                         RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;
4930                         RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;
4931                         RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;
4932                         RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;
4933                         RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;
4934                         RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;
4935                     }
4936                 }
4937     
4938                 // IO functions
4939                 function writeBits(bs)
4940                 {
4941                     var value = bs[0];
4942                     var posval = bs[1]-1;
4943                     while ( posval >= 0 ) {
4944                         if (value & (1 << posval) ) {
4945                             bytenew |= (1 << bytepos);
4946                         }
4947                         posval--;
4948                         bytepos--;
4949                         if (bytepos < 0) {
4950                             if (bytenew == 0xFF) {
4951                                 writeByte(0xFF);
4952                                 writeByte(0);
4953                             }
4954                             else {
4955                                 writeByte(bytenew);
4956                             }
4957                             bytepos=7;
4958                             bytenew=0;
4959                         }
4960                     }
4961                 }
4962     
4963                 function writeByte(value)
4964                 {
4965                     byteout.push(clt[value]); // write char directly instead of converting later
4966                 }
4967     
4968                 function writeWord(value)
4969                 {
4970                     writeByte((value>>8)&0xFF);
4971                     writeByte((value   )&0xFF);
4972                 }
4973     
4974                 // DCT & quantization core
4975                 function fDCTQuant(data, fdtbl)
4976                 {
4977                     var d0, d1, d2, d3, d4, d5, d6, d7;
4978                     /* Pass 1: process rows. */
4979                     var dataOff=0;
4980                     var i;
4981                     var I8 = 8;
4982                     var I64 = 64;
4983                     for (i=0; i<I8; ++i)
4984                     {
4985                         d0 = data[dataOff];
4986                         d1 = data[dataOff+1];
4987                         d2 = data[dataOff+2];
4988                         d3 = data[dataOff+3];
4989                         d4 = data[dataOff+4];
4990                         d5 = data[dataOff+5];
4991                         d6 = data[dataOff+6];
4992                         d7 = data[dataOff+7];
4993     
4994                         var tmp0 = d0 + d7;
4995                         var tmp7 = d0 - d7;
4996                         var tmp1 = d1 + d6;
4997                         var tmp6 = d1 - d6;
4998                         var tmp2 = d2 + d5;
4999                         var tmp5 = d2 - d5;
5000                         var tmp3 = d3 + d4;
5001                         var tmp4 = d3 - d4;
5002     
5003                         /* Even part */
5004                         var tmp10 = tmp0 + tmp3;    /* phase 2 */
5005                         var tmp13 = tmp0 - tmp3;
5006                         var tmp11 = tmp1 + tmp2;
5007                         var tmp12 = tmp1 - tmp2;
5008     
5009                         data[dataOff] = tmp10 + tmp11; /* phase 3 */
5010                         data[dataOff+4] = tmp10 - tmp11;
5011     
5012                         var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
5013                         data[dataOff+2] = tmp13 + z1; /* phase 5 */
5014                         data[dataOff+6] = tmp13 - z1;
5015     
5016                         /* Odd part */
5017                         tmp10 = tmp4 + tmp5; /* phase 2 */
5018                         tmp11 = tmp5 + tmp6;
5019                         tmp12 = tmp6 + tmp7;
5020     
5021                         /* The rotator is modified from fig 4-8 to avoid extra negations. */
5022                         var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
5023                         var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */
5024                         var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
5025                         var z3 = tmp11 * 0.707106781; /* c4 */
5026     
5027                         var z11 = tmp7 + z3;    /* phase 5 */
5028                         var z13 = tmp7 - z3;
5029     
5030                         data[dataOff+5] = z13 + z2; /* phase 6 */
5031                         data[dataOff+3] = z13 - z2;
5032                         data[dataOff+1] = z11 + z4;
5033                         data[dataOff+7] = z11 - z4;
5034     
5035                         dataOff += 8; /* advance pointer to next row */
5036                     }
5037     
5038                     /* Pass 2: process columns. */
5039                     dataOff = 0;
5040                     for (i=0; i<I8; ++i)
5041                     {
5042                         d0 = data[dataOff];
5043                         d1 = data[dataOff + 8];
5044                         d2 = data[dataOff + 16];
5045                         d3 = data[dataOff + 24];
5046                         d4 = data[dataOff + 32];
5047                         d5 = data[dataOff + 40];
5048                         d6 = data[dataOff + 48];
5049                         d7 = data[dataOff + 56];
5050     
5051                         var tmp0p2 = d0 + d7;
5052                         var tmp7p2 = d0 - d7;
5053                         var tmp1p2 = d1 + d6;
5054                         var tmp6p2 = d1 - d6;
5055                         var tmp2p2 = d2 + d5;
5056                         var tmp5p2 = d2 - d5;
5057                         var tmp3p2 = d3 + d4;
5058                         var tmp4p2 = d3 - d4;
5059     
5060                         /* Even part */
5061                         var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */
5062                         var tmp13p2 = tmp0p2 - tmp3p2;
5063                         var tmp11p2 = tmp1p2 + tmp2p2;
5064                         var tmp12p2 = tmp1p2 - tmp2p2;
5065     
5066                         data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
5067                         data[dataOff+32] = tmp10p2 - tmp11p2;
5068     
5069                         var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
5070                         data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */
5071                         data[dataOff+48] = tmp13p2 - z1p2;
5072     
5073                         /* Odd part */
5074                         tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
5075                         tmp11p2 = tmp5p2 + tmp6p2;
5076                         tmp12p2 = tmp6p2 + tmp7p2;
5077     
5078                         /* The rotator is modified from fig 4-8 to avoid extra negations. */
5079                         var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
5080                         var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */
5081                         var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
5082                         var z3p2 = tmp11p2 * 0.707106781; /* c4 */
5083     
5084                         var z11p2 = tmp7p2 + z3p2;  /* phase 5 */
5085                         var z13p2 = tmp7p2 - z3p2;
5086     
5087                         data[dataOff+40] = z13p2 + z2p2; /* phase 6 */
5088                         data[dataOff+24] = z13p2 - z2p2;
5089                         data[dataOff+ 8] = z11p2 + z4p2;
5090                         data[dataOff+56] = z11p2 - z4p2;
5091     
5092                         dataOff++; /* advance pointer to next column */
5093                     }
5094     
5095                     // Quantize/descale the coefficients
5096                     var fDCTQuant;
5097                     for (i=0; i<I64; ++i)
5098                     {
5099                         // Apply the quantization and scaling factor & Round to nearest integer
5100                         fDCTQuant = data[i]*fdtbl[i];
5101                         outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);
5102                         //outputfDCTQuant[i] = fround(fDCTQuant);
5103     
5104                     }
5105                     return outputfDCTQuant;
5106                 }
5107     
5108                 function writeAPP0()
5109                 {
5110                     writeWord(0xFFE0); // marker
5111                     writeWord(16); // length
5112                     writeByte(0x4A); // J
5113                     writeByte(0x46); // F
5114                     writeByte(0x49); // I
5115                     writeByte(0x46); // F
5116                     writeByte(0); // = "JFIF",'\0'
5117                     writeByte(1); // versionhi
5118                     writeByte(1); // versionlo
5119                     writeByte(0); // xyunits
5120                     writeWord(1); // xdensity
5121                     writeWord(1); // ydensity
5122                     writeByte(0); // thumbnwidth
5123                     writeByte(0); // thumbnheight
5124                 }
5125     
5126                 function writeSOF0(width, height)
5127                 {
5128                     writeWord(0xFFC0); // marker
5129                     writeWord(17);   // length, truecolor YUV JPG
5130                     writeByte(8);    // precision
5131                     writeWord(height);
5132                     writeWord(width);
5133                     writeByte(3);    // nrofcomponents
5134                     writeByte(1);    // IdY
5135                     writeByte(0x11); // HVY
5136                     writeByte(0);    // QTY
5137                     writeByte(2);    // IdU
5138                     writeByte(0x11); // HVU
5139                     writeByte(1);    // QTU
5140                     writeByte(3);    // IdV
5141                     writeByte(0x11); // HVV
5142                     writeByte(1);    // QTV
5143                 }
5144     
5145                 function writeDQT()
5146                 {
5147                     writeWord(0xFFDB); // marker
5148                     writeWord(132);    // length
5149                     writeByte(0);
5150                     for (var i=0; i<64; i++) {
5151                         writeByte(YTable[i]);
5152                     }
5153                     writeByte(1);
5154                     for (var j=0; j<64; j++) {
5155                         writeByte(UVTable[j]);
5156                     }
5157                 }
5158     
5159                 function writeDHT()
5160                 {
5161                     writeWord(0xFFC4); // marker
5162                     writeWord(0x01A2); // length
5163     
5164                     writeByte(0); // HTYDCinfo
5165                     for (var i=0; i<16; i++) {
5166                         writeByte(std_dc_luminance_nrcodes[i+1]);
5167                     }
5168                     for (var j=0; j<=11; j++) {
5169                         writeByte(std_dc_luminance_values[j]);
5170                     }
5171     
5172                     writeByte(0x10); // HTYACinfo
5173                     for (var k=0; k<16; k++) {
5174                         writeByte(std_ac_luminance_nrcodes[k+1]);
5175                     }
5176                     for (var l=0; l<=161; l++) {
5177                         writeByte(std_ac_luminance_values[l]);
5178                     }
5179     
5180                     writeByte(1); // HTUDCinfo
5181                     for (var m=0; m<16; m++) {
5182                         writeByte(std_dc_chrominance_nrcodes[m+1]);
5183                     }
5184                     for (var n=0; n<=11; n++) {
5185                         writeByte(std_dc_chrominance_values[n]);
5186                     }
5187     
5188                     writeByte(0x11); // HTUACinfo
5189                     for (var o=0; o<16; o++) {
5190                         writeByte(std_ac_chrominance_nrcodes[o+1]);
5191                     }
5192                     for (var p=0; p<=161; p++) {
5193                         writeByte(std_ac_chrominance_values[p]);
5194                     }
5195                 }
5196     
5197                 function writeSOS()
5198                 {
5199                     writeWord(0xFFDA); // marker
5200                     writeWord(12); // length
5201                     writeByte(3); // nrofcomponents
5202                     writeByte(1); // IdY
5203                     writeByte(0); // HTY
5204                     writeByte(2); // IdU
5205                     writeByte(0x11); // HTU
5206                     writeByte(3); // IdV
5207                     writeByte(0x11); // HTV
5208                     writeByte(0); // Ss
5209                     writeByte(0x3f); // Se
5210                     writeByte(0); // Bf
5211                 }
5212     
5213                 function processDU(CDU, fdtbl, DC, HTDC, HTAC){
5214                     var EOB = HTAC[0x00];
5215                     var M16zeroes = HTAC[0xF0];
5216                     var pos;
5217                     var I16 = 16;
5218                     var I63 = 63;
5219                     var I64 = 64;
5220                     var DU_DCT = fDCTQuant(CDU, fdtbl);
5221                     //ZigZag reorder
5222                     for (var j=0;j<I64;++j) {
5223                         DU[ZigZag[j]]=DU_DCT[j];
5224                     }
5225                     var Diff = DU[0] - DC; DC = DU[0];
5226                     //Encode DC
5227                     if (Diff==0) {
5228                         writeBits(HTDC[0]); // Diff might be 0
5229                     } else {
5230                         pos = 32767+Diff;
5231                         writeBits(HTDC[category[pos]]);
5232                         writeBits(bitcode[pos]);
5233                     }
5234                     //Encode ACs
5235                     var end0pos = 63; // was const... which is crazy
5236                     for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};
5237                     //end0pos = first element in reverse order !=0
5238                     if ( end0pos == 0) {
5239                         writeBits(EOB);
5240                         return DC;
5241                     }
5242                     var i = 1;
5243                     var lng;
5244                     while ( i <= end0pos ) {
5245                         var startpos = i;
5246                         for (; (DU[i]==0) && (i<=end0pos); ++i) {}
5247                         var nrzeroes = i-startpos;
5248                         if ( nrzeroes >= I16 ) {
5249                             lng = nrzeroes>>4;
5250                             for (var nrmarker=1; nrmarker <= lng; ++nrmarker)
5251                                 writeBits(M16zeroes);
5252                             nrzeroes = nrzeroes&0xF;
5253                         }
5254                         pos = 32767+DU[i];
5255                         writeBits(HTAC[(nrzeroes<<4)+category[pos]]);
5256                         writeBits(bitcode[pos]);
5257                         i++;
5258                     }
5259                     if ( end0pos != I63 ) {
5260                         writeBits(EOB);
5261                     }
5262                     return DC;
5263                 }
5264     
5265                 function initCharLookupTable(){
5266                     var sfcc = String.fromCharCode;
5267                     for(var i=0; i < 256; i++){ ///// ACHTUNG // 255
5268                         clt[i] = sfcc(i);
5269                     }
5270                 }
5271     
5272                 this.encode = function(image,quality) // image data object
5273                 {
5274                     // var time_start = new Date().getTime();
5275     
5276                     if(quality) setQuality(quality);
5277     
5278                     // Initialize bit writer
5279                     byteout = new Array();
5280                     bytenew=0;
5281                     bytepos=7;
5282     
5283                     // Add JPEG headers
5284                     writeWord(0xFFD8); // SOI
5285                     writeAPP0();
5286                     writeDQT();
5287                     writeSOF0(image.width,image.height);
5288                     writeDHT();
5289                     writeSOS();
5290     
5291     
5292                     // Encode 8x8 macroblocks
5293                     var DCY=0;
5294                     var DCU=0;
5295                     var DCV=0;
5296     
5297                     bytenew=0;
5298                     bytepos=7;
5299     
5300     
5301                     this.encode.displayName = "_encode_";
5302     
5303                     var imageData = image.data;
5304                     var width = image.width;
5305                     var height = image.height;
5306     
5307                     var quadWidth = width*4;
5308                     var tripleWidth = width*3;
5309     
5310                     var x, y = 0;
5311                     var r, g, b;
5312                     var start,p, col,row,pos;
5313                     while(y < height){
5314                         x = 0;
5315                         while(x < quadWidth){
5316                         start = quadWidth * y + x;
5317                         p = start;
5318                         col = -1;
5319                         row = 0;
5320     
5321                         for(pos=0; pos < 64; pos++){
5322                             row = pos >> 3;// /8
5323                             col = ( pos & 7 ) * 4; // %8
5324                             p = start + ( row * quadWidth ) + col;
5325     
5326                             if(y+row >= height){ // padding bottom
5327                                 p-= (quadWidth*(y+1+row-height));
5328                             }
5329     
5330                             if(x+col >= quadWidth){ // padding right
5331                                 p-= ((x+col) - quadWidth +4)
5332                             }
5333     
5334                             r = imageData[ p++ ];
5335                             g = imageData[ p++ ];
5336                             b = imageData[ p++ ];
5337     
5338     
5339                             /* // calculate YUV values dynamically
5340                             YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80
5341                             UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));
5342                             VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));
5343                             */
5344     
5345                             // use lookup table (slightly faster)
5346                             YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;
5347                             UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;
5348                             VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;
5349     
5350                         }
5351     
5352                         DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
5353                         DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
5354                         DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
5355                         x+=32;
5356                         }
5357                         y+=8;
5358                     }
5359     
5360     
5361                     ////////////////////////////////////////////////////////////////
5362     
5363                     // Do the bit alignment of the EOI marker
5364                     if ( bytepos >= 0 ) {
5365                         var fillbits = [];
5366                         fillbits[1] = bytepos+1;
5367                         fillbits[0] = (1<<(bytepos+1))-1;
5368                         writeBits(fillbits);
5369                     }
5370     
5371                     writeWord(0xFFD9); //EOI
5372     
5373                     var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));
5374     
5375                     byteout = [];
5376     
5377                     // benchmarking
5378                     // var duration = new Date().getTime() - time_start;
5379                     // console.log('Encoding time: '+ currentQuality + 'ms');
5380                     //
5381     
5382                     return jpegDataUri
5383             }
5384     
5385             function setQuality(quality){
5386                 if (quality <= 0) {
5387                     quality = 1;
5388                 }
5389                 if (quality > 100) {
5390                     quality = 100;
5391                 }
5392     
5393                 if(currentQuality == quality) return // don't recalc if unchanged
5394     
5395                 var sf = 0;
5396                 if (quality < 50) {
5397                     sf = Math.floor(5000 / quality);
5398                 } else {
5399                     sf = Math.floor(200 - quality*2);
5400                 }
5401     
5402                 initQuantTables(sf);
5403                 currentQuality = quality;
5404                 // console.log('Quality set to: '+quality +'%');
5405             }
5406     
5407             function init(){
5408                 // var time_start = new Date().getTime();
5409                 if(!quality) quality = 50;
5410                 // Create tables
5411                 initCharLookupTable()
5412                 initHuffmanTbl();
5413                 initCategoryNumber();
5414                 initRGBYUVTable();
5415     
5416                 setQuality(quality);
5417                 // var duration = new Date().getTime() - time_start;
5418                 // console.log('Initialization '+ duration + 'ms');
5419             }
5420     
5421             init();
5422     
5423         };
5424     
5425         JPEGEncoder.encode = function( data, quality ) {
5426             var encoder = new JPEGEncoder( quality );
5427     
5428             return encoder.encode( data );
5429         }
5430     
5431         return JPEGEncoder;
5432     });
5433     /**
5434      * @fileOverview Fix android canvas.toDataUrl bug.
5435      */
5436     define('runtime/html5/androidpatch',[
5437         'runtime/html5/util',
5438         'runtime/html5/jpegencoder',
5439         'base'
5440     ], function( Util, encoder, Base ) {
5441         var origin = Util.canvasToDataUrl,
5442             supportJpeg;
5443     
5444         Util.canvasToDataUrl = function( canvas, type, quality ) {
5445             var ctx, w, h, fragement, parts;
5446     
5447             // 非android手机直接跳过。
5448             if ( !Base.os.android ) {
5449                 return origin.apply( null, arguments );
5450             }
5451     
5452             // 检测是否canvas支持jpeg导出,根据数据格式来判断。
5453             // JPEG 前两位分别是:255, 216
5454             if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {
5455                 fragement = origin.apply( null, arguments );
5456     
5457                 parts = fragement.split(',');
5458     
5459                 if ( ~parts[ 0 ].indexOf('base64') ) {
5460                     fragement = atob( parts[ 1 ] );
5461                 } else {
5462                     fragement = decodeURIComponent( parts[ 1 ] );
5463                 }
5464     
5465                 fragement = fragement.substring( 0, 2 );
5466     
5467                 supportJpeg = fragement.charCodeAt( 0 ) === 255 &&
5468                         fragement.charCodeAt( 1 ) === 216;
5469             }
5470     
5471             // 只有在android环境下才修复
5472             if ( type === 'image/jpeg' && !supportJpeg ) {
5473                 w = canvas.width;
5474                 h = canvas.height;
5475                 ctx = canvas.getContext('2d');
5476     
5477                 return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );
5478             }
5479     
5480             return origin.apply( null, arguments );
5481         };
5482     });
5483     /**
5484      * @fileOverview Transport
5485      * @todo 支持chunked传输,优势:
5486      * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分,
5487      * 而不需要重头再传一次。另外断点续传也需要用chunked方式。
5488      */
5489     define('runtime/html5/transport',[
5490         'base',
5491         'runtime/html5/runtime'
5492     ], function( Base, Html5Runtime ) {
5493     
5494         var noop = Base.noop,
5495             $ = Base.$;
5496     
5497         return Html5Runtime.register( 'Transport', {
5498             init: function() {
5499                 this._status = 0;
5500                 this._response = null;
5501             },
5502     
5503             send: function() {
5504                 var owner = this.owner,
5505                     opts = this.options,
5506                     xhr = this._initAjax(),
5507                     blob = owner._blob,
5508                     server = opts.server,
5509                     formData, binary, fr;
5510     
5511                 if ( opts.sendAsBinary ) {
5512                     server += (/\?/.test( server ) ? '&' : '?') +
5513                             $.param( owner._formData );
5514     
5515                     binary = blob.getSource();
5516                 } else {
5517                     formData = new FormData();
5518                     $.each( owner._formData, function( k, v ) {
5519                         formData.append( k, v );
5520                     });
5521     
5522                     formData.append( opts.fileVal, blob.getSource(),
5523                             opts.filename || owner._formData.name || '' );
5524                 }
5525     
5526                 if ( opts.withCredentials && 'withCredentials' in xhr ) {
5527                     xhr.open( opts.method, server, true );
5528                     xhr.withCredentials = true;
5529                 } else {
5530                     xhr.open( opts.method, server );
5531                 }
5532     
5533                 this._setRequestHeader( xhr, opts.headers );
5534     
5535                 if ( binary ) {
5536                     xhr.overrideMimeType('application/octet-stream');
5537     
5538                     // android直接发送blob会导致服务端接收到的是空文件。
5539                     // bug详情。
5540                     // https://code.google.com/p/android/issues/detail?id=39882
5541                     // 所以先用fileReader读取出来再通过arraybuffer的方式发送。
5542                     if ( Base.os.android ) {
5543                         fr = new FileReader();
5544     
5545                         fr.onload = function() {
5546                             xhr.send( this.result );
5547                             fr = fr.onload = null;
5548                         };
5549     
5550                         fr.readAsArrayBuffer( binary );
5551                     } else {
5552                         xhr.send( binary );
5553                     }
5554                 } else {
5555                     xhr.send( formData );
5556                 }
5557             },
5558     
5559             getResponse: function() {
5560                 return this._response;
5561             },
5562     
5563             getResponseAsJson: function() {
5564                 return this._parseJson( this._response );
5565             },
5566     
5567             getStatus: function() {
5568                 return this._status;
5569             },
5570     
5571             abort: function() {
5572                 var xhr = this._xhr;
5573     
5574                 if ( xhr ) {
5575                     xhr.upload.onprogress = noop;
5576                     xhr.onreadystatechange = noop;
5577                     xhr.abort();
5578     
5579                     this._xhr = xhr = null;
5580                 }
5581             },
5582     
5583             destroy: function() {
5584                 this.abort();
5585             },
5586     
5587             _initAjax: function() {
5588                 var me = this,
5589                     xhr = new XMLHttpRequest(),
5590                     opts = this.options;
5591     
5592                 if ( opts.withCredentials && !('withCredentials' in xhr) &&
5593                         typeof XDomainRequest !== 'undefined' ) {
5594                     xhr = new XDomainRequest();
5595                 }
5596     
5597                 xhr.upload.onprogress = function( e ) {
5598                     var percentage = 0;
5599     
5600                     if ( e.lengthComputable ) {
5601                         percentage = e.loaded / e.total;
5602                     }
5603     
5604                     return me.trigger( 'progress', percentage );
5605                 };
5606     
5607                 xhr.onreadystatechange = function() {
5608     
5609                     if ( xhr.readyState !== 4 ) {
5610                         return;
5611                     }
5612     
5613                     xhr.upload.onprogress = noop;
5614                     xhr.onreadystatechange = noop;
5615                     me._xhr = null;
5616                     me._status = xhr.status;
5617     
5618                     if ( xhr.status >= 200 && xhr.status < 300 ) {
5619                         me._response = xhr.responseText;
5620                         return me.trigger('load');
5621                     } else if ( xhr.status >= 500 && xhr.status < 600 ) {
5622                         me._response = xhr.responseText;
5623                         return me.trigger( 'error', 'server' );
5624                     }
5625     
5626     
5627                     return me.trigger( 'error', me._status ? 'http' : 'abort' );
5628                 };
5629     
5630                 me._xhr = xhr;
5631                 return xhr;
5632             },
5633     
5634             _setRequestHeader: function( xhr, headers ) {
5635                 $.each( headers, function( key, val ) {
5636                     xhr.setRequestHeader( key, val );
5637                 });
5638             },
5639     
5640             _parseJson: function( str ) {
5641                 var json;
5642     
5643                 try {
5644                     json = JSON.parse( str );
5645                 } catch ( ex ) {
5646                     json = {};
5647                 }
5648     
5649                 return json;
5650             }
5651         });
5652     });
5653     define('webuploader',[
5654         'base',
5655         'widgets/filepicker',
5656         'widgets/image',
5657         'widgets/queue',
5658         'widgets/runtime',
5659         'widgets/upload',
5660         'runtime/html5/blob',
5661         'runtime/html5/filepicker',
5662         'runtime/html5/imagemeta/exif',
5663         'runtime/html5/image',
5664         'runtime/html5/androidpatch',
5665         'runtime/html5/transport'
5666     ], function( Base ) {
5667         return Base;
5668     });
5669     return require('assets/common/editor/third-party/webuploader/webuploader');
5670 });