You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2860 lines
74 KiB
JavaScript

/*! Jcrop.js v2.0.4 - build: 20151117
* @copyright 2008-2015 Tapmodo Interactive LLC
* @license Free software under MIT License
* @website http://jcrop.org/
**/
(function($){
'use strict';
// Jcrop constructor
var Jcrop = function(element,opt){
var _ua = navigator.userAgent.toLowerCase();
this.opt = $.extend({},Jcrop.defaults,opt || {});
this.container = $(element);
this.opt.is_msie = /msie/.test(_ua);
this.opt.is_ie_lt9 = /msie [1-8]\./.test(_ua);
this.container.addClass(this.opt.css_container);
this.ui = {};
this.state = null;
this.ui.multi = [];
this.ui.selection = null;
this.filter = {};
this.init();
this.setOptions(opt);
this.applySizeConstraints();
this.container.trigger('cropinit',this);
// IE<9 doesn't work if mouse events are attached to window
if (this.opt.is_ie_lt9)
this.opt.dragEventTarget = document.body;
};
// Jcrop static functions
$.extend(Jcrop,{
component: { },
filter: { },
stage: { },
registerComponent: function(name,component){
Jcrop.component[name] = component;
},
registerFilter: function(name,filter){
Jcrop.filter[name] = filter;
},
registerStageType: function(name,stage){
Jcrop.stage[name] = stage;
},
// attach: function(element,opt){{{
attach: function(element,opt){
var obj = new $.Jcrop(element,opt);
return obj;
},
// }}}
// imgCopy: function(imgel){{{
imgCopy: function(imgel){
var img = new Image;
img.src = imgel.src;
return img;
},
// }}}
// imageClone: function(imgel){{{
imageClone: function(imgel){
return $.Jcrop.supportsCanvas?
Jcrop.canvasClone(imgel):
Jcrop.imgCopy(imgel);
},
// }}}
// canvasClone: function(imgel){{{
canvasClone: function(imgel){
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
$(canvas).width(imgel.width).height(imgel.height),
canvas.width = imgel.naturalWidth;
canvas.height = imgel.naturalHeight;
ctx.drawImage(imgel,0,0,imgel.naturalWidth,imgel.naturalHeight);
return canvas;
},
// }}}
// propagate: function(plist,config,obj){{{
propagate: function(plist,config,obj){
for(var i=0,l=plist.length;i<l;i++)
if (config.hasOwnProperty(plist[i]))
obj[plist[i]] = config[plist[i]];
},
// }}}
// getLargestBox: function(ratio,w,h){{{
getLargestBox: function(ratio,w,h){
if ((w/h) > ratio)
return [ h * ratio, h ];
else return [ w, w / ratio ];
},
// }}}
// stageConstructor: function(el,options,callback){{{
stageConstructor: function(el,options,callback){
// Get a priority-ordered list of available stages
var stages = [];
$.each(Jcrop.stage,function(i,e){
stages.push(e);
});
stages.sort(function(a,b){ return a.priority - b.priority; });
// Find the first one that supports this element
for(var i=0,l=stages.length;i<l;i++){
if (stages[i].isSupported(el,options)){
stages[i].create(el,options,function(obj,opt){
if (typeof callback == 'function') callback(obj,opt);
});
break;
}
}
},
// }}}
// supportsColorFade: function(){{{
supportsColorFade: function(){
return $.fx.step.hasOwnProperty('backgroundColor');
},
// }}}
// wrapFromXywh: function(xywh){{{
wrapFromXywh: function(xywh){
var b = { x: xywh[0], y: xywh[1], w: xywh[2], h: xywh[3] };
b.x2 = b.x + b.w;
b.y2 = b.y + b.h;
return b;
}
// }}}
});
var AbstractStage = function(){
};
$.extend(AbstractStage,{
isSupported: function(el,o){
// @todo: should actually check if it's an HTML element
return true;
},
// A higher priority means less desirable
// AbstractStage is the last one we want to use
priority: 100,
create: function(el,options,callback){
var obj = new AbstractStage;
obj.element = el;
callback.call(this,obj,options);
},
prototype: {
attach: function(core){
this.init(core);
core.ui.stage = this;
},
triggerEvent: function(ev){
$(this.element).trigger(ev);
return this;
},
getElement: function(){
return this.element;
}
}
});
Jcrop.registerStageType('Block',AbstractStage);
var ImageStage = function(){
};
ImageStage.prototype = new AbstractStage();
$.extend(ImageStage,{
isSupported: function(el,o){
if (el.tagName == 'IMG') return true;
},
priority: 90,
create: function(el,options,callback){
$.Jcrop.component.ImageLoader.attach(el,function(w,h){
var obj = new ImageStage;
obj.element = $(el).wrap('<div />').parent();
obj.element.width(w).height(h);
obj.imgsrc = el;
if (typeof callback == 'function')
callback.call(this,obj,options);
});
}
});
Jcrop.registerStageType('Image',ImageStage);
var CanvasStage = function(){
this.angle = 0;
this.scale = 1;
this.scaleMin = 0.2;
this.scaleMax = 1.25;
this.offset = [0,0];
};
CanvasStage.prototype = new ImageStage();
$.extend(CanvasStage,{
isSupported: function(el,o){
if ($.Jcrop.supportsCanvas && (el.tagName == 'IMG')) return true;
},
priority: 60,
create: function(el,options,callback){
var $el = $(el);
var opt = $.extend({},options);
$.Jcrop.component.ImageLoader.attach(el,function(w,h){
var obj = new CanvasStage;
$el.hide();
obj.createCanvas(el,w,h);
$el.before(obj.element);
obj.imgsrc = el;
opt.imgsrc = el;
if (typeof callback == 'function'){
callback(obj,opt);
obj.redraw();
}
});
}
});
$.extend(CanvasStage.prototype,{
init: function(core){
this.core = core;
},
// setOffset: function(x,y) {{{
setOffset: function(x,y) {
this.offset = [x,y];
return this;
},
// }}}
// setAngle: function(v) {{{
setAngle: function(v) {
this.angle = v;
return this;
},
// }}}
// setScale: function(v) {{{
setScale: function(v) {
this.scale = this.boundScale(v);
return this;
},
// }}}
boundScale: function(v){
if (v<this.scaleMin) v = this.scaleMin;
else if (v>this.scaleMax) v = this.scaleMax;
return v;
},
createCanvas: function(img,w,h){
this.width = w;
this.height = h;
this.canvas = document.createElement('canvas');
this.canvas.width = w;
this.canvas.height = h;
this.$canvas = $(this.canvas).width('100%').height('100%');
this.context = this.canvas.getContext('2d');
this.fillstyle = "rgb(0,0,0)";
this.element = this.$canvas.wrap('<div />').parent().width(w).height(h);
},
triggerEvent: function(ev){
this.$canvas.trigger(ev);
return this;
},
// clear: function() {{{
clear: function() {
this.context.fillStyle = this.fillstyle;
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
return this;
},
// }}}
// redraw: function() {{{
redraw: function() {
// Save the current context
this.context.save();
this.clear();
// Translate to the center point of our image
this.context.translate(parseInt(this.width * 0.5), parseInt(this.height * 0.5));
// Perform the rotation and scaling
this.context.translate(this.offset[0]/this.core.opt.xscale,this.offset[1]/this.core.opt.yscale);
this.context.rotate(this.angle * (Math.PI/180));
this.context.scale(this.scale,this.scale);
// Translate back to the top left of our image
this.context.translate(-parseInt(this.width * 0.5), -parseInt(this.height * 0.5));
// Finally we draw the image
this.context.drawImage(this.imgsrc,0,0,this.width,this.height);
// And restore the updated context
this.context.restore();
this.$canvas.trigger('cropredraw');
return this;
},
// }}}
// setFillStyle: function(v) {{{
setFillStyle: function(v) {
this.fillstyle = v;
return this;
}
// }}}
});
Jcrop.registerStageType('Canvas',CanvasStage);
/**
* BackoffFilter
* move out-of-bounds selection into allowed position at same size
*/
var BackoffFilter = function(){
this.minw = 40;
this.minh = 40;
this.maxw = 0;
this.maxh = 0;
this.core = null;
};
$.extend(BackoffFilter.prototype,{
tag: 'backoff',
priority: 22,
filter: function(b){
var r = this.bound;
if (b.x < r.minx) { b.x = r.minx; b.x2 = b.w + b.x; }
if (b.y < r.miny) { b.y = r.miny; b.y2 = b.h + b.y; }
if (b.x2 > r.maxx) { b.x2 = r.maxx; b.x = b.x2 - b.w; }
if (b.y2 > r.maxy) { b.y2 = r.maxy; b.y = b.y2 - b.h; }
return b;
},
refresh: function(sel){
this.elw = sel.core.container.width();
this.elh = sel.core.container.height();
this.bound = {
minx: 0 + sel.edge.w,
miny: 0 + sel.edge.n,
maxx: this.elw + sel.edge.e,
maxy: this.elh + sel.edge.s
};
}
});
Jcrop.registerFilter('backoff',BackoffFilter);
/**
* ConstrainFilter
* a filter to constrain crop selection to bounding element
*/
var ConstrainFilter = function(){
this.core = null;
};
$.extend(ConstrainFilter.prototype,{
tag: 'constrain',
priority: 5,
filter: function(b,ord){
if (ord == 'move') {
if (b.x < this.minx) { b.x = this.minx; b.x2 = b.w + b.x; }
if (b.y < this.miny) { b.y = this.miny; b.y2 = b.h + b.y; }
if (b.x2 > this.maxx) { b.x2 = this.maxx; b.x = b.x2 - b.w; }
if (b.y2 > this.maxy) { b.y2 = this.maxy; b.y = b.y2 - b.h; }
} else {
if (b.x < this.minx) { b.x = this.minx; }
if (b.y < this.miny) { b.y = this.miny; }
if (b.x2 > this.maxx) { b.x2 = this.maxx; }
if (b.y2 > this.maxy) { b.y2 = this.maxy; }
}
b.w = b.x2 - b.x;
b.h = b.y2 - b.y;
return b;
},
refresh: function(sel){
this.elw = sel.core.container.width();
this.elh = sel.core.container.height();
this.minx = 0 + sel.edge.w;
this.miny = 0 + sel.edge.n;
this.maxx = this.elw + sel.edge.e;
this.maxy = this.elh + sel.edge.s;
}
});
Jcrop.registerFilter('constrain',ConstrainFilter);
/**
* ExtentFilter
* a filter to implement minimum or maximum size
*/
var ExtentFilter = function(){
this.core = null;
};
$.extend(ExtentFilter.prototype,{
tag: 'extent',
priority: 12,
offsetFromCorner: function(corner,box,b){
var w = box[0], h = box[1];
switch(corner){
case 'bl': return [ b.x2 - w, b.y, w, h ];
case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
case 'br': return [ b.x, b.y, w, h ];
case 'tr': return [ b.x, b.y2 - h, w, h ];
}
},
getQuadrant: function(s){
var relx = s.opposite[0]-s.offsetx
var rely = s.opposite[1]-s.offsety;
if ((relx < 0) && (rely < 0)) return 'br';
else if ((relx >= 0) && (rely >= 0)) return 'tl';
else if ((relx < 0) && (rely >= 0)) return 'tr';
return 'bl';
},
filter: function(b,ord,sel){
if (ord == 'move') return b;
var w = b.w, h = b.h, st = sel.state, r = this.limits;
var quad = st? this.getQuadrant(st): 'br';
if (r.minw && (w < r.minw)) w = r.minw;
if (r.minh && (h < r.minh)) h = r.minh;
if (r.maxw && (w > r.maxw)) w = r.maxw;
if (r.maxh && (h > r.maxh)) h = r.maxh;
if ((w == b.w) && (h == b.h)) return b;
return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,[w,h],b));
},
refresh: function(sel){
this.elw = sel.core.container.width();
this.elh = sel.core.container.height();
this.limits = {
minw: sel.minSize[0],
minh: sel.minSize[1],
maxw: sel.maxSize[0],
maxh: sel.maxSize[1]
};
}
});
Jcrop.registerFilter('extent',ExtentFilter);
/**
* GridFilter
* a rudimentary grid effect
*/
var GridFilter = function(){
this.stepx = 1;
this.stepy = 1;
this.core = null;
};
$.extend(GridFilter.prototype,{
tag: 'grid',
priority: 19,
filter: function(b){
var n = {
x: Math.round(b.x / this.stepx) * this.stepx,
y: Math.round(b.y / this.stepy) * this.stepy,
x2: Math.round(b.x2 / this.stepx) * this.stepx,
y2: Math.round(b.y2 / this.stepy) * this.stepy
};
n.w = n.x2 - n.x;
n.h = n.y2 - n.y;
return n;
}
});
Jcrop.registerFilter('grid',GridFilter);
/**
* RatioFilter
* implements aspectRatio locking
*/
var RatioFilter = function(){
this.ratio = 0;
this.core = null;
};
$.extend(RatioFilter.prototype,{
tag: 'ratio',
priority: 15,
offsetFromCorner: function(corner,box,b){
var w = box[0], h = box[1];
switch(corner){
case 'bl': return [ b.x2 - w, b.y, w, h ];
case 'tl': return [ b.x2 - w , b.y2 - h, w, h ];
case 'br': return [ b.x, b.y, w, h ];
case 'tr': return [ b.x, b.y2 - h, w, h ];
}
},
getBoundRatio: function(b,quad){
var box = Jcrop.getLargestBox(this.ratio,b.w,b.h);
return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,box,b));
},
getQuadrant: function(s){
var relx = s.opposite[0]-s.offsetx
var rely = s.opposite[1]-s.offsety;
if ((relx < 0) && (rely < 0)) return 'br';
else if ((relx >= 0) && (rely >= 0)) return 'tl';
else if ((relx < 0) && (rely >= 0)) return 'tr';
return 'bl';
},
filter: function(b,ord,sel){
if (!this.ratio) return b;
var rt = b.w / b.h;
var st = sel.state;
var quad = st? this.getQuadrant(st): 'br';
ord = ord || 'se';
if (ord == 'move') return b;
switch(ord) {
case 'n':
b.x2 = this.elw;
b.w = b.x2 - b.x;
quad = 'tr';
break;
case 's':
b.x2 = this.elw;
b.w = b.x2 - b.x;
quad = 'br';
break;
case 'e':
b.y2 = this.elh;
b.h = b.y2 - b.y;
quad = 'br';
break;
case 'w':
b.y2 = this.elh;
b.h = b.y2 - b.y;
quad = 'bl';
break;
}
return this.getBoundRatio(b,quad);
},
refresh: function(sel){
this.ratio = sel.aspectRatio;
this.elw = sel.core.container.width();
this.elh = sel.core.container.height();
}
});
Jcrop.registerFilter('ratio',RatioFilter);
/**
* RoundFilter
* rounds coordinate values to integers
*/
var RoundFilter = function(){
this.core = null;
};
$.extend(RoundFilter.prototype,{
tag: 'round',
priority: 90,
filter: function(b){
var n = {
x: Math.round(b.x),
y: Math.round(b.y),
x2: Math.round(b.x2),
y2: Math.round(b.y2)
};
n.w = n.x2 - n.x;
n.h = n.y2 - n.y;
return n;
}
});
Jcrop.registerFilter('round',RoundFilter);
/**
* ShadeFilter
* A filter that implements div-based shading on any element
*
* The shading you see is actually four semi-opaque divs
* positioned inside the container, around the selection
*/
var ShadeFilter = function(opacity,color){
this.color = color || 'black';
this.opacity = opacity || 0.5;
this.core = null;
this.shades = {};
};
$.extend(ShadeFilter.prototype,{
tag: 'shader',
fade: true,
fadeEasing: 'swing',
fadeSpeed: 320,
priority: 95,
init: function(){
var t = this;
if (!t.attached) {
t.visible = false;
t.container = $('<div />').addClass(t.core.opt.css_shades)
.prependTo(this.core.container).hide();
t.elh = this.core.container.height();
t.elw = this.core.container.width();
t.shades = {
top: t.createShade(),
right: t.createShade(),
left: t.createShade(),
bottom: t.createShade()
};
t.attached = true;
}
},
destroy: function(){
this.container.remove();
},
setColor: function(color,instant){
var t = this;
if (color == t.color) return t;
this.color = color;
var colorfade = Jcrop.supportsColorFade();
$.each(t.shades,function(u,i){
if (!t.fade || instant || !colorfade) i.css('backgroundColor',color);
else i.animate({backgroundColor:color},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing});
});
return t;
},
setOpacity: function(opacity,instant){
var t = this;
if (opacity == t.opacity) return t;
t.opacity = opacity;
$.each(t.shades,function(u,i){
if (!t.fade || instant) i.css({opacity:opacity});
else i.animate({opacity:opacity},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing});
});
return t;
},
createShade: function(){
return $('<div />').css({
position: 'absolute',
backgroundColor: this.color,
opacity: this.opacity
}).appendTo(this.container);
},
refresh: function(sel){
var m = this.core, s = this.shades;
this.setColor(sel.bgColor?sel.bgColor:this.core.opt.bgColor);
this.setOpacity(sel.bgOpacity?sel.bgOpacity:this.core.opt.bgOpacity);
this.elh = m.container.height();
this.elw = m.container.width();
s.right.css('height',this.elh+'px');
s.left.css('height',this.elh+'px');
},
filter: function(b,ord,sel){
if (!sel.active) return b;
var t = this,
s = t.shades;
s.top.css({
left: Math.round(b.x)+'px',
width: Math.round(b.w)+'px',
height: Math.round(b.y)+'px'
});
s.bottom.css({
top: Math.round(b.y2)+'px',
left: Math.round(b.x)+'px',
width: Math.round(b.w)+'px',
height: (t.elh-Math.round(b.y2))+'px'
});
s.right.css({
left: Math.round(b.x2)+'px',
width: (t.elw-Math.round(b.x2))+'px'
});
s.left.css({
width: Math.round(b.x)+'px'
});
if (!t.visible) {
t.container.show();
t.visible = true;
}
return b;
}
});
Jcrop.registerFilter('shader',ShadeFilter);
/**
* CanvasAnimator
* manages smooth cropping animation
*
* This object is called internally to manage animation.
* An in-memory div is animated and a progress callback
* is used to update the selection coordinates of the
* visible selection in realtime.
*/
var CanvasAnimator = function(stage){
this.stage = stage;
this.core = stage.core;
this.cloneStagePosition();
};
CanvasAnimator.prototype = {
cloneStagePosition: function(){
var s = this.stage;
this.angle = s.angle;
this.scale = s.scale;
this.offset = s.offset;
},
getElement: function(){
var s = this.stage;
return $('<div />')
.css({
position: 'absolute',
top: s.offset[0]+'px',
left: s.offset[1]+'px',
width: s.angle+'px',
height: s.scale+'px'
});
},
animate: function(cb){
var t = this;
this.scale = this.stage.boundScale(this.scale);
t.stage.triggerEvent('croprotstart');
t.getElement().animate({
top: t.offset[0]+'px',
left: t.offset[1]+'px',
width: t.angle+'px',
height: t.scale+'px'
},{
easing: t.core.opt.animEasing,
duration: t.core.opt.animDuration,
complete: function(){
t.stage.triggerEvent('croprotend');
(typeof cb == 'function') && cb.call(this);
},
progress: function(anim){
var props = {}, i, tw = anim.tweens;
for(i=0;i<tw.length;i++){
props[tw[i].prop] = tw[i].now; }
t.stage.setAngle(props.width)
.setScale(props.height)
.setOffset(props.top,props.left)
.redraw();
}
});
}
};
Jcrop.stage.Canvas.prototype.getAnimator = function(){
return new CanvasAnimator(this);
};
Jcrop.registerComponent('CanvasAnimator',CanvasAnimator);
/**
* CropAnimator
* manages smooth cropping animation
*
* This object is called internally to manage animation.
* An in-memory div is animated and a progress callback
* is used to update the selection coordinates of the
* visible selection in realtime.
*/
// var CropAnimator = function(selection){{{
var CropAnimator = function(selection){
this.selection = selection;
this.core = selection.core;
};
// }}}
CropAnimator.prototype = {
getElement: function(){
var b = this.selection.get();
return $('<div />')
.css({
position: 'absolute',
top: b.y+'px',
left: b.x+'px',
width: b.w+'px',
height: b.h+'px'
});
},
animate: function(x,y,w,h,cb){
var t = this;
t.selection.allowResize(false);
t.getElement().animate({
top: y+'px',
left: x+'px',
width: w+'px',
height: h+'px'
},{
easing: t.core.opt.animEasing,
duration: t.core.opt.animDuration,
complete: function(){
t.selection.allowResize(true);
cb && cb.call(this);
},
progress: function(anim){
var props = {}, i, tw = anim.tweens;
for(i=0;i<tw.length;i++){
props[tw[i].prop] = tw[i].now; }
var b = {
x: parseInt(props.left),
y: parseInt(props.top),
w: parseInt(props.width),
h: parseInt(props.height)
};
b.x2 = b.x + b.w;
b.y2 = b.y + b.h;
t.selection.updateRaw(b,'se');
}
});
}
};
Jcrop.registerComponent('Animator',CropAnimator);
/**
* DragState
* an object that handles dragging events
*
* This object is used by the built-in selection object to
* track a dragging operation on a selection
*/
// var DragState = function(e,selection,ord){{{
var DragState = function(e,selection,ord){
var t = this;
t.x = e.pageX;
t.y = e.pageY;
t.selection = selection;
t.eventTarget = selection.core.opt.dragEventTarget;
t.orig = selection.get();
selection.callFilterFunction('refresh');
var p = selection.core.container.position();
t.elx = p.left;
t.ely = p.top;
t.offsetx = 0;
t.offsety = 0;
t.ord = ord;
t.opposite = t.getOppositeCornerOffset();
t.initEvents(e);
};
// }}}
DragState.prototype = {
// getOppositeCornerOffset: function(){{{
// Calculate relative offset of locked corner
getOppositeCornerOffset: function(){
var o = this.orig;
var relx = this.x - this.elx - o.x;
var rely = this.y - this.ely - o.y;
switch(this.ord){
case 'nw':
case 'w':
return [ o.w - relx, o.h - rely ];
return [ o.x + o.w, o.y + o.h ];
case 'sw':
return [ o.w - relx, -rely ];
return [ o.x + o.w, o.y ];
case 'se':
case 's':
case 'e':
return [ -relx, -rely ];
return [ o.x, o.y ];
case 'ne':
case 'n':
return [ -relx, o.h - rely ];
return [ o.w, o.y + o.h ];
}
return [ null, null ];
},
// }}}
// initEvents: function(e){{{
initEvents: function(e){
$(this.eventTarget)
.on('mousemove.jcrop',this.createDragHandler())
.on('mouseup.jcrop',this.createStopHandler());
},
// }}}
// dragEvent: function(e){{{
dragEvent: function(e){
this.offsetx = e.pageX - this.x;
this.offsety = e.pageY - this.y;
this.selection.updateRaw(this.getBox(),this.ord);
},
// }}}
// endDragEvent: function(e){{{
endDragEvent: function(e){
var sel = this.selection;
sel.core.container.removeClass('jcrop-dragging');
sel.element.trigger('cropend',[sel,sel.core.unscale(sel.get())]);
sel.focus();
},
// }}}
// createStopHandler: function(){{{
createStopHandler: function(){
var t = this;
return function(e){
$(t.eventTarget).off('.jcrop');
t.endDragEvent(e);
return false;
};
},
// }}}
// createDragHandler: function(){{{
createDragHandler: function(){
var t = this;
return function(e){
t.dragEvent(e);
return false;
};
},
// }}}
//update: function(x,y){{{
update: function(x,y){
var t = this;
t.offsetx = x - t.x;
t.offsety = y - t.y;
},
//}}}
//resultWrap: function(d){{{
resultWrap: function(d){
var b = {
x: Math.min(d[0],d[2]),
y: Math.min(d[1],d[3]),
x2: Math.max(d[0],d[2]),
y2: Math.max(d[1],d[3])
};
b.w = b.x2 - b.x;
b.h = b.y2 - b.y;
return b;
},
//}}}
//getBox: function(){{{
getBox: function(){
var t = this;
var o = t.orig;
var _c = { x2: o.x + o.w, y2: o.y + o.h };
switch(t.ord){
case 'n': return t.resultWrap([ o.x, t.offsety + o.y, _c.x2, _c.y2 ]);
case 's': return t.resultWrap([ o.x, o.y, _c.x2, t.offsety + _c.y2 ]);
case 'e': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, _c.y2 ]);
case 'w': return t.resultWrap([ o.x + t.offsetx, o.y, _c.x2, _c.y2 ]);
case 'sw': return t.resultWrap([ t.offsetx + o.x, o.y, _c.x2, t.offsety + _c.y2 ]);
case 'se': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, t.offsety + _c.y2 ]);
case 'ne': return t.resultWrap([ o.x, t.offsety + o.y, t.offsetx + _c.x2, _c.y2 ]);
case 'nw': return t.resultWrap([ t.offsetx + o.x, t.offsety + o.y, _c.x2, _c.y2 ]);
case 'move':
_c.nx = o.x + t.offsetx;
_c.ny = o.y + t.offsety;
return t.resultWrap([ _c.nx, _c.ny, _c.nx + o.w, _c.ny + o.h ]);
}
}
//}}}
};
Jcrop.registerComponent('DragState',DragState);
/**
* EventManager
* provides internal event support
*/
var EventManager = function(core){
this.core = core;
};
EventManager.prototype = {
on: function(n,cb){ $(this).on(n,cb); },
off: function(n){ $(this).off(n); },
trigger: function(n){ $(this).trigger(n); }
};
Jcrop.registerComponent('EventManager',EventManager);
/**
* Image Loader
* Reliably pre-loads images
*/
// var ImageLoader = function(src,element,cb){{{
var ImageLoader = function(src,element,cb){
this.src = src;
if (!element) element = new Image;
this.element = element;
this.callback = cb;
this.load();
};
// }}}
$.extend(ImageLoader,{
// attach: function(el,cb){{{
attach: function(el,cb){
return new ImageLoader(el.src,el,cb);
},
// }}}
// prototype: {{{
prototype: {
getDimensions: function(){
var el = this.element;
if (el.naturalWidth)
return [ el.naturalWidth, el. naturalHeight ];
if (el.width)
return [ el.width, el.height ];
return null;
},
fireCallback: function(){
this.element.onload = null;
if (typeof this.callback == 'function')
this.callback.apply(this,this.getDimensions());
},
isLoaded: function(){
return this.element.complete;
},
load: function(){
var t = this;
var el = t.element;
el.src = t.src;
if (t.isLoaded()) t.fireCallback();
else t.element.onload = function(e){
t.fireCallback();
};
}
}
// }}}
});
Jcrop.registerComponent('ImageLoader',ImageLoader);
/**
* JcropTouch
* Detects and enables mobile touch support
*/
// var JcropTouch = function(core){{{
var JcropTouch = function(core){
this.core = core;
this.init();
};
// }}}
$.extend(JcropTouch,{
// support: function(){{{
support: function(){
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch)
return true;
},
// }}}
prototype: {
// init: function(){{{
init: function(){
var t = this,
p = $.Jcrop.component.DragState.prototype;
// A bit of an ugly hack to make sure we modify prototype
// only once, store a key on the prototype
if (!p.touch) {
t.initEvents();
t.shimDragState();
t.shimStageDrag();
p.touch = true;
}
},
// }}}
// shimDragState: function(){{{
shimDragState: function(){
var t = this;
$.Jcrop.component.DragState.prototype.initEvents = function(e){
// Attach subsequent drag event handlers based on initial
// event type - avoids collecting "pseudo-mouse" events
// generated by some mobile browsers in some circumstances
if (e.type.substr(0,5) == 'touch') {
$(this.eventTarget)
.on('touchmove.jcrop.jcrop-touch',t.dragWrap(this.createDragHandler()))
.on('touchend.jcrop.jcrop-touch',this.createStopHandler());
}
// For other events, use the mouse handlers that
// the default DragState.initEvents() method sets...
else {
$(this.eventTarget)
.on('mousemove.jcrop',this.createDragHandler())
.on('mouseup.jcrop',this.createStopHandler());
}
};
},
// }}}
// shimStageDrag: function(){{{
shimStageDrag: function(){
this.core.container
.addClass('jcrop-touch')
.on('touchstart.jcrop.jcrop-stage',this.dragWrap(this.core.ui.manager.startDragHandler()));
},
// }}}
// dragWrap: function(cb){{{
dragWrap: function(cb){
return function(e){
e.preventDefault();
e.stopPropagation();
if (e.type.substr(0,5) == 'touch') {
e.pageX = e.originalEvent.changedTouches[0].pageX;
e.pageY = e.originalEvent.changedTouches[0].pageY;
return cb(e);
}
return false;
};
},
// }}}
// initEvents: function(){{{
initEvents: function(){
var t = this, c = t.core;
c.container.on(
'touchstart.jcrop.jcrop-touch',
'.'+c.opt.css_drag,
t.dragWrap(c.startDrag())
);
}
// }}}
}
});
Jcrop.registerComponent('Touch',JcropTouch);
/**
* KeyWatcher
* provides keyboard support
*/
// var KeyWatcher = function(core){{{
var KeyWatcher = function(core){
this.core = core;
this.init();
};
// }}}
$.extend(KeyWatcher,{
// defaults: {{{
defaults: {
eventName: 'keydown.jcrop',
passthru: [ 9 ],
debug: false
},
// }}}
prototype: {
// init: function(){{{
init: function(){
$.extend(this,KeyWatcher.defaults);
this.enable();
},
// }}}
// disable: function(){{{
disable: function(){
this.core.container.off(this.eventName);
},
// }}}
// enable: function(){{{
enable: function(){
var t = this, m = t.core;
m.container.on(t.eventName,function(e){
var nudge = e.shiftKey? 16: 2;
if ($.inArray(e.keyCode,t.passthru) >= 0)
return true;
switch(e.keyCode){
case 37: m.nudge(-nudge,0); break;
case 38: m.nudge(0,-nudge); break;
case 39: m.nudge(nudge,0); break;
case 40: m.nudge(0,nudge); break;
case 46:
case 8:
m.requestDelete();
return false;
break;
default:
if (t.debug) console.log('keycode: ' + e.keyCode);
break;
}
if (!e.metaKey && !e.ctrlKey)
e.preventDefault();
});
}
// }}}
}
});
Jcrop.registerComponent('Keyboard',KeyWatcher);
/**
* Selection
* Built-in selection object
*/
var Selection = function(){};
$.extend(Selection,{
// defaults: {{{
defaults: {
minSize: [ 8, 8 ],
maxSize: [ 0, 0 ],
aspectRatio: 0,
edge: { n: 0, s: 0, e: 0, w: 0 },
bgColor: null,
bgOpacity: null,
last: null,
state: null,
active: true,
linked: true,
canDelete: true,
canDrag: true,
canResize: true,
canSelect: true
},
// }}}
prototype: {
// init: function(core){{{
init: function(core){
this.core = core;
this.startup();
this.linked = this.core.opt.linked;
this.attach();
this.setOptions(this.core.opt);
core.container.trigger('cropcreate',[this]);
},
// }}}
// attach: function(){{{
attach: function(){
// For extending init() sequence
},
// }}}
// startup: function(){{{
startup: function(){
var t = this, o = t.core.opt;
$.extend(t,Selection.defaults);
t.filter = t.core.getDefaultFilters();
t.element = $('<div />').addClass(o.css_selection).data({ selection: t });
t.frame = $('<button />').addClass(o.css_button).data('ord','move').attr('type','button');
t.element.append(t.frame).appendTo(t.core.container);
// IE background/draggable hack
if (t.core.opt.is_msie) t.frame.css({
opacity: 0,
backgroundColor: 'white'
});
t.insertElements();
// Bind focus and blur events for this selection
t.frame.on('focus.jcrop',function(e){
t.core.setSelection(t);
t.element.trigger('cropfocus',t);
t.element.addClass('jcrop-focus');
}).on('blur.jcrop',function(e){
t.element.removeClass('jcrop-focus');
t.element.trigger('cropblur',t);
});
},
// }}}
// propagate: [{{{
propagate: [
'canDelete', 'canDrag', 'canResize', 'canSelect',
'minSize', 'maxSize', 'aspectRatio', 'edge'
],
// }}}
// setOptions: function(opt){{{
setOptions: function(opt){
Jcrop.propagate(this.propagate,opt,this);
this.refresh();
return this;
},
// }}}
// refresh: function(){{{
refresh: function(){
this.allowResize();
this.allowDrag();
this.allowSelect();
this.callFilterFunction('refresh');
this.updateRaw(this.get(),'se');
},
// }}}
// callFilterFunction: function(f,args){{{
callFilterFunction: function(f,args){
for(var i=0;i<this.filter.length;i++)
if (this.filter[i][f]) this.filter[i][f](this);
return this;
},
// }}}
//addFilter: function(filter){{{
addFilter: function(filter){
filter.core = this.core;
if (!this.hasFilter(filter)) {
this.filter.push(filter);
this.sortFilters();
if (filter.init) filter.init();
this.refresh();
}
},
//}}}
// hasFilter: function(filter){{{
hasFilter: function(filter){
var i, f = this.filter, n = [];
for(i=0;i<f.length;i++) if (f[i] === filter) return true;
},
// }}}
// sortFilters: function(){{{
sortFilters: function(){
this.filter.sort(
function(x,y){ return x.priority - y.priority; }
);
},
// }}}
//clearFilters: function(){{{
clearFilters: function(){
var i, f = this.filter;
for(var i=0;i<f.length;i++)
if (f[i].destroy) f[i].destroy();
this.filter = [];
},
//}}}
// removeFiltersByTag: function(tag){{{
removeFilter: function(tag){
var i, f = this.filter, n = [];
for(var i=0;i<f.length;i++)
if ((f[i].tag && (f[i].tag == tag)) || (tag === f[i])){
if (f[i].destroy) f[i].destroy();
}
else n.push(f[i]);
this.filter = n;
},
// }}}
// runFilters: function(b,ord){{{
runFilters: function(b,ord){
for(var i=0;i<this.filter.length;i++)
b = this.filter[i].filter(b,ord,this);
return b;
},
// }}}
//endDrag: function(){{{
endDrag: function(){
if (this.state) {
$(document.body).off('.jcrop');
this.focus();
this.state = null;
}
},
//}}}
// startDrag: function(e,ord){{{
startDrag: function(e,ord){
var t = this;
var m = t.core;
ord = ord || $(e.target).data('ord');
this.focus();
if ((ord == 'move') && t.element.hasClass(t.core.opt.css_nodrag))
return false;
this.state = new Jcrop.component.DragState(e,this,ord);
return false;
},
// }}}
// allowSelect: function(v){{{
allowSelect: function(v){
if (v === undefined) v = this.canSelect;
if (v && this.canSelect) this.frame.attr('disabled',false);
else this.frame.attr('disabled','disabled');
return this;
},
// }}}
// allowDrag: function(v){{{
allowDrag: function(v){
var t = this, o = t.core.opt;
if (v == undefined) v = t.canDrag;
if (v && t.canDrag) t.element.removeClass(o.css_nodrag);
else t.element.addClass(o.css_nodrag);
return this;
},
// }}}
// allowResize: function(v){{{
allowResize: function(v){
var t = this, o = t.core.opt;
if (v == undefined) v = t.canResize;
if (v && t.canResize) t.element.removeClass(o.css_noresize);
else t.element.addClass(o.css_noresize);
return this;
},
// }}}
// remove: function(){{{
remove: function(){
this.element.trigger('cropremove',this);
this.element.remove();
},
// }}}
// toBack: function(){{{
toBack: function(){
this.active = false;
this.element.removeClass('jcrop-current jcrop-focus');
},
// }}}
// toFront: function(){{{
toFront: function(){
this.active = true;
this.element.addClass('jcrop-current');
this.callFilterFunction('refresh');
this.refresh();
},
// }}}
// redraw: function(b){{{
redraw: function(b){
this.moveTo(b.x,b.y);
this.resize(b.w,b.h);
this.last = b;
return this;
},
// }}}
// update: function(b,ord){{{
update: function(b,ord){
return this.updateRaw(this.core.scale(b),ord);
},
// }}}
// update: function(b,ord){{{
updateRaw: function(b,ord){
b = this.runFilters(b,ord);
this.redraw(b);
this.element.trigger('cropmove',[this,this.core.unscale(b)]);
return this;
},
// }}}
// animateTo: function(box,cb){{{
animateTo: function(box,cb){
var ca = new Jcrop.component.Animator(this),
b = this.core.scale(Jcrop.wrapFromXywh(box));
ca.animate(b.x,b.y,b.w,b.h,cb);
},
// }}}
// center: function(instant){{{
center: function(instant){
var b = this.get(), m = this.core;
var elw = m.container.width(), elh = m.container.height();
var box = [ (elw-b.w)/2, (elh-b.h)/2, b.w, b.h ];
return this[instant?'setSelect':'animateTo'](box);
},
// }}}
//createElement: function(type,ord){{{
createElement: function(type,ord){
return $('<div />').addClass(type+' ord-'+ord).data('ord',ord);
},
//}}}
//moveTo: function(x,y){{{
moveTo: function(x,y){
this.element.css({top: y+'px', left: x+'px'});
},
//}}}
// blur: function(){{{
blur: function(){
this.element.blur();
return this;
},
// }}}
// focus: function(){{{
focus: function(){
this.core.setSelection(this);
this.frame.focus();
return this;
},
// }}}
//resize: function(w,h){{{
resize: function(w,h){
this.element.css({width: w+'px', height: h+'px'});
},
//}}}
//get: function(){{{
get: function(){
var b = this.element,
o = b.position(),
w = b.width(),
h = b.height(),
rv = { x: o.left, y: o.top };
rv.x2 = rv.x + w;
rv.y2 = rv.y + h;
rv.w = w;
rv.h = h;
return rv;
},
//}}}
//insertElements: function(){{{
insertElements: function(){
var t = this, i,
m = t.core,
fr = t.element,
o = t.core.opt,
b = o.borders,
h = o.handles,
d = o.dragbars;
for(i=0; i<d.length; i++)
fr.append(t.createElement(o.css_dragbars,d[i]));
for(i=0; i<h.length; i++)
fr.append(t.createElement(o.css_handles,h[i]));
for(i=0; i<b.length; i++)
fr.append(t.createElement(o.css_borders,b[i]));
}
//}}}
}
});
Jcrop.registerComponent('Selection',Selection);
/**
* StageDrag
* Facilitates dragging
*/
// var StageDrag = function(manager,opt){{{
var StageDrag = function(manager,opt){
$.extend(this,StageDrag.defaults,opt || {});
this.manager = manager;
this.core = manager.core;
};
// }}}
// StageDrag.defaults = {{{
StageDrag.defaults = {
offset: [ -8, -8 ],
active: true,
minsize: [ 20, 20 ]
};
// }}}
$.extend(StageDrag.prototype,{
// start: function(e){{{
start: function(e){
var c = this.core;
// Do nothing if allowSelect is off
if (!c.opt.allowSelect) return;
// Also do nothing if we can't draw any more selections
if (c.opt.multi && c.opt.multiMax && (c.ui.multi.length >= c.opt.multiMax)) return false;
// calculate a few variables for this drag operation
var o = $(e.currentTarget).offset();
var origx = e.pageX - o.left + this.offset[0];
var origy = e.pageY - o.top + this.offset[1];
var m = c.ui.multi;
// Determine newly dragged crop behavior if multi disabled
if (!c.opt.multi) {
// For multiCleaanup true, remove all existing selections
if (c.opt.multiCleanup){
for(var i=0;i<m.length;i++) m[i].remove();
c.ui.multi = [];
}
// If not, only remove the currently active selection
else {
c.removeSelection(c.ui.selection);
}
}
c.container.addClass('jcrop-dragging');
// Create the new selection
var sel = c.newSelection()
// and position it
.updateRaw(Jcrop.wrapFromXywh([origx,origy,1,1]));
sel.element.trigger('cropstart',[sel,this.core.unscale(sel.get())]);
return sel.startDrag(e,'se');
},
// }}}
// end: function(x,y){{{
end: function(x,y){
this.drag(x,y);
var b = this.sel.get();
this.core.container.removeClass('jcrop-dragging');
if ((b.w < this.minsize[0]) || (b.h < this.minsize[1]))
this.core.requestDelete();
else this.sel.focus();
}
// }}}
});
Jcrop.registerComponent('StageDrag',StageDrag);
/**
* StageManager
* Provides basic stage-specific functionality
*/
// var StageManager = function(core){{{
var StageManager = function(core){
this.core = core;
this.ui = core.ui;
this.init();
};
// }}}
$.extend(StageManager.prototype,{
// init: function(){{{
init: function(){
this.setupEvents();
this.dragger = new StageDrag(this);
},
// }}}
// tellConfigUpdate: function(options){{{
tellConfigUpdate: function(options){
for(var i=0,m=this.ui.multi,l=m.length;i<l;i++)
if (m[i].setOptions && (m[i].linked || (this.core.opt.linkCurrent && m[i] == this.ui.selection)))
m[i].setOptions(options);
},
// }}}
// startDragHandler: function(){{{
startDragHandler: function(){
var t = this;
return function(e){
if (!e.button || t.core.opt.is_ie_lt9) return t.dragger.start(e);
};
},
// }}}
// removeEvents: function(){{{
removeEvents: function(){
this.core.event.off('.jcrop-stage');
this.core.container.off('.jcrop-stage');
},
// }}}
// shimLegacyHandlers: function(options){{{
// This method uses the legacyHandlers configuration object to
// gracefully wrap old-style Jcrop events with new ones
shimLegacyHandlers: function(options){
var _x = {}, core = this.core, tmp;
$.each(core.opt.legacyHandlers,function(k,i){
if (k in options) {
tmp = options[k];
core.container.off('.jcrop-'+k)
.on(i+'.jcrop.jcrop-'+k,function(e,s,c){
tmp.call(core,c);
});
delete options[k];
}
});
},
// }}}
// setupEvents: function(){{{
setupEvents: function(){
var t = this, c = t.core;
c.event.on('configupdate.jcrop-stage',function(e){
t.shimLegacyHandlers(c.opt);
t.tellConfigUpdate(c.opt)
c.container.trigger('cropconfig',[c,c.opt]);
});
this.core.container
.on('mousedown.jcrop.jcrop-stage',this.startDragHandler());
}
// }}}
});
Jcrop.registerComponent('StageManager',StageManager);
var Thumbnailer = function(){
};
$.extend(Thumbnailer,{
defaults: {
// Set to a specific Selection object
// If this value is set, the preview will only track that Selection
selection: null,
fading: true,
fadeDelay: 1000,
fadeDuration: 1000,
autoHide: false,
width: 80,
height: 80,
_hiding: null
},
prototype: {
recopyCanvas: function(){
var s = this.core.ui.stage, cxt = s.context;
this.context.putImageData(cxt.getImageData(0,0,s.canvas.width,s.canvas.height),0,0);
},
init: function(core,options){
var t = this;
this.core = core;
$.extend(this,Thumbnailer.defaults,options);
t.initEvents();
t.refresh();
t.insertElements();
if (t.selection) {
t.renderSelection(t.selection);
t.selectionTarget = t.selection.element[0];
} else if (t.core.ui.selection) {
t.renderSelection(t.core.ui.selection);
}
if (t.core.ui.stage.canvas) {
t.context = t.preview[0].getContext('2d');
t.core.container.on('cropredraw',function(e){
t.recopyCanvas();
t.refresh();
});
}
},
updateImage: function(imgel){
this.preview.remove();
this.preview = $($.Jcrop.imageClone(imgel));
this.element.append(this.preview);
this.refresh();
return this;
},
insertElements: function(){
this.preview = $($.Jcrop.imageClone(this.core.ui.stage.imgsrc));
this.element = $('<div />').addClass('jcrop-thumb')
.width(this.width).height(this.height)
.append(this.preview)
.appendTo(this.core.container);
},
resize: function(w,h){
this.width = w;
this.height = h;
this.element.width(w).height(h);
this.renderCoords(this.last);
},
refresh: function(){
this.cw = (this.core.opt.xscale * this.core.container.width());
this.ch = (this.core.opt.yscale * this.core.container.height());
if (this.last) {
this.renderCoords(this.last);
}
},
renderCoords: function(c){
var rx = this.width / c.w;
var ry = this.height / c.h;
this.preview.css({
width: Math.round(rx * this.cw) + 'px',
height: Math.round(ry * this.ch) + 'px',
marginLeft: '-' + Math.round(rx * c.x) + 'px',
marginTop: '-' + Math.round(ry * c.y) + 'px'
});
this.last = c;
return this;
},
renderSelection: function(s){
return this.renderCoords(s.core.unscale(s.get()));
},
selectionStart: function(s){
this.renderSelection(s);
},
show: function(){
if (this._hiding) clearTimeout(this._hiding);
if (!this.fading) this.element.stop().css({ opacity: 1 });
else this.element.stop().animate({ opacity: 1 },{ duration: 80, queue: false });
},
hide: function(){
var t = this;
if (!t.fading) t.element.hide();
else t._hiding = setTimeout(function(){
t._hiding = null;
t.element.stop().animate({ opacity: 0 },{ duration: t.fadeDuration, queue: false });
},t.fadeDelay);
},
initEvents: function(){
var t = this;
t.core.container.on('croprotstart croprotend cropimage cropstart cropmove cropend',function(e,s,c){
if (t.selectionTarget && (t.selectionTarget !== e.target)) return false;
switch(e.type){
case 'cropimage':
t.updateImage(c);
break;
case 'cropstart':
t.selectionStart(s);
case 'croprotstart':
t.show();
break;
case 'cropend':
t.renderCoords(c);
case 'croprotend':
if (t.autoHide) t.hide();
break;
case 'cropmove':
t.renderCoords(c);
break;
}
});
}
}
});
Jcrop.registerComponent('Thumbnailer',Thumbnailer);
/**
* DialDrag component
* This is a little hacky, it was adapted from some previous/old code
* Plan to update this API in the future
*/
var DialDrag = function() { };
DialDrag.prototype = {
init: function(core,actuator,callback){
var that = this;
if (!actuator) actuator = core.container;
this.$btn = $(actuator);
this.$targ = $(actuator);
this.core = core;
this.$btn
.addClass('dialdrag')
.on('mousedown.dialdrag',this.mousedown())
.data('dialdrag',this);
if (!$.isFunction(callback)) callback = function(){ };
this.callback = callback;
this.ondone = callback;
},
remove: function(){
this.$btn
.removeClass('dialdrag')
.off('.dialdrag')
.data('dialdrag',null);
return this;
},
setTarget: function(obj){
this.$targ = $(obj);
return this;
},
getOffset: function(){
var targ = this.$targ, pos = targ.offset();
return [
pos.left + (targ.width()/2),
pos.top + (targ.height()/2)
];
},
relMouse: function(e){
var x = e.pageX - this.offset[0],
y = e.pageY - this.offset[1],
ang = Math.atan2(y,x) * (180 / Math.PI),
vec = Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
return [ x, y, ang, vec ];
},
mousedown: function(){
var that = this;
function mouseUp(e){
$(window).off('.dialdrag');
that.ondone.call(that,that.relMouse(e));
that.core.container.trigger('croprotend');
}
function mouseMove(e){
that.callback.call(that,that.relMouse(e));
}
return function(e) {
that.offset = that.getOffset();
var rel = that.relMouse(e);
that.angleOffset = -that.core.ui.stage.angle+rel[2];
that.distOffset = rel[3];
that.dragOffset = [rel[0],rel[1]];
that.core.container.trigger('croprotstart');
$(window)
.on('mousemove.dialdrag',mouseMove)
.on('mouseup.dialdrag',mouseUp);
that.callback.call(that,that.relMouse(e));
return false;
};
}
};
Jcrop.registerComponent('DialDrag',DialDrag);
/////////////////////////////////
// DEFAULT SETTINGS
Jcrop.defaults = {
// Selection Behavior
edge: { n: 0, s: 0, e: 0, w: 0 },
setSelect: null,
linked: true,
linkCurrent: true,
canDelete: true,
canSelect: true,
canDrag: true,
canResize: true,
// Component constructors
eventManagerComponent: Jcrop.component.EventManager,
keyboardComponent: Jcrop.component.Keyboard,
dragstateComponent: Jcrop.component.DragState,
stagemanagerComponent: Jcrop.component.StageManager,
animatorComponent: Jcrop.component.Animator,
selectionComponent: Jcrop.component.Selection,
// This is a function that is called, which returns a stage object
stageConstructor: Jcrop.stageConstructor,
// Stage Behavior
allowSelect: true,
multi: false,
multiMax: false,
multiCleanup: true,
animation: true,
animEasing: 'swing',
animDuration: 400,
fading: true,
fadeDuration: 300,
fadeEasing: 'swing',
bgColor: 'black',
bgOpacity: .5,
// Startup options
applyFilters: [ 'constrain', 'extent', 'backoff', 'ratio', 'shader', 'round' ],
borders: [ 'e', 'w', 's', 'n' ],
handles: [ 'n', 's', 'e', 'w', 'sw', 'ne', 'nw', 'se' ],
dragbars: [ 'n', 'e', 'w', 's' ],
dragEventTarget: window,
xscale: 1,
yscale: 1,
boxWidth: null,
boxHeight: null,
// CSS Classes
// @todo: These need to be moved to top-level object keys
// for better customization. Currently if you try to extend one
// via an options object to Jcrop, it will wipe out all
// the others you don't specify. Be careful for now!
css_nodrag: 'jcrop-nodrag',
css_drag: 'jcrop-drag',
css_container: 'jcrop-active',
css_shades: 'jcrop-shades',
css_selection: 'jcrop-selection',
css_borders: 'jcrop-border',
css_handles: 'jcrop-handle jcrop-drag',
css_button: 'jcrop-box jcrop-drag',
css_noresize: 'jcrop-noresize',
css_dragbars: 'jcrop-dragbar jcrop-drag',
legacyHandlers: {
onChange: 'cropmove',
onSelect: 'cropend'
}
};
// Jcrop API methods
$.extend(Jcrop.prototype,{
//init: function(){{{
init: function(){
this.event = new this.opt.eventManagerComponent(this);
this.ui.keyboard = new this.opt.keyboardComponent(this);
this.ui.manager = new this.opt.stagemanagerComponent(this);
this.applyFilters();
if ($.Jcrop.supportsTouch)
new $.Jcrop.component.Touch(this);
this.initEvents();
},
//}}}
// applySizeConstraints: function(){{{
applySizeConstraints: function(){
var o = this.opt,
img = this.opt.imgsrc;
if (img){
var iw = img.naturalWidth || img.width,
ih = img.naturalHeight || img.height,
bw = o.boxWidth || iw,
bh = o.boxHeight || ih;
if (img && ((iw > bw) || (ih > bh))){
var bx = Jcrop.getLargestBox(iw/ih,bw,bh);
$(img).width(bx[0]).height(bx[1]);
this.resizeContainer(bx[0],bx[1]);
this.opt.xscale = iw / bx[0];
this.opt.yscale = ih / bx[1];
}
}
if (this.opt.trueSize){
var dw = this.opt.trueSize[0];
var dh = this.opt.trueSize[1];
var cs = this.getContainerSize();
this.opt.xscale = dw / cs[0];
this.opt.yscale = dh / cs[1];
}
},
// }}}
initComponent: function(name){
if (Jcrop.component[name]) {
var args = Array.prototype.slice.call(arguments);
var obj = new Jcrop.component[name];
args.shift();
args.unshift(this);
obj.init.apply(obj,args);
return obj;
}
},
// setOptions: function(opt){{{
setOptions: function(opt,proptype){
if (!$.isPlainObject(opt)) opt = {};
$.extend(this.opt,opt);
// Handle a setSelect value
if (this.opt.setSelect) {
// If there is no current selection
// passing setSelect will create one
if (!this.ui.multi.length)
this.newSelection();
// Use these values to update the current selection
this.setSelect(this.opt.setSelect);
// Set to null so it doesn't get called again
this.opt.setSelect = null;
}
this.event.trigger('configupdate');
return this;
},
// }}}
//destroy: function(){{{
destroy: function(){
if (this.opt.imgsrc) {
this.container.before(this.opt.imgsrc);
this.container.remove();
$(this.opt.imgsrc).removeData('Jcrop').show();
} else {
// @todo: more elegant destroy() process for non-image containers
this.container.remove();
}
},
// }}}
// applyFilters: function(){{{
applyFilters: function(){
var obj;
for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++){
if ($.Jcrop.filter[f[i]])
obj = new $.Jcrop.filter[f[i]];
obj.core = this;
if (obj.init) obj.init();
this.filter[f[i]] = obj;
}
},
// }}}
// getDefaultFilters: function(){{{
getDefaultFilters: function(){
var rv = [];
for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++)
if(this.filter.hasOwnProperty(f[i]))
rv.push(this.filter[f[i]]);
rv.sort(function(x,y){ return x.priority - y.priority; });
return rv;
},
// }}}
// setSelection: function(sel){{{
setSelection: function(sel){
var m = this.ui.multi;
var n = [];
for(var i=0;i<m.length;i++) {
if (m[i] !== sel) n.push(m[i]);
m[i].toBack();
}
n.unshift(sel);
this.ui.multi = n;
this.ui.selection = sel;
sel.toFront();
return sel;
},
// }}}
// getSelection: function(raw){{{
getSelection: function(raw){
var b = this.ui.selection.get();
return b;
},
// }}}
// newSelection: function(){{{
newSelection: function(sel){
if (!sel)
sel = new this.opt.selectionComponent();
sel.init(this);
this.setSelection(sel);
return sel;
},
// }}}
// hasSelection: function(sel){{{
hasSelection: function(sel){
for(var i=0;i<this.ui.multi;i++)
if (sel === this.ui.multi[i]) return true;
},
// }}}
// removeSelection: function(sel){{{
removeSelection: function(sel){
var i, n = [], m = this.ui.multi;
for(var i=0;i<m.length;i++){
if (sel !== m[i])
n.push(m[i]);
else m[i].remove();
}
return this.ui.multi = n;
},
// }}}
//addFilter: function(filter){{{
addFilter: function(filter){
for(var i=0,m=this.ui.multi,l=m.length; i<l; i++)
m[i].addFilter(filter);
return this;
},
//}}}
// removeFiltersByTag: function(tag){{{
removeFilter: function(filter){
for(var i=0,m=this.ui.multi,l=m.length; i<l; i++)
m[i].removeFilter(filter);
return this;
},
// }}}
// blur: function(){{{
blur: function(){
this.ui.selection.blur();
return this;
},
// }}}
// focus: function(){{{
focus: function(){
this.ui.selection.focus();
return this;
},
// }}}
//initEvents: function(){{{
initEvents: function(){
var t = this;
t.container.on('selectstart',function(e){ return false; })
.on('mousedown','.'+t.opt.css_drag,t.startDrag());
},
//}}}
// maxSelect: function(){{{
maxSelect: function(){
this.setSelect([0,0,this.elw,this.elh]);
},
// }}}
// nudge: function(x,y){{{
nudge: function(x,y){
var s = this.ui.selection, b = s.get();
b.x += x;
b.x2 += x;
b.y += y;
b.y2 += y;
if (b.x < 0) { b.x2 = b.w; b.x = 0; }
else if (b.x2 > this.elw) { b.x2 = this.elw; b.x = b.x2 - b.w; }
if (b.y < 0) { b.y2 = b.h; b.y = 0; }
else if (b.y2 > this.elh) { b.y2 = this.elh; b.y = b.y2 - b.h; }
s.element.trigger('cropstart',[s,this.unscale(b)]);
s.updateRaw(b,'move');
s.element.trigger('cropend',[s,this.unscale(b)]);
},
// }}}
// refresh: function(){{{
refresh: function(){
for(var i=0,s=this.ui.multi,l=s.length;i<l;i++)
s[i].refresh();
},
// }}}
// blurAll: function(){{{
blurAll: function(){
var m = this.ui.multi;
for(var i=0;i<m.length;i++) {
if (m[i] !== sel) n.push(m[i]);
m[i].toBack();
}
},
// }}}
// scale: function(b){{{
scale: function(b){
var xs = this.opt.xscale,
ys = this.opt.yscale;
return {
x: b.x / xs,
y: b.y / ys,
x2: b.x2 / xs,
y2: b.y2 / ys,
w: b.w / xs,
h: b.h / ys
};
},
// }}}
// unscale: function(b){{{
unscale: function(b){
var xs = this.opt.xscale,
ys = this.opt.yscale;
return {
x: b.x * xs,
y: b.y * ys,
x2: b.x2 * xs,
y2: b.y2 * ys,
w: b.w * xs,
h: b.h * ys
};
},
// }}}
// requestDelete: function(){{{
requestDelete: function(){
if ((this.ui.multi.length > 1) && (this.ui.selection.canDelete))
return this.deleteSelection();
},
// }}}
// deleteSelection: function(){{{
deleteSelection: function(){
if (this.ui.selection) {
this.removeSelection(this.ui.selection);
if (this.ui.multi.length) this.ui.multi[0].focus();
this.ui.selection.refresh();
}
},
// }}}
// animateTo: function(box){{{
animateTo: function(box){
if (this.ui.selection)
this.ui.selection.animateTo(box);
return this;
},
// }}}
// setselect: function(box){{{
setSelect: function(box){
if (this.ui.selection)
this.ui.selection.update(Jcrop.wrapFromXywh(box));
return this;
},
// }}}
//startDrag: function(){{{
startDrag: function(){
var t = this;
return function(e){
var $targ = $(e.target);
var selection = $targ.closest('.'+t.opt.css_selection).data('selection');
var ord = $targ.data('ord');
t.container.trigger('cropstart',[selection,t.unscale(selection.get())]);
selection.startDrag(e,ord);
return false;
};
},
//}}}
// getContainerSize: function(){{{
getContainerSize: function(){
return [ this.container.width(), this.container.height() ];
},
// }}}
// resizeContainer: function(w,h){{{
resizeContainer: function(w,h){
this.container.width(w).height(h);
this.refresh();
},
// }}}
// setImage: function(src,cb){{{
setImage: function(src,cb){
var t = this, targ = t.opt.imgsrc;
if (!targ) return false;
new $.Jcrop.component.ImageLoader(src,null,function(w,h){
t.resizeContainer(w,h);
targ.src = src;
$(targ).width(w).height(h);
t.applySizeConstraints();
t.refresh();
t.container.trigger('cropimage',[t,targ]);
if (typeof cb == 'function')
cb.call(t,w,h);
});
},
// }}}
// update: function(b){{{
update: function(b){
if (this.ui.selection)
this.ui.selection.update(b);
}
// }}}
});
// Jcrop jQuery plugin function
$.fn.Jcrop = function(options,callback){
options = options || {};
var first = this.eq(0).data('Jcrop');
var args = Array.prototype.slice.call(arguments);
// Return API if requested
if (options == 'api') { return first; }
// Allow calling API methods (with arguments)
else if (first && (typeof options == 'string')) {
// Call method if it exists
if (first[options]) {
args.shift();
first[options].apply(first,args);
return first;
}
// Unknown input/method does not exist
return false;
}
// Otherwise, loop over selected elements
this.each(function(){
var t = this, $t = $(this);
var exists = $t.data('Jcrop');
var obj;
// If Jcrop already exists on this element only setOptions()
if (exists)
exists.setOptions(options);
else {
if (!options.stageConstructor)
options.stageConstructor = $.Jcrop.stageConstructor;
options.stageConstructor(this,options,function(stage,options){
var selection = options.setSelect;
if (selection) delete(options.setSelect);
var obj = $.Jcrop.attach(stage.element,options);
if (typeof stage.attach == 'function')
stage.attach(obj);
$t.data('Jcrop',obj);
if (selection) {
obj.newSelection();
obj.setSelect(selection);
}
if (typeof callback == 'function')
callback.call(obj);
});
}
return this;
});
};
/* Modernizr 2.7.1 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-csstransforms-canvas-canvastext-draganddrop-inlinesvg-svg-svgclippaths-touch-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-url_data_uri
*/
;
var Modernizr = (function( window, document, undefined ) {
var version = '2.7.1',
Modernizr = {},
docElement = document.documentElement,
mod = 'modernizr',
modElem = document.createElement(mod),
mStyle = modElem.style,
inputElem ,
toString = {}.toString,
prefixes = ' -webkit- -moz- -o- -ms- '.split(' '),
omPrefixes = 'Webkit Moz O ms',
cssomPrefixes = omPrefixes.split(' '),
domPrefixes = omPrefixes.toLowerCase().split(' '),
ns = {'svg': 'http://www.w3.org/2000/svg'},
tests = {},
inputs = {},
attrs = {},
classes = [],
slice = classes.slice,
featureName,
injectElementWithStyles = function( rule, callback, nodes, testnames ) {
var style, ret, node, docOverflow,
div = document.createElement('div'),
body = document.body,
fakeBody = body || document.createElement('body');
if ( parseInt(nodes, 10) ) {
while ( nodes-- ) {
node = document.createElement('div');
node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
div.appendChild(node);
}
}
style = ['&#173;','<style id="s', mod, '">', rule, '</style>'].join('');
div.id = mod;
(body ? div : fakeBody).innerHTML += style;
fakeBody.appendChild(div);
if ( !body ) {
fakeBody.style.background = '';
fakeBody.style.overflow = 'hidden';
docOverflow = docElement.style.overflow;
docElement.style.overflow = 'hidden';
docElement.appendChild(fakeBody);
}
ret = callback(div, rule);
if ( !body ) {
fakeBody.parentNode.removeChild(fakeBody);
docElement.style.overflow = docOverflow;
} else {
div.parentNode.removeChild(div);
}
return !!ret;
},
isEventSupported = (function() {
var TAGNAMES = {
'select': 'input', 'change': 'input',
'submit': 'form', 'reset': 'form',
'error': 'img', 'load': 'img', 'abort': 'img'
};
function isEventSupported( eventName, element ) {
element = element || document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
var isSupported = eventName in element;
if ( !isSupported ) {
if ( !element.setAttribute ) {
element = document.createElement('div');
}
if ( element.setAttribute && element.removeAttribute ) {
element.setAttribute(eventName, '');
isSupported = is(element[eventName], 'function');
if ( !is(element[eventName], 'undefined') ) {
element[eventName] = undefined;
}
element.removeAttribute(eventName);
}
}
element = null;
return isSupported;
}
return isEventSupported;
})(),
_hasOwnProperty = ({}).hasOwnProperty, hasOwnProp;
if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) {
hasOwnProp = function (object, property) {
return _hasOwnProperty.call(object, property);
};
}
else {
hasOwnProp = function (object, property) {
return ((property in object) && is(object.constructor.prototype[property], 'undefined'));
};
}
if (!Function.prototype.bind) {
Function.prototype.bind = function bind(that) {
var target = this;
if (typeof target != "function") {
throw new TypeError();
}
var args = slice.call(arguments, 1),
bound = function () {
if (this instanceof bound) {
var F = function(){};
F.prototype = target.prototype;
var self = new F();
var result = target.apply(
self,
args.concat(slice.call(arguments))
);
if (Object(result) === result) {
return result;
}
return self;
} else {
return target.apply(
that,
args.concat(slice.call(arguments))
);
}
};
return bound;
};
}
function setCss( str ) {
mStyle.cssText = str;
}
function setCssAll( str1, str2 ) {
return setCss(prefixes.join(str1 + ';') + ( str2 || '' ));
}
function is( obj, type ) {
return typeof obj === type;
}
function contains( str, substr ) {
return !!~('' + str).indexOf(substr);
}
function testProps( props, prefixed ) {
for ( var i in props ) {
var prop = props[i];
if ( !contains(prop, "-") && mStyle[prop] !== undefined ) {
return prefixed == 'pfx' ? prop : true;
}
}
return false;
}
function testDOMProps( props, obj, elem ) {
for ( var i in props ) {
var item = obj[props[i]];
if ( item !== undefined) {
if (elem === false) return props[i];
if (is(item, 'function')){
return item.bind(elem || obj);
}
return item;
}
}
return false;
}
function testPropsAll( prop, prefixed, elem ) {
var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' ');
if(is(prefixed, "string") || is(prefixed, "undefined")) {
return testProps(props, prefixed);
} else {
props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' ');
return testDOMProps(props, prefixed, elem);
}
}
tests['canvas'] = function() {
var elem = document.createElement('canvas');
return !!(elem.getContext && elem.getContext('2d'));
};
tests['canvastext'] = function() {
return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function'));
};
tests['touch'] = function() {
var bool;
if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
bool = true;
} else {
injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) {
bool = node.offsetTop === 9;
});
}
return bool;
};
tests['draganddrop'] = function() {
var div = document.createElement('div');
return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
};
tests['csstransforms'] = function() {
return !!testPropsAll('transform');
};
tests['svg'] = function() {
return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect;
};
tests['inlinesvg'] = function() {
var div = document.createElement('div');
div.innerHTML = '<svg/>';
return (div.firstChild && div.firstChild.namespaceURI) == ns.svg;
};
tests['svgclippaths'] = function() {
return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath')));
};
for ( var feature in tests ) {
if ( hasOwnProp(tests, feature) ) {
featureName = feature.toLowerCase();
Modernizr[featureName] = tests[feature]();
classes.push((Modernizr[featureName] ? '' : 'no-') + featureName);
}
}
Modernizr.addTest = function ( feature, test ) {
if ( typeof feature == 'object' ) {
for ( var key in feature ) {
if ( hasOwnProp( feature, key ) ) {
Modernizr.addTest( key, feature[ key ] );
}
}
} else {
feature = feature.toLowerCase();
if ( Modernizr[feature] !== undefined ) {
return Modernizr;
}
test = typeof test == 'function' ? test() : test;
if (typeof enableClasses !== "undefined" && enableClasses) {
docElement.className += ' ' + (test ? '' : 'no-') + feature;
}
Modernizr[feature] = test;
}
return Modernizr;
};
setCss('');
modElem = inputElem = null;
Modernizr._version = version;
Modernizr._prefixes = prefixes;
Modernizr._domPrefixes = domPrefixes;
Modernizr._cssomPrefixes = cssomPrefixes;
Modernizr.hasEvent = isEventSupported;
Modernizr.testProp = function(prop){
return testProps([prop]);
};
Modernizr.testAllProps = testPropsAll;
Modernizr.testStyles = injectElementWithStyles;
return Modernizr;
})(window, window.document);
// data uri test.
// https://github.com/Modernizr/Modernizr/issues/14
// This test is asynchronous. Watch out.
// in IE7 in HTTPS this can cause a Mixed Content security popup.
// github.com/Modernizr/Modernizr/issues/362
// To avoid that you can create a new iframe and inject this.. perhaps..
(function(){
var datauri = new Image();
datauri.onerror = function() {
Modernizr.addTest('datauri', function () { return false; });
};
datauri.onload = function() {
Modernizr.addTest('datauri', function () { return (datauri.width == 1 && datauri.height == 1); });
};
datauri.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
})();
;
// Attach to jQuery object
$.Jcrop = Jcrop;
$.Jcrop.supportsCanvas = Modernizr.canvas;
$.Jcrop.supportsCanvasText = Modernizr.canvastext;
$.Jcrop.supportsDragAndDrop = Modernizr.draganddrop;
$.Jcrop.supportsDataURI = Modernizr.datauri;
$.Jcrop.supportsSVG = Modernizr.svg;
$.Jcrop.supportsInlineSVG = Modernizr.inlinesvg;
$.Jcrop.supportsSVGClipPaths = Modernizr.svgclippaths;
$.Jcrop.supportsCSSTransforms = Modernizr.csstransforms;
$.Jcrop.supportsTouch = Modernizr.touch;
})(jQuery);