Skip to content

Instantly share code, notes, and snippets.

@salzhrani
Created March 26, 2015 13:48
Show Gist options
  • Save salzhrani/fecff40b862170bd5f1a to your computer and use it in GitHub Desktop.
Save salzhrani/fecff40b862170bd5f1a to your computer and use it in GitHub Desktop.
Good quality JS image resize
<!DOCTYPE html>
<html>
<head></head>
<body>
<img src="img.jpg" width="500" alt="">
<button onclick="doResize()">Resize</button>
<script src="parallel.js"></script>
<script>
function resize(data) {
// adapted from JS-Image-Resizer (c) 2012 - Grant Galitz
function generateFloatBuffer(bufferLength) {
try {
return new Float32Array(bufferLength);
}
catch (error) {
return [];
}
}
function generateUint8Buffer(bufferLength) {
try {
return new Uint8Array(bufferLength);
}
catch (error) {
return [];
}
}
function resizeWidth(buffer, ratioWeightWidthPass, widthBuffer, widthPassResultSize, originalWidthMultipliedByChannels, targetWidthMultipliedByChannels, widthOriginal) {
var ratioWeight = ratioWeightWidthPass;
var weight = 0;
var finalOffset = 0;
var pixelOffset = 0;
var firstWeight = 0;
var secondWeight = 0;
var outputBuffer = widthBuffer;
//Handle for only one interpolation input being valid for start calculation:
for (var targetPosition = 0; weight < 1/3; targetPosition += 4, weight += ratioWeight) {
for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < widthPassResultSize; pixelOffset += originalWidthMultipliedByChannels, finalOffset += targetWidthMultipliedByChannels) {
outputBuffer[finalOffset] = buffer[pixelOffset];
outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
}
}
//Adjust for overshoot of the last pass's counter:
weight -= 1/3;
for (var interpolationWidthSourceReadStop = widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 4, weight += ratioWeight) {
//Calculate weightings:
secondWeight = weight % 1;
firstWeight = 1 - secondWeight;
//Interpolate:
for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 4; finalOffset < widthPassResultSize; pixelOffset += originalWidthMultipliedByChannels, finalOffset += targetWidthMultipliedByChannels) {
outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight);
outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight);
outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight);
outputBuffer[finalOffset + 3] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight);
}
}
//Handle for only one interpolation input being valid for end calculation:
for (interpolationWidthSourceReadStop = originalWidthMultipliedByChannels - 4; targetPosition < targetWidthMultipliedByChannels; targetPosition += 4) {
for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < widthPassResultSize; pixelOffset += originalWidthMultipliedByChannels, finalOffset += targetWidthMultipliedByChannels) {
outputBuffer[finalOffset] = buffer[pixelOffset];
outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
}
}
return outputBuffer;
}
function resizeHeight(buffer, ratioWeightHeightPass, heightBuffer, targetWidthMultipliedByChannels, heightOriginal, finalResultSize){
var ratioWeight = ratioWeightHeightPass;
var weight = 0;
var finalOffset = 0;
var pixelOffset = 0;
var pixelOffsetAccumulated = 0;
var pixelOffsetAccumulated2 = 0;
var firstWeight = 0;
var secondWeight = 0;
var outputBuffer = heightBuffer;
//Handle for only one interpolation input being valid for start calculation:
for (; weight < 1/3; weight += ratioWeight) {
for (pixelOffset = 0; pixelOffset < targetWidthMultipliedByChannels;) {
outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);
}
}
//Adjust for overshoot of the last pass's counter:
weight -= 1/3;
for (var interpolationHeightSourceReadStop = heightOriginal - 1; weight < interpolationHeightSourceReadStop; weight += ratioWeight) {
//Calculate weightings:
secondWeight = weight % 1;
firstWeight = 1 - secondWeight;
//Interpolate:
pixelOffsetAccumulated = Math.floor(weight) * targetWidthMultipliedByChannels;
pixelOffsetAccumulated2 = pixelOffsetAccumulated + targetWidthMultipliedByChannels;
for (pixelOffset = 0; pixelOffset < targetWidthMultipliedByChannels; ++pixelOffset) {
outputBuffer[finalOffset++] = Math.round((buffer[pixelOffsetAccumulated++] * firstWeight) + (buffer[pixelOffsetAccumulated2++] * secondWeight));
}
}
//Handle for only one interpolation input being valid for end calculation:
while (finalOffset < finalResultSize) {
for (pixelOffset = 0, pixelOffsetAccumulated = interpolationHeightSourceReadStop * targetWidthMultipliedByChannels; pixelOffset < targetWidthMultipliedByChannels; ++pixelOffset) {
outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++]);
}
}
return outputBuffer;
}
var origianlWidth = data.width;
var origianlHeight = data.height;
var width = data.newWidth;
var height = data.newHeight;
var colorChannels = 4;
var interpolationPass = true;
var targetWidthMultipliedByChannels = width * colorChannels;
var originalWidthMultipliedByChannels = origianlWidth * colorChannels;
var originalHeightMultipliedByChannels = origianlHeight * colorChannels;
var widthPassResultSize = targetWidthMultipliedByChannels * origianlHeight;
var finalResultSize = targetWidthMultipliedByChannels * height;
// initializeFirstPassBuffers(true);
var widthBuffer = generateFloatBuffer(widthPassResultSize);
var outputWidthWorkBench = generateFloatBuffer(originalHeightMultipliedByChannels);
// initializeSecondPassBuffers(true);
var heightBuffer = generateUint8Buffer(finalResultSize);
var outputHeightWorkBench = generateFloatBuffer(targetWidthMultipliedByChannels);
var ratioWeightWidthPass = origianlWidth / width;
var ratioWeightHeightPass = origianlHeight / height;
var w = resizeWidth(data.data, ratioWeightWidthPass, widthBuffer, widthPassResultSize, originalWidthMultipliedByChannels, targetWidthMultipliedByChannels, origianlWidth);
return resizeHeight(w, ratioWeightHeightPass, heightBuffer, targetWidthMultipliedByChannels, origianlHeight, finalResultSize);
}
function doResize(){
var img = document.querySelector('img');
var canvas = document.createElement('canvas');
canvas.height = img.naturalHeight;
canvas.width = img.naturalWidth;
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0, canvas.width, canvas.height);
var data = context.getImageData(0,0,canvas.width, canvas.height);
var width = 600;
var height = 500;
var params = {
width: data.width,
height: data.height,
data: data.data,
newWidth: 600,
newHeight: 500
};
var p = new Parallel(params);
p.spawn(resize)
.then(function(r){
var newData = context.createImageData(width, height);
var arr = newData.data;
for (var i = 0, l = r.length; i < l; i++) {
arr[i] = r[i];
}
canvas.width = width;
canvas.height = height;
context.putImageData(newData,0,0);
var newImage = document.createElement('img');
newImage.src = canvas.toDataURL();
document.querySelector('body').appendChild(newImage);
});
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment