From 02cd2e42b245d7d81e4664729552fd94bb62b6d7 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 30 Jan 2017 15:43:48 +0100 Subject: [PATCH] Improve avatar resampling of non-animated canvas --- .../components/components/avatar.jsx | 94 ++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx index 8d5c90b334..ee9fc72556 100644 --- a/app/assets/javascripts/components/components/avatar.jsx +++ b/app/assets/javascripts/components/components/avatar.jsx @@ -1,5 +1,91 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; +// From: http://stackoverflow.com/a/18320662 +const resample = (canvas, width, height, resize_canvas) => { + let width_source = canvas.width; + let height_source = canvas.height; + width = Math.round(width); + height = Math.round(height); + + let ratio_w = width_source / width; + let ratio_h = height_source / height; + let ratio_w_half = Math.ceil(ratio_w / 2); + let ratio_h_half = Math.ceil(ratio_h / 2); + + let ctx = canvas.getContext("2d"); + let img = ctx.getImageData(0, 0, width_source, height_source); + let img2 = ctx.createImageData(width, height); + let data = img.data; + let data2 = img2.data; + + for (let j = 0; j < height; j++) { + for (let i = 0; i < width; i++) { + let x2 = (i + j * width) * 4; + let weight = 0; + let weights = 0; + let weights_alpha = 0; + let gx_r = 0; + let gx_g = 0; + let gx_b = 0; + let gx_a = 0; + let center_y = (j + 0.5) * ratio_h; + let yy_start = Math.floor(j * ratio_h); + let yy_stop = Math.ceil((j + 1) * ratio_h); + + for (let yy = yy_start; yy < yy_stop; yy++) { + let dy = Math.abs(center_y - (yy + 0.5)) / ratio_h_half; + let center_x = (i + 0.5) * ratio_w; + let w0 = dy * dy; //pre-calc part of w + let xx_start = Math.floor(i * ratio_w); + let xx_stop = Math.ceil((i + 1) * ratio_w); + + for (let xx = xx_start; xx < xx_stop; xx++) { + let dx = Math.abs(center_x - (xx + 0.5)) / ratio_w_half; + let w = Math.sqrt(w0 + dx * dx); + + if (w >= 1) { + // pixel too far + continue; + } + + // hermite filter + weight = 2 * w * w * w - 3 * w * w + 1; + let pos_x = 4 * (xx + yy * width_source); + + // alpha + gx_a += weight * data[pos_x + 3]; + weights_alpha += weight; + + // colors + if (data[pos_x + 3] < 255) + weight = weight * data[pos_x + 3] / 250; + + gx_r += weight * data[pos_x]; + gx_g += weight * data[pos_x + 1]; + gx_b += weight * data[pos_x + 2]; + weights += weight; + } + } + + data2[x2] = gx_r / weights; + data2[x2 + 1] = gx_g / weights; + data2[x2 + 2] = gx_b / weights; + data2[x2 + 3] = gx_a / weights_alpha; + } + } + + // clear and resize canvas + if (resize_canvas === true) { + canvas.width = width; + canvas.height = height; + } else { + ctx.clearRect(0, 0, width_source, height_source); + } + + // draw + ctx.putImageData(img2, 0, 0); +}; + const Avatar = React.createClass({ propTypes: { @@ -25,7 +111,11 @@ const Avatar = React.createClass({ }, handleLoad () { - this.canvas.getContext('2d').drawImage(this.image, 0, 0, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio); + this.canvas.width = this.image.naturalWidth; + this.canvas.height = this.image.naturalHeight; + this.canvas.getContext('2d').drawImage(this.image, 0, 0); + + resample(this.canvas, this.props.size * window.devicePixelRatio, this.props.size * window.devicePixelRatio, true); }, setImageRef (c) { @@ -42,7 +132,7 @@ const Avatar = React.createClass({ return (
- +
); }