Last active
October 9, 2018 11:49
-
-
Save aescarcha/c04e97d9c8c841861e537aa20eac0e4f to your computer and use it in GitHub Desktop.
Browser image downscaling on Typescript with Angular example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Example of picking a file and using this | |
// Angular + material, you can go to handleInput | |
<button type="button" mat-raised-button color="primary" (click)="fileInput.click()"> | |
<span>Sube tu foto</span> | |
<input #fileInput type="file" (change)="handleFileInput($event)" style="display:none;" /> | |
</button> | |
public handleFileInput(event: any) { | |
if (event.target.files[0]) { | |
const reader = new FileReader(); | |
reader.onload = (e: any) => { | |
try { | |
// Resizing, it can be processing heavy but will save bandwidth | |
const img = new Image(); | |
img.onload = () => { | |
this.uploaded.emit({ | |
data: this.fitImage(img).toDataURL() | |
}); | |
}; | |
img.src = e.srcElement.result; | |
} catch (exception) { | |
// Error, upload raw image | |
this.uploaded.emit({ | |
data: e.srcElement.result | |
}); | |
} | |
}; | |
reader.readAsDataURL(event.target.files[0]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Credits to GameAlchemist | |
// https://stackoverflow.com/a/19144434/2132253 | |
// downscales the image to match max allowed size | |
// returns a canvas containing the scaled image. | |
function fitImage(img: HTMLImageElement, maxAllowedSize = 800): HTMLCanvasElement { | |
const imgCV = document.createElement('canvas'); | |
imgCV.width = img.width; | |
imgCV.height = img.height; | |
const maxDimension = Math.max(img.width, img.height); | |
const scale = maxAllowedSize / maxDimension; | |
const imgCtx = imgCV.getContext('2d'); | |
imgCtx.drawImage(img, 0, 0); | |
return downScaleCanvas(imgCV, scale); | |
} | |
// scales the canvas by (float) scale < 1 | |
// returns a new canvas containing the scaled image. | |
/* tslint:disable:no-bitwise */ | |
function downScaleCanvas(cv: HTMLCanvasElement, scale: number): HTMLCanvasElement { | |
if (!(scale < 1) || !(scale > 0)) { | |
return cv; | |
} | |
const sqScale = scale * scale; // square scale = area of source pixel within target | |
const sw = cv.width; // source image width | |
const sh = cv.height; // source image height | |
const tw = Math.floor(sw * scale); // target image width | |
const th = Math.floor(sh * scale); // target image height | |
let sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array | |
let tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array | |
let tX = 0, tY = 0; // rounded tx, ty | |
let w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y | |
// weight is weight of current source point within target. | |
// next weight is weight of current source point within next target's point. | |
let crossX = false; // does scaled px cross its current px right border ? | |
let crossY = false; // does scaled px cross its current px bottom border ? | |
const sBuffer = cv.getContext('2d'). | |
getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba | |
const tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb | |
let sR = 0, sG = 0, sB = 0; // source's current point r,g,b | |
/* untested ! | |
var sA = 0; //source alpha */ | |
for (sy = 0; sy < sh; sy++) { | |
ty = sy * scale; // y src position within target | |
tY = 0 | ty; // rounded : target pixel's y | |
yIndex = 3 * tY * tw; // line index within target array | |
crossY = (tY !== (0 | ty + scale)); | |
if (crossY) { // if pixel is crossing botton target pixel | |
wy = (tY + 1 - ty); // weight of point within target pixel | |
nwy = (ty + scale - tY - 1); // ... within y+1 target pixel | |
} | |
for (sx = 0; sx < sw; sx++, sIndex += 4) { | |
tx = sx * scale; // x src position within target | |
tX = 0 | tx; // rounded : target pixel's x | |
tIndex = yIndex + tX * 3; // target pixel index within target array | |
crossX = (tX !== (0 | tx + scale)); | |
if (crossX) { // if pixel is crossing target pixel's right | |
wx = (tX + 1 - tx); // weight of point within target pixel | |
nwx = (tx + scale - tX - 1); // ... within x+1 target pixel | |
} | |
sR = sBuffer[sIndex ]; // retrieving r,g,b for curr src px. | |
sG = sBuffer[sIndex + 1]; | |
sB = sBuffer[sIndex + 2]; | |
/* !! untested : handling alpha !! | |
sA = sBuffer[sIndex + 3]; | |
if (!sA) continue; | |
if (sA != 0xFF) { | |
sR = (sR * sA) >> 8; // or use /256 instead ?? | |
sG = (sG * sA) >> 8; | |
sB = (sB * sA) >> 8; | |
} | |
*/ | |
if (!crossX && !crossY) { // pixel does not cross | |
// just add components weighted by squared scale. | |
tBuffer[tIndex ] += sR * sqScale; | |
tBuffer[tIndex + 1] += sG * sqScale; | |
tBuffer[tIndex + 2] += sB * sqScale; | |
} else if (crossX && !crossY) { // cross on X only | |
w = wx * scale; | |
// add weighted component for current px | |
tBuffer[tIndex ] += sR * w; | |
tBuffer[tIndex + 1] += sG * w; | |
tBuffer[tIndex + 2] += sB * w; | |
// add weighted component for next (tX+1) px | |
nw = nwx * scale; | |
tBuffer[tIndex + 3] += sR * nw; | |
tBuffer[tIndex + 4] += sG * nw; | |
tBuffer[tIndex + 5] += sB * nw; | |
} else if (crossY && !crossX) { // cross on Y only | |
w = wy * scale; | |
// add weighted component for current px | |
tBuffer[tIndex ] += sR * w; | |
tBuffer[tIndex + 1] += sG * w; | |
tBuffer[tIndex + 2] += sB * w; | |
// add weighted component for next (tY+1) px | |
nw = nwy * scale; | |
tBuffer[tIndex + 3 * tw ] += sR * nw; | |
tBuffer[tIndex + 3 * tw + 1] += sG * nw; | |
tBuffer[tIndex + 3 * tw + 2] += sB * nw; | |
} else { // crosses both x and y : four target points involved | |
// add weighted component for current px | |
w = wx * wy; | |
tBuffer[tIndex ] += sR * w; | |
tBuffer[tIndex + 1] += sG * w; | |
tBuffer[tIndex + 2] += sB * w; | |
// for tX + 1; tY px | |
nw = nwx * wy; | |
tBuffer[tIndex + 3] += sR * nw; | |
tBuffer[tIndex + 4] += sG * nw; | |
tBuffer[tIndex + 5] += sB * nw; | |
// for tX ; tY + 1 px | |
nw = wx * nwy; | |
tBuffer[tIndex + 3 * tw ] += sR * nw; | |
tBuffer[tIndex + 3 * tw + 1] += sG * nw; | |
tBuffer[tIndex + 3 * tw + 2] += sB * nw; | |
// for tX + 1 ; tY +1 px | |
nw = nwx * nwy; | |
tBuffer[tIndex + 3 * tw + 3] += sR * nw; | |
tBuffer[tIndex + 3 * tw + 4] += sG * nw; | |
tBuffer[tIndex + 3 * tw + 5] += sB * nw; | |
} | |
} // end for sx | |
} // end for sy | |
// create result canvas | |
const resCV = document.createElement('canvas'); | |
resCV.width = tw; | |
resCV.height = th; | |
const resCtx = resCV.getContext('2d'); | |
const imgRes = resCtx.getImageData(0, 0, tw, th); | |
const tByteBuffer = imgRes.data; | |
// convert float32 array into a UInt8Clamped Array | |
let pxIndex = 0; // | |
for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) { | |
tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]); | |
tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]); | |
tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]); | |
tByteBuffer[tIndex + 3] = 255; | |
} | |
// writing result to canvas. | |
resCtx.putImageData(imgRes, 0, 0); | |
return resCV; | |
} | |
/* tslint:disable */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment