<html>
<head>
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
<meta name="description" content="Image resize on client with supersampling">
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<canvas id="c" width="100" height="300"></canvas>
<br>
<input type="file" id="f" multiple>
<button id="run">run</button>
<button id="save">save</button>
<div id="log"></div>
</body>
</html>
body {
font-family: sans-serif;
line-height: 1.4;
}
// noprotect
function supersampleFast(xsizeIn, ysizeIn, data, xsizeOut, ysizeOut, dataOut) {
var xres = xsizeOut / xsizeIn;
var yres = ysizeOut / ysizeIn;
xsizeIn = xsizeIn|0;
xsizeOut = xsizeOut|0;
var buf = new Int32Array(xsizeOut * 4);
var pxx = new Int32Array(xsizeIn);
var pcountx = new Int32Array(xsizeOut);
for (var x = 0; x < xsizeIn; x++) {
var dstx = (x * xres + xres / 2)|0;
pxx[x] = dstx * 4;
pcountx[dstx] += 1;
}
var lastyy = 0;
var county = 0;
var lowY = 0;
for (var i = 0; i < data.length; i++) {
var dataIn = data[i];
var nextY = Math.floor(dataIn.length / xsizeIn / 4);
for (y = 0; y < nextY; y++) {
var yy = ((y + lowY) * yres + yres / 2)|0;
if (yy > lastyy) {
var dstIndex = lastyy * xsizeOut * 4;
var xcc = 0;
for (var x = 0; x < xsizeOut; x++) {
var count = (county * pcountx[x])|0;
dataOut[dstIndex + xcc + 0] = (buf[xcc + 0] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 1] = (buf[xcc + 1] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 2] = (buf[xcc + 2] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 3] = (buf[xcc + 3] + (count >> 1)) / count;
xcc += 4;
}
var buf = new Int32Array(xsizeOut * 4);
county = 0;
}
lastyy = yy;
county += 1;
var srcIndex = y * xsizeIn * 4;
for (var x = 0; x < xsizeIn; x++) {
var xx = pxx[x];
buf[xx + 0] += dataIn[srcIndex + 0];
buf[xx + 1] += dataIn[srcIndex + 1];
buf[xx + 2] += dataIn[srcIndex + 2];
buf[xx + 3] += dataIn[srcIndex + 3];
srcIndex += 4;
}
}
lowY += nextY;
}
var dstIndex = lastyy * xsizeOut * 4;
var xcc = 0;
for (var x = 0; x < xsizeOut; x++) {
var count = (county * pcountx[x])|0;
dataOut[dstIndex + xcc + 0] = (buf[xcc + 0] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 1] = (buf[xcc + 1] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 2] = (buf[xcc + 2] + (count >> 1)) / count;
dataOut[dstIndex + xcc + 3] = (buf[xcc + 3] + (count >> 1)) / count;
xcc += 4;
}
return dataOut;
}
function supersampleImage(i, ctxOut, callback) {
var start = new Date();
function consume(img) {
var drawTime = 0;
var getDataTime = 0;
var width = img.width;
var chunkHeight = Math.ceil(2 * 1024 * 1024 / width)|0;
var c = document.createElement('canvas');
c.width = width;
c.height = chunkHeight;
var ctxIn = c.getContext('2d');
var data = [];
for (var i = 0; i < img.height; i += chunkHeight) {
chunkHeight = Math.min(chunkHeight, img.height - i);
var start = new Date();
ctxIn.drawImage(img, 0, i, width, chunkHeight, 0, 0, width, chunkHeight);
var drawTime1 = (new Date() - start);
start = new Date();
data.push(ctxIn.getImageData(0, 0, width, chunkHeight).data);
var getDataTime1 = (new Date() - start);
drawTime += drawTime1;
getDataTime += getDataTime1;
log(' draw, get: ' + drawTime1 + ' ' + getDataTime1);
}
log('Draw to canvas: ' + drawTime);
log('Get image data: ' + getDataTime);
return data;
}
var data = consume(i);
for (var j = 0; j < 1; j++) {
var imageDataOut = ctxOut.createImageData(ctxOut.canvas.width,
ctxOut.canvas.height);
start = new Date();
dataOut = supersampleFast(
i.width, i.height, data,
ctxOut.canvas.width, ctxOut.canvas.height,
imageDataOut.data
);
log('Resize: ' + (new Date() - start)); start = new Date();
ctxOut.putImageData(imageDataOut, 0, 0);
log('Put data back: ' + (new Date() - start));
}
start = new Date();
ctxOut.canvas.toBlob(function(blob) {
log('Get image blob: ' + ((new Date()) - start));
callback(blob);
}, 'image/jpeg');
}
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var dataURL = this.toDataURL(type, quality);
var binStr = atob( dataURL.split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
var c = document.getElementById('c');
function log(string) {
console.log(string);
$('#log').append([string, '<br>']);
}
function run() {
var fileUrl = (URL || webkitURL).createObjectURL($('#f').prop('files')[0]);
setTimeout(function() {
var start = new Date();
var img = new Image();
img.onload = function() {
log('Image load: ' + (new Date() - start));
c.height = 300;
c.width = 300 * img.width / img.height;
supersampleImage(img, c.getContext('2d'), function(blob) {
log('Total: ' + (new Date() - start));
log('');
});
};
img.src = fileUrl;
}, 0);
}
$('#run').on('click', run);
$('#f').on('change', run);
$('#save').on('click', function() {
window.open(c.toDataURL('image/png', 1));
});
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |