Skip to content

Instantly share code, notes, and snippets.

@steida
Created May 15, 2009 12:35
Show Gist options
  • Save steida/112192 to your computer and use it in GitHub Desktop.
Save steida/112192 to your computer and use it in GitHub Desktop.
/*
* 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