Created
May 15, 2009 12:35
-
-
Save steida/112192 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* ImageCanvas - image drawn by canvas with methods for scaling and rotation | |
* | |
* License: MIT-style license. | |
* Copyright: Copyright (c) 2009 [Daniel Steigerwald](http://daniel.steigerwald.cz/). | |
* | |
*/ | |
var ImageCanvas = new Class({ | |
Extends: Canvas, | |
options: {/* | |
onZoom: fn(scale)*/ | |
canvasOptions: {}, | |
keysEnabled: true, | |
center: false, | |
mouseZoom: true, | |
mouseZoomStep: 1.3, | |
mouseZoomDelay: 100, | |
mouseZoomCenter: true, | |
zoomFxOptions: { duration: 350, link: 'cancel' }, | |
effects: { | |
rotation: 0, // in degrees | |
scale: 1 | |
} | |
}, | |
initialize: function(img, options) { | |
this.image = $(img); | |
this.document = img.ownerDocument; | |
this.setOptions(options); | |
this.parent(img.ownerDocument, this.options.canvasOptions); | |
}, | |
lazyInit: function() { | |
if (this.lazyInited) return; | |
this.lazyInited = true; | |
this.initMouseZoom(); | |
this.initKeys(); | |
}, | |
initMouseZoom: function() { | |
if (!this.options.mouseZoom) return; | |
$(this.element).addEvent('mousewheel', this.onMouseWheel.bind(this)); | |
this.zoomFx = new ImageCanvas.ZoomFx(this, this.options.zoomFxOptions); | |
this.mouseWheel = 0; | |
}, | |
initKeys: function() { | |
if (!this.options.keysEnabled) return; | |
this.bound = { | |
keydown: this.onKeydown.bind(this), | |
keypress: this.onKeypress.bind(this) | |
}; | |
this.document.addEvents({ | |
keydown: this.bound.keydown, | |
keypress: this.bound.keypress | |
}); | |
}, | |
onMouseWheel: function(e) { | |
this.mouseWheel += e.wheel; | |
this.mousePos = e.page; | |
$clear(this.zoomDelay); | |
this.zoomDelay = (function() { | |
var scale = this.options.effects.scale, step = this.options.mouseZoomStep; | |
(this.mouseWheel).abs().times(function() { | |
scale = (e.wheel < 0) ? scale / step : scale * step; | |
}, this); | |
this.mouseWheel = 0; | |
this.zoom(scale); | |
}).delay(this.options.mouseZoomDelay, this); | |
}, | |
onKeypress: function(e) { | |
if (e.key == '-' || e.key == '+') { | |
var step = this.element.width / (this.element.width - 2), | |
scale = this.options.effects.scale; | |
scale = e.key == '-' ? scale / step : scale * step; | |
this.scale(scale); | |
e.stop(); | |
} | |
}, | |
onKeydown: function(e) { | |
var left = parseInt(this.element.style.left), | |
top = parseInt(this.element.style.top), | |
step = e.shift ? 16 : 1; | |
switch (e.key) { | |
case 'left': | |
case 'right': | |
this.move({ x: step * (e.key == 'left' ? -1 : 1) }); | |
e.stop(); | |
break; | |
case 'up': | |
case 'down': | |
this.move({ y: step * (e.key == 'up' ? -1 : 1) }); | |
e.stop(); | |
break; | |
} | |
}, | |
move: function(pos) { | |
var style = this.element.style; | |
if (pos.x) style.left = parseInt(this.element.style.left) + pos.x + 'px'; | |
if (pos.y) style.top = parseInt(this.element.style.top) + pos.y + 'px'; | |
}, | |
zoom: function(scale) { | |
this.computeMouseCenterOffset(scale); | |
var from = { | |
scale: this.options.effects.scale, | |
left: parseInt(this.element.style.left), | |
top: parseInt(this.element.style.top) | |
}; | |
var to = { | |
scale: scale, | |
left: from.left + this.mouseOffset.x, | |
top: from.top + this.mouseOffset.y | |
}; | |
this.zoomFx.start(from, to); | |
}, | |
computeMouseCenterOffset: function(scale) { | |
if (!this.options.mouseZoomCenter) { | |
this.mouseOffset = { x: 0, y: 0 }; | |
return; | |
} | |
var pos = this.element.getPosition(), | |
size = $merge(this.boundingSize), | |
oldScale = this.options.effects.scale; | |
var p1 = { x: this.mousePos.x - pos.x, y: this.mousePos.y - pos.y }; | |
this.options.effects.scale = scale; | |
this.computeCoords(); | |
this.options.effects.scale = oldScale; | |
var p2 = { x: this.boundingSize.x / size.x * p1.x, y: this.boundingSize.y / size.y * p1.y }; | |
this.mouseOffset = { x: (p1.x - p2.x).round(), y: (p1.y - p2.y).round() }; | |
}, | |
drawImage: function() { | |
this.computeCoords(); | |
this.setSize(this.boundingSize); | |
this.context.save(); | |
this.context.translate(this.boundingCenter.x, this.boundingCenter.y); | |
this.context.rotate(this.getRotationRadius()); | |
if (this.options.center) { | |
var style = this.element.style; | |
style.visibility = 'hidden'; // to prevent jumping | |
var rox = this.center.x - this.boundingCenter.x, | |
roy = this.center.y - this.boundingCenter.y, | |
sox = ((this.image.width - this.size.x) / 2).round(), | |
soy = ((this.image.height - this.size.y) / 2).round(); | |
var prevOffset = this.centerOffset || { x: 0, y: 0 }; | |
this.centerOffset = { x: rox + sox, y: roy + soy }; | |
style.left = (parseInt(style.left || 0) + (this.centerOffset.x - prevOffset.x)) + 'px'; | |
style.top = (parseInt(style.top || 0) + (this.centerOffset.y - prevOffset.y)) + 'px'; | |
style.visibility = 'visible'; | |
} | |
this.context.drawImage(this.image, -this.center.x, -this.center.y, this.size.x, this.size.y); | |
this.context.restore(); | |
}, | |
computeCoords: function() { | |
this.computeSizeAndCenter(); | |
this.computeBoundingSizeAndCenter(); | |
}, | |
computeSizeAndCenter: function() { | |
var scale = this.options.effects.scale; | |
this.size = { | |
x: (this.image.width * scale).round(), | |
y: (this.image.height * scale).round() | |
}; | |
this.center = { | |
x: (this.size.x / 2).round(), | |
y: (this.size.y / 2).round() | |
}; | |
}, | |
computeBoundingSizeAndCenter: function() { | |
var radius = this.getRotationRadius(); | |
var x = this.center.x, y = this.center.y; | |
var computeX = function(x, y) { return x * Math.cos(-radius) - y * Math.sin(-radius); }; | |
var computeY = function(x, y) { return y * Math.cos(-radius) + x * Math.sin(-radius); }; | |
var x1 = computeX(x, y), y1 = computeY(x, y), | |
x2 = computeX(x, -y), y2 = computeY(x, -y), | |
x3 = computeX(-x, -y), y3 = computeY(-x, -y), | |
x4 = computeX(-x, y), y4 = computeY(-x, y); | |
var minX = Math.min(Math.min(x1, x2), Math.min(x3, x4)), | |
minY = Math.min(Math.min(y1, y2), Math.min(y3, y4)), | |
maxX = Math.max(Math.max(x1, x2), Math.max(x3, x4)), | |
maxY = Math.max(Math.max(y1, y2), Math.max(y3, y4)); | |
this.boundingSize = { | |
x: (maxX - minX).round(), | |
y: (maxY - minY).round() | |
}; | |
this.boundingCenter = { | |
x: (this.boundingSize.x / 2).round(), | |
y: (this.boundingSize.y / 2).round() | |
}; | |
}, | |
getRotationRadius: function() { | |
return this.options.effects.rotation * (Math.PI / 180); | |
}, | |
rotate: function(degrees) { | |
this.options.effects.rotation = degrees; | |
this.drawImage(); | |
}, | |
scale: function(scale) { | |
this.options.effects.scale = scale; | |
this.drawImage(); | |
}, | |
effect: function(effects) { | |
$extend(this.options.effects, effects); | |
}, | |
inject: function(el, where) { | |
this.parent(el, where); | |
this.lazyInit(); | |
this.drawImage(); | |
return this; | |
}, | |
makeDraggable: function(options) { | |
$(this).makeDraggable(options); | |
return this; | |
}, | |
destroy: function() { | |
this.dispose(); | |
this.document.removeEvents({ | |
keydown: this.bound.keydown, | |
keypress: this.bound.keypress | |
}); | |
} | |
}); | |
ImageCanvas.ZoomFx = new Class({ | |
Extends: Fx, | |
initialize: function(imageCanvas, options) { | |
this.imageCanvas = imageCanvas; | |
this.parent(options); | |
}, | |
set: function(now) { | |
this.imageCanvas.scale(now.scale); | |
if (this.imageCanvas.options.mouseZoomCenter) { | |
this.imageCanvas.element.setStyles({ left: now.left, top: now.top }); | |
} | |
this.imageCanvas.fireEvent('zoom', now.scale); | |
return now; | |
}, | |
compute: function(from, to, delta) { | |
return { | |
scale: Fx.compute(from.scale, to.scale, delta), | |
left: Fx.compute(from.left, to.left, delta), | |
top: Fx.compute(from.top, to.top, delta) | |
}; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment