commit: 02cd2e42b245d7d81e4664729552fd94bb62b6d7
parent: 57159804b843ccee40c23f987899a68f15446e17
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 30 Jan 2017 15:43:48 +0100
Improve avatar resampling of non-animated canvas
Diffstat:
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
@@ -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 (
<div onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={{ ...this.props.style, width: `${this.props.size}px`, height: `${this.props.size}px`, position: 'relative' }}>
<img ref={this.setImageRef} onLoad={this.handleLoad} src={this.props.src} width={this.props.size} height={this.props.size} alt='' style={{ position: 'absolute', top: '0', left: '0', visibility: hovering ? 'visible' : 'hidden', borderRadius: '4px' }} />
- <canvas ref={this.setCanvasRef} width={this.props.size * window.devicePixelRatio} height={this.props.size * window.devicePixelRatio} style={{ borderRadius: '4px', width: this.props.size, height: this.props.size }} />
+ <canvas ref={this.setCanvasRef} style={{ borderRadius: '4px', width: this.props.size, height: this.props.size, visibility: hovering ? 'hidden' : 'visible' }} />
</div>
);
}