Skip to content

Instantly share code, notes, and snippets.

@thomwiggers
Created July 1, 2015 11:40
Show Gist options
  • Select an option

  • Save thomwiggers/51d79e19ca518c7344fa to your computer and use it in GitHub Desktop.

Select an option

Save thomwiggers/51d79e19ca518c7344fa to your computer and use it in GitHub Desktop.
/**
* Javascript Image Editor
*
* Allows to resize images inside the browser.
*
* Supports IE10+ with native support
*
* @author Thom Wiggers
* @copyright InQbator B.V. 2013-2014
*/
"use strict";
function ImageEditor (canvas, targetWidth, targetHeight, scale, fallbackmode) {
this.canvas = canvas;
this.canvas.width += 0;
this.width = targetWidth;
this.height = targetHeight;
this.CANVASWIDTH = 800;
this.CANVASHEIGHT = 600;
this.CANVASMARGIN = 20;
this.handleSize = 15;
this.canvas.width = this.CANVASWIDTH + this.CANVASMARGIN;
this.canvas.height = this.CANVASHEIGHT + this.CANVASMARGIN;
this.scale = typeof scale !== 'undefined' ? scale : 1;
// make canvas bigger if necessary
if((targetWidth/this.scale) > this.CANVASWIDTH) {
this.scale *= (targetWidth/this.scale)/this.CANVASWIDTH;
}
if((targetHeight/this.scale) > this.CANVASHEIGHT) {
this.scale *= (targetHeight/this.scale)/this.CANVASHEIGHT;
}
this.imgLeft = null;
this.imgTop = null;
this.imgWidth = null;
this.imgHeight = null;
this.img = null;
this.movingCorner = null;
this.dragging = false;
this.origin = {x:null, y:null};
// Cache for background
this.cache = {};
this.fallbackmode = typeof fallbackmode !== 'undefined' ? fallbackmode : false;
this.canvas.addEventListener('mouseup', function(event) {
event.preventDefault();
this.movingCorner = null;
this.dragging = false;
this.canvas.style.cursor = "inherit";
this.origin = {x:null, y:null};
}.bind(this), false);
this.canvas.addEventListener('mousemove', function(event) {
event.preventDefault();
var xClick = event.pageX - this.canvas.offsetLeft,
yClick = event.pageY - this.canvas.offsetTop;
// if we're not moving...
if(this.movingCorner === null && !this.dragging) {
// half handle size
var hhs = this.handleSize/2;
//op een van de hoekpunten?
if ((Math.abs(this.imgTop - yClick) <= hhs) ||
(Math.abs( (this.imgTop + this.imgHeight) - yClick) <= hhs) ||
(Math.abs(this.imgLeft-xClick) <= hhs) ||
(Math.abs( (this.imgLeft + this.imgWidth) - xClick) <= hhs)) {
this.canvas.style.cursor = "grab";
} else {
this.canvas.style.cursor = "inherit";
}
return;
}
// linksboven
if(this.movingCorner === "top left") {
this.imgWidth -= xClick - this.imgLeft;
this.imgLeft = xClick;
this.imgHeight -= yClick - this.imgTop;
this.imgTop = yClick;
}
// linksonder
else if (this.movingCorner === "bottom left") {
this.imgWidth -= xClick - this.imgLeft;
this.imgLeft = xClick;
this.imgHeight = yClick - this.imgTop;
}
// rechtsonder
else if (this.movingCorner === "bottom right") {
this.imgWidth = xClick - this.imgLeft;
this.imgHeight = yClick - this.imgTop;
}
//rechtsboven
else if (this.movingCorner === "top right") {
this.imgWidth = xClick - this.imgLeft;
this.imgHeight -= yClick - this.imgTop;
this.imgTop = yClick;
} else if (this.dragging) {
this.imgLeft -= (this.origin.x - xClick);
this.imgTop -= (this.origin.y - yClick);
this.origin = {x:xClick,y:yClick};
}
this.redraw();
}.bind(this), false);
this.canvas.addEventListener('mousedown', function(event) {
event.preventDefault();
var xClick = event.pageX - this.canvas.offsetLeft,
yClick = event.pageY - this.canvas.offsetTop;
this.origin = {x:xClick,y:yClick};
var movingCorner = "",
cursor = "";
// half handle size
var hhs = this.handleSize/2;
//bovenkant of onderkant:
if ((Math.abs(this.imgTop - yClick) <= hhs)) {
movingCorner = "top ";
cursor += "n";
} else if ((Math.abs( (this.imgTop + this.imgHeight) - yClick) <= hhs)) {
movingCorner = "bottom ";
cursor += "s";
}
//links of rechts
if((Math.abs(this.imgLeft-xClick) <= hhs)) {
movingCorner += "left";
cursor += "w-resize";
} else if((Math.abs( (this.imgLeft + this.imgWidth) - xClick) <= hhs)) {
movingCorner += "right";
cursor += "e-resize";
} else {
movingCorner = null;
cursor = "inherit";
}
this.movingCorner = movingCorner;
this.canvas.style.cursor = cursor;
if(movingCorner === null) {
if (
(
(xClick > this.imgLeft && xClick < (this.imgLeft+this.imgWidth)) ||
(xClick < this.imgLeft && xClick > (this.imgLeft+this.imgWidth))
) &&
(
(yClick > this.imgTop && yClick < (this.imgTop+this.imgHeight)) ||
(yClick < this.imgTop && yClick > (this.imgTop+this.imgHeight))
)
) {
this.dragging = true;
this.canvas.style.cursor = "move";
}
}
}.bind(this), false);
this.canvas.addEventListener('ondrag', function(event) {
event.preventDefault();
});
}
/**
* Save the image
*
* @param ajaxCall Callback function to save the image, takes a dataUrl object.
* @param filetype (optional) Filetype to save the file as. Default "image/png"
* @param fallbackCall (optional) Callback function which takes the dimensions
* of the image, the canvas, and the viewport.
*/
ImageEditor.prototype.saveImage = function (ajaxCall, filetype, fallbackCall) {
filetype = typeof filetype !== 'undefined' ? filetype : "image/png";
var context = this.canvas.getContext("2d"),
viewportLeft = ((this.CANVASWIDTH+this.CANVASMARGIN) - this.width/this.scale) / 2,
viewportTop = ((this.CANVASHEIGHT+this.CANVASMARGIN) - this.height/this.scale) /2,
offsetImageLeft = this.imgLeft - viewportLeft,
offsetImageTop = this.imgTop - viewportTop;
this.canvas.width += 0;
this.drawImage(false);
if((!context.getImageData || this.fallbackmode) && typeof fallbackCall !== 'undefined') {
var canvasData = {
imageCoords: {
top: this.imgTop,
left: this.imgLeft,
width: this.imgWidth,
height: this.imgHeight,
offsetImageTop: offsetImageTop,
offsetImageLeft: offsetImageLeft
},
settings: {
targetWidth: this.width,
targetHeight: this.height,
scale: this.scale
}
};
fallbackCall(canvasData);
} else {
var tempCanvas = document.createElement("canvas"),
tempContext = tempCanvas.getContext("2d");
tempCanvas.width = this.width;
tempCanvas.height = this.height;
console.log(tempCanvas);
tempContext.drawImage(this.img,
offsetImageLeft,
offsetImageTop,
this.imgWidth * this.scale,
this.imgHeight * this.scale
);
var dataURL = tempCanvas.toDataURL(filetype);
if(dataURL === "data:,") {
alert("Something went wrong while trying to save your image. Please try again");
return;
}
ajaxCall(dataURL);
}
};
ImageEditor.prototype.drawImage = function drawImage(decoration) {
decoration = typeof decoration !== 'undefined' ? decoration : true;
var context = this.canvas.getContext("2d");
context.save();
var width, height, top, left;
if(this.imgHeight === null || this.imgWidth === null
|| this.imgTop === null || this.imgLeft === null) {
width = this.img.width;
height = this.img.height;
if (width > this.CANVASWIDTH) {
height *= this.CANVASWIDTH / width;
width = this.CANVASWIDTH;
}
if (height > this.CANVASHEIGHT) {
width *= this.CANVASHEIGHT / height;
height = this.CANVASHEIGHT;
}
this.imgLeft = left = ((this.CANVASWIDTH - width) /2) + 10;
this.imgTop = top = ((this.CANVASHEIGHT - height) / 2) + 10;
this.imgHeight = height;
this.imgWidth = width;
} else {
width = this.imgWidth;
height = this.imgHeight;
left = this.imgLeft;
top = this.imgTop;
}
try {
context.drawImage(this.img, left, top, width, height);
} catch (error) {
var message = "An error occurred while trying to draw your image!\n";
message += "Your image might be invalid. Try opening it on your computer "
+ "or try again.\n\n Technical details: " + error.description;
}
if(decoration) {
//first draw viewport
this.drawViewport();
// draw dashed box around image
context.globalAlpha = 1;
context.save();
context.beginPath();
context.rect(left,top,width,height);
context.lineWidth = 4;
//Add a placeholder function for browsers that don't have setLineDash()
// in other words: old browsers. setLineDash is only available since
// VERY RECENTLY
if (!context.setLineDash) {
context.setLineDash = function () {}
}
context.setLineDash([10, 4]);
context.strokeStyle = "#000";
context.stroke();
context.restore();
// draw handles
// hs: handlesize, hhs=half hs
var hs = this.handleSize,
hhs = hs/2;
context.save()
context.strokeStyle = "#ddd";
context.lineWidth = 3;
context.beginPath();
context.rect(left-hhs,top-hhs,hs,hs);
context.rect(left-hhs,top+height-hhs,hs,hs);
context.rect(left+width-hhs,top-hhs,hs,hs);
context.rect(left+width-hhs,top+height-hhs,hs,hs);
context.fillStyle = '#000';
context.fill();
context.stroke();
context.restore();
}
}
/**
* Draws a background. Tries to cache it.
*/
ImageEditor.prototype.drawBackground = function () {
//check the cache
if(typeof this.cache['background'] !== 'undefined') {
this.canvas.getContext("2d").drawImage(this.cache['background'], 0, 0);
return;
}
var canvas = document.createElement("canvas");
canvas.height = this.CANVASHEIGHT + this.CANVASMARGIN;
canvas.width = this.CANVASWIDTH + this.CANVASMARGIN;
var context = canvas.getContext("2d");
context.globalAlpha = 1;
function drawSquare(initX, initY) {
context.beginPath();
context.fillStyle = "#656565";
context.fillRect(initX, initY, 5, 5);
}
for (var i = 0; i < (this.CANVASWIDTH+this.CANVASMARGIN)/10; i++) {
for (var j = 0; j < (this.CANVASHEIGHT+this.CANVASMARGIN)/5; j++) {
if ((j % 2) === 1) {
drawSquare(i * 10, j * 5);
} else {
drawSquare(i * 10 + 5, j * 5);
}
}
}
this.canvas.getContext("2d").drawImage(canvas, 0, 0);
//cache canvas
this.cache['background'] = canvas;
}
/**
* Draws a view port
*/
ImageEditor.prototype.drawViewport = function() {
//check the cache
if(typeof this.cache['viewport'] !== 'undefined') {
this.canvas.getContext("2d").drawImage(this.cache['viewport'], 0, 0);
return;
}
// ok construct a new viewport
var canvas = document.createElement("canvas");
canvas.width = this.CANVASWIDTH+this.CANVASMARGIN;
canvas.height = this.CANVASHEIGHT+this.CANVASMARGIN;
var context = canvas.getContext("2d");
context.save();
context.beginPath();
context.fillStyle = "#666";
var height = this.height / this.scale,
width = this.width / this.scale;
var viewportLeft = ((this.CANVASWIDTH+this.CANVASMARGIN) - width) / 2,
viewportRight = ((this.CANVASWIDTH+this.CANVASMARGIN) + width) / 2,
viewportBottom = ((this.CANVASHEIGHT+this.CANVASMARGIN) + height) / 2,
viewportTop = ((this.CANVASHEIGHT+this.CANVASMARGIN) - height) /2;
//draw border
context.strokeStyle = "#000";
context.lineWidth = "2";
context.rect(viewportLeft, viewportTop, width, height);
context.stroke();
//draw gray area
context.globalAlpha = 0.5;
context.fillRect(0, 0, viewportLeft, this.CANVASHEIGHT+this.CANVASMARGIN);
context.fillRect(viewportRight, 0, this.CANVASWIDTH/2, this.CANVASHEIGHT+this.CANVASMARGIN);
context.fillRect(viewportLeft, 0, width, viewportTop);
context.fillRect(viewportLeft, viewportBottom, width, viewportTop);
context.restore()
this.canvas.getContext("2d").drawImage(canvas, 0, 0);
this.cache['viewport'] = canvas;
}
/**
* redraw the image
*/
ImageEditor.prototype.redraw = function () {
this.canvas.width +=0;
this.drawBackground();
this.drawImage();
}
/**
* loads an image onto the canvas
*
* @param file file from form.
* @param fallbackhandler (optional) how to handle the fallback. Expects a
* function which when provided with an image
* will return an image URL.
*/
ImageEditor.prototype.loadImage = function(file, fallbackhandler) {
this.img = new Image();
if ((!window.URL.createObjectURL || this.fallbackmode) &&
typeof fallbackhandler !== 'undefined' ) {
this.img.src = fallbackhandler(file);
} else {
this.img.src = window.URL.createObjectURL(file);
}
this.img.onload = function () {
this.redraw();
}.bind(this);
}
// vim: set ts=4 sw=2 tw=0 et :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment