Skip to content

Instantly share code, notes, and snippets.

@anpleenko
Created February 26, 2015 19:46
Show Gist options
  • Save anpleenko/3728555f76c8cc4e4e1d to your computer and use it in GitHub Desktop.
Save anpleenko/3728555f76c8cc4e4e1d to your computer and use it in GitHub Desktop.
100% Client Side Image Resizing
<!doctype html>
<html>
<head>
<title>JavaScript Image Resample :: WebReflection</title>
</head>
<body>
<input id="width" type="text" value="320" />
<input id="height" type="text" />
<input id="file" type="file" />
<br /><span id="message"></span><br />
<div id="img"></div>
</body>
<script src="resample.js"></script>
<script>
(function (global, $width, $height, $file, $message, $img) {
// (C) WebReflection Mit Style License
// simple FileReader detection
if (!global.FileReader)
// no way to do what we are trying to do ...
return $message.innerHTML = "FileReader API not supported"
;
// async callback, received the
// base 64 encoded resampled image
function resampled(data) {
$message.innerHTML = "done";
($img.lastChild || $img.appendChild(new Image)
).src = data;
}
// async callback, fired when the image
// file has been loaded
function load(e) {
$message.innerHTML = "resampling ...";
// see resample.js
Resample(
this.result,
this._width || null,
this._height || null,
resampled
);
}
// async callback, fired if the operation
// is aborted ( for whatever reason )
function abort(e) {
$message.innerHTML = "operation aborted";
}
// async callback, fired
// if an error occur (i.e. security)
function error(e) {
$message.innerHTML = "Error: " + (this.result || e);
}
// listener for the input@file onchange
$file.addEventListener("change", function change() {
var
// retrieve the width in pixel
width = parseInt($width.value, 10),
// retrieve the height in pixels
height = parseInt($height.value, 10),
// temporary variable, different purposes
file
;
// no width and height specified
// or both are NaN
if (!width && !height) {
// reset the input simply swapping it
$file.parentNode.replaceChild(
file = $file.cloneNode(false),
$file
);
// remove the listener to avoid leaks, if any
$file.removeEventListener("change", change, false);
// reassign the $file DOM pointer
// with the new input text and
// add the change listener
($file = file).addEventListener("change", change, false);
// notify user there was something wrong
$message.innerHTML = "please specify width or height";
} else if(
// there is a files property
// and this has a length greater than 0
($file.files || []).length &&
// the first file in this list
// has an image type, hopefully
// compatible with canvas and drawImage
// not strictly filtered in this example
/^image\//.test((file = $file.files[0]).type)
) {
// reading action notification
$message.innerHTML = "reading ...";
// create a new object
file = new FileReader;
// assign directly events
// as example, Chrome does not
// inherit EventTarget yet
// so addEventListener won't
// work as expected
file.onload = load;
file.onabort = abort;
file.onerror = error;
// cheap and easy place to store
// desired width and/or height
file._width = width;
file._height = height;
// time to read as base 64 encoded
// data te selected image
file.readAsDataURL($file.files[0]);
// it will notify onload when finished
// An onprogress listener could be added
// as well, not in this demo tho (I am lazy)
} else if (file) {
// if file variable has been created
// during precedent checks, there is a file
// but the type is not the expected one
// wrong file type notification
$message.innerHTML = "please chose an image";
} else {
// no file selected ... or no files at all
// there is really nothing to do here ...
$message.innerHTML = "nothing to do";
}
}, false);
}(
// the global object
this,
// all required fields ...
document.getElementById("width"),
document.getElementById("height"),
document.getElementById("file"),
document.getElementById("message"),
document.getElementById("img")
));
</script>
</html>
var Resample = (function (canvas) {
// (C) WebReflection Mit Style License
// Resample function, accepts an image
// as url, base64 string, or Image/HTMLImgElement
// optional width or height, and a callback
// to invoke on operation complete
function Resample(img, width, height, onresample) {
var
// check the image type
load = typeof img == "string",
// Image pointer
i = load || img
;
// if string, a new Image is needed
if (load) {
i = new Image;
// with propers callbacks
i.onload = onload;
i.onerror = onerror;
}
// easy/cheap way to store info
i._onresample = onresample;
i._width = width;
i._height = height;
// if string, we trust the onload event
// otherwise we call onload directly
// with the image as callback context
load ? (i.src = img) : onload.call(img);
}
// just in case something goes wrong
function onerror() {
throw ("not found: " + this.src);
}
// called when the Image is ready
function onload() {
var
// minifier friendly
img = this,
// the desired width, if any
width = img._width,
// the desired height, if any
height = img._height,
// the callback
onresample = img._onresample
;
// if width and height are both specified
// the resample uses these pixels
// if width is specified but not the height
// the resample respects proportions
// accordingly with orginal size
// same is if there is a height, but no width
width == null && (width = round(img.width * height / img.height));
height == null && (height = round(img.height * width / img.width));
// remove (hopefully) stored info
delete img._onresample;
delete img._width;
delete img._height;
// when we reassign a canvas size
// this clears automatically
// the size should be exactly the same
// of the final image
// so that toDataURL ctx method
// will return the whole canvas as png
// without empty spaces or lines
canvas.width = width;
canvas.height = height;
// drawImage has different overloads
// in this case we need the following one ...
context.drawImage(
// original image
img,
// starting x point
0,
// starting y point
0,
// image width
img.width,
// image height
img.height,
// destination x point
0,
// destination y point
0,
// destination width
width,
// destination height
height
);
// retrieve the canvas content as
// base4 encoded PNG image
// and pass the result to the callback
onresample(canvas.toDataURL("image/png"));
}
var
// point one, use every time ...
context = canvas.getContext("2d"),
// local scope shortcut
round = Math.round
;
return Resample;
}(
// lucky us we don't even need to append
// and render anything on the screen
// let's keep this DOM node in RAM
// for all resizes we want
this.document.createElement("canvas"))
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment