├── .npmignore
├── .travis.yml
├── .babelrc
├── example
├── .babelrc
├── README.md
├── index.html
├── src
│ └── index.jsx
├── webpack.config.js
└── package.json
├── shared
├── getStyles.js
├── styles.js
└── stackblur.js
├── src
├── FullImage.jsx
├── LazyImage.jsx
└── index.js
├── tests
├── .setup.js
└── test.js
├── .gitignore
├── LICENSE
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | example
3 | examples
4 | test
5 | tests
6 | .gitignore
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "5.1"
4 | - "6.1"
5 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "airbnb"],
3 | "plugins": ["transform-runtime"],
4 | }
5 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["transform-runtime"],
4 | }
5 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # LazyImage Example
2 | An example for implementing progressive image loading with "LazyImage" React Component.
3 |
4 | ## Usage
5 | Installation & compiling:
6 |
7 | `npm install`
8 | `npm run build`
9 |
10 | open `index.html`
11 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LazyImage Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/shared/getStyles.js:
--------------------------------------------------------------------------------
1 | const conditionalStyles = require('./styles')
2 |
3 | function getStyles(target, styles, condition) {
4 | return Object.assign({},
5 | styles,
6 | condition && conditionalStyles[target].true,
7 | !condition && conditionalStyles[target].false
8 | )
9 | }
10 |
11 | module.exports = getStyles
12 |
--------------------------------------------------------------------------------
/shared/styles.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | LazyImage: {
3 | true: {
4 | display: 'none',
5 | opacity: 0,
6 | },
7 | false: {
8 | display: 'block',
9 | opacity: 1,
10 | }
11 | },
12 | FullImage: {
13 | true: {
14 | display: 'block',
15 | opacity: 1,
16 | },
17 | false: {
18 | display: 'none',
19 | opacity: 0,
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/FullImage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const FullImage = (props) => {
4 | const { src, style, onLoad } = props
5 | return (
6 |
7 |

8 |
9 | )
10 | }
11 |
12 | FullImage.propTypes = {
13 | src: React.PropTypes.string,
14 | style: React.PropTypes.object,
15 | onload: React.PropTypes.func
16 | };
17 |
18 | export default FullImage
19 |
--------------------------------------------------------------------------------
/tests/.setup.js:
--------------------------------------------------------------------------------
1 | require('babel-register')();
2 |
3 | var jsdom = require('jsdom').jsdom;
4 |
5 | var exposedProperties = ['window', 'navigator', 'document'];
6 |
7 | global.document = jsdom('');
8 | global.window = document.defaultView;
9 | Object.keys(document.defaultView).forEach((property) => {
10 | if (typeof global[property] === 'undefined') {
11 | exposedProperties.push(property);
12 | global[property] = document.defaultView[property];
13 | }
14 | });
15 |
16 | global.navigator = {
17 | userAgent: 'node'
18 | };
19 |
20 |
21 | documentRef = document;
22 |
--------------------------------------------------------------------------------
/example/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import LazyImage from '../../lib/index.js'
4 |
5 | const App = (props) => (
6 |
7 |
14 |
15 |
16 | )
17 |
18 | ReactDOM.render(,
19 | document.getElementById('content')
20 | );
21 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: [
5 | 'babel-polyfill',
6 | path.join(__dirname, './src/index.jsx')
7 | ],
8 | resolve: {
9 | extensions: ['', '.js', '.jsx'],
10 | alias: {
11 | react: path.resolve('./node_modules/react'),
12 | }
13 | },
14 | output: {
15 | filename: path.join(__dirname, './lib/index.js')
16 | },
17 | module: {
18 | loaders: [{
19 | test: /\.jsx?$/,
20 | exclude: /(node_modules|bower_components)/,
21 | loader: 'babel-loader',
22 | query: {
23 | presets: ['es2015', 'react', 'stage-0'],
24 | plugins: ['transform-runtime']
25 | }
26 | }]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
28 | node_modules
29 |
30 | # Optional npm cache directory
31 | .npm
32 |
33 | # Optional REPL history
34 | .node_repl_history
35 |
36 | lib/
37 | example/lib
38 |
39 | # System
40 | .DS\_Store
41 | *.sw?
42 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lazyimage-example",
3 | "version": "1.0.0",
4 | "description": "Example for implementing progressive image loading with lazyimage",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack"
9 | },
10 | "keywords": [
11 | "React",
12 | "Image"
13 | ],
14 | "author": "Matthias Gattermeier, ",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "babel-core": "^6.4.0",
18 | "babel-loader": "^6.2.1",
19 | "babel-plugin-transform-runtime": "^6.4.3",
20 | "babel-preset-es2015": "^6.3.13",
21 | "babel-preset-react": "^6.3.13",
22 | "babel-preset-stage-0": "^6.3.13",
23 | "webpack": "^1.13.1"
24 | },
25 | "dependencies": {
26 | "babel-polyfill": "^6.3.14",
27 | "babel-runtime": "^6.3.19",
28 | "react": "^15.1.0",
29 | "react-dom": "^15.1.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Code from 'code';
3 | import Lab from 'lab';
4 | const lab = exports.lab = Lab.script();
5 |
6 | import { shallow, mount, render } from 'enzyme';
7 |
8 | const suite = lab.suite;
9 | const test = lab.test;
10 | const expect = Code.expect;
11 |
12 | import LazyImageWrapper from '../src/index';
13 |
14 | suite('LazyImage Component', () => {
15 | test(' .. does not throw error without props', (done) => {
16 | const wrapper = shallow( );
17 | expect(wrapper).to.exist();
18 | done();
19 | });
20 | test(' .. should contain and components', (done) => {
21 | const wrapper = shallow();
22 | expect(wrapper.find('LazyImage')).to.exist();
23 | expect(wrapper.find('FullImage')).to.exist();
24 | done()
25 | })
26 | test(' .. hould have default props', (done) => {
27 | const wrapper = mount( );
28 | expect(wrapper.props().blurRadius).to.equal(10);
29 | expect(wrapper.props().width).to.equal(600);
30 | expect(wrapper.props().height).to.equal(190);
31 | done();
32 | })
33 | });
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Matthias Gattermeier
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/LazyImage.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | const StackBlur = require('../shared/stackblur');
3 |
4 | class LazyImage extends React.Component {
5 | componentDidMount(){
6 | this.canvas = this.refs.canvas
7 | this.preImg = document.createElement('img');
8 | this.preImg.crossOrigin = 'Anonymous';
9 | this.preImg.onload = () => {
10 | StackBlur(this.preImg, this.refs.canvas, this.props.blurRadius, this.props.width, this.props.height);
11 | };
12 | this.preImg.src = this.props.src;
13 | }
14 | componentWillUpdate(nextProps) {
15 | if (this.preImg.src !== nextProps.src) {
16 | this.preImg.src = nextProps.src;
17 | }
18 | StackBlur(this.preImg, this.canvas, nextProps.blurRadius, this.props.width, this.props.height);
19 | }
20 |
21 | render() {
22 | const { style, width, height } = this.props;
23 |
24 | return (
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | LazyImage.propTypes = {
33 | src: React.PropTypes.string,
34 | style: React.PropTypes.object,
35 | blurRadius: React.PropTypes.number,
36 | width: React.PropTypes.number,
37 | height: React.PropTypes.number
38 | };
39 |
40 | LazyImage.defaultProps = {
41 | blurRadius: 10,
42 | width: 600,
43 | height: 190
44 | };
45 |
46 | export default LazyImage
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lazyimage",
3 | "version": "2.1.2",
4 | "description": "A React Component for Progressive (Lazy) Image Loading",
5 | "main": "lib/index.js",
6 | "dependencies": {
7 | "babel-polyfill": "^6.3.14",
8 | "babel-runtime": "^6.3.19"
9 | },
10 | "devDependencies": {
11 | "babel-cli": "^6.9.0",
12 | "babel-core": "^6.4.0",
13 | "babel-loader": "^6.2.1",
14 | "babel-plugin-transform-runtime": "^6.4.3",
15 | "babel-preset-airbnb": "^1.1.1",
16 | "babel-preset-es2015": "^6.6.0",
17 | "babel-preset-react": "^6.5.0",
18 | "babel-preset-stage-0": "^6.3.13",
19 | "babel-register": "^6.7.2",
20 | "code": "^2.2.0",
21 | "enzyme": "^2.2.0",
22 | "jsdom": "^8.4.0",
23 | "lab": "^10.3.1",
24 | "react": "^15.0.1",
25 | "react-addons-test-utils": "^15.0.1",
26 | "react-dom": "^15.0.1"
27 | },
28 | "scripts": {
29 | "test": "lab tests/.setup.js tests/test.js --leaks",
30 | "compile": "babel ./src --out-dir ./lib",
31 | "prepublish": "npm run compile"
32 | },
33 | "repository": {
34 | "type": "git",
35 | "url": "git+https://github.com/Gattermeier/LazyImage.git"
36 | },
37 | "keywords": [
38 | "React",
39 | "Image",
40 | "lazy loading"
41 | ],
42 | "author": "Matthias Gattermeier, ",
43 | "license": "MIT",
44 | "bugs": {
45 | "url": "https://github.com/Gattermeier/LazyImage/issues"
46 | },
47 | "homepage": "https://github.com/Gattermeier/LazyImage#readme"
48 | }
49 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FullImage from './FullImage'
3 | import LazyImage from './LazyImage'
4 | import getStyles from '../shared/getStyles'
5 |
6 | class LazyImageWrapper extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = {
10 | loaded: false
11 | }
12 | this.handleLoaded = this.handleLoaded.bind(this);
13 | }
14 | handleLoaded() {
15 | this.setState({
16 | loaded: true
17 | })
18 | }
19 | render() {
20 | const { src, low, width, height, blurRadius } = this.props;
21 |
22 | const styles = {
23 | width,
24 | height,
25 | padding: 0,
26 | margin: 0,
27 | top: 0,
28 | }
29 |
30 | if (!this.props.low) {
31 | return
32 | }
33 |
34 | return (
35 |
36 |
41 | {!this.state.loaded &&
42 |
49 | }
50 |
51 | )
52 | }
53 | }
54 |
55 | LazyImageWrapper.propTypes = {
56 | blurRadius: React.PropTypes.number,
57 | width: React.PropTypes.number,
58 | height: React.PropTypes.number
59 | };
60 |
61 | LazyImageWrapper.defaultProps = {
62 | blurRadius: 10,
63 | width: 600,
64 | height: 190
65 | };
66 |
67 |
68 | module.exports = LazyImageWrapper
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LazyImage
2 | A React Component for Progressive Image Loading.
3 |
4 | [](https://travis-ci.org/Gattermeier/LazyImage)
5 | [](https://badge.fury.io/js/hapi-ff)
6 | [](https://david-dm.org/Gattermeier/lazyimage)
7 | [](https://david-dm.org/Gattermeier/lazyimage#info=devDependencies)
8 |
9 |
10 | LazyImage loads a low-res version of an image blurred before replacing it with a larger, higher-res image after it was loaded completely. Inspired by a blog article on Medium's progressive image loading by José Manuel Pérez: https://jmperezperez.com/medium-image-progressive-loading-placeholder/
11 |
12 | ## Install via npm
13 | `npm i --save lazyimage`
14 |
15 | ## Usage
16 | ```javascript
17 | import LazyImage from 'lazyimage'
18 |
19 | ...
20 |
21 |
28 | ```
29 |
30 | ## Features
31 | * If no `low`-resolution source is provided a regular image is rendered.
32 | * `blurRadius` defaults to `10`
33 |
34 |
35 | ## Tests
36 | Uses [Lab](https://github.com/hapijs/lab), [Code](https://github.com/hapijs/code), [Enzyme](https://github.com/airbnb/enzyme) for unit tests. If you want to know more about React unit testing using Lab instead of Mocha or Tape read the [blog post on Medium](https://medium.com/@gattermeier/react-unit-testing-with-enzyme-lab-and-code-24dad077f6d4#.3lawhddx2)
37 |
38 | Run tests with:
39 |
40 | `npm test`
41 |
--------------------------------------------------------------------------------
/shared/stackblur.js:
--------------------------------------------------------------------------------
1 | var mul_table = [
2 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
3 | 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
4 | 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
5 | 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
6 | 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
7 | 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
8 | 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
9 | 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
10 | 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
11 | 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
12 | 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
13 | 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
14 | 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
15 | 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
16 | 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
17 | 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259
18 | ];
19 |
20 | var shg_table = [
21 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
22 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
23 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
24 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
25 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
26 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
27 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
28 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
29 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
30 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
31 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
32 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
33 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
34 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
35 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
36 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
37 | ];
38 |
39 | function stackBlurImage(img, canvas, radius, w, h) {
40 | var nw = img.naturalWidth;
41 | var nh = img.naturalHeight;
42 |
43 | canvas.style.width = w + "px";
44 | canvas.style.height = h + "px";
45 | canvas.width = w;
46 | canvas.height = h;
47 |
48 | var context = canvas.getContext("2d");
49 | context.clearRect(0, 0, w, h);
50 |
51 | var ratio = 1;
52 | if (nw / w > nh / h) {
53 | ratio = nh / h;
54 | } else {
55 | ratio = nw / w;
56 | }
57 |
58 | var drawW = nw / ratio;
59 | var drawH = nh / ratio;
60 |
61 | try {
62 | context.drawImage(img, Math.floor((drawW - w) / -2), Math.floor((drawH - h) / -2), Math.ceil(drawW), Math.ceil(drawH));
63 | } catch (e) {
64 | console.error('There was a problem drawing the image. ' + e);
65 | }
66 |
67 | if (isNaN(radius) || radius < 1) return;
68 | stackBlurCanvasRGB(canvas, 0, 0, w, h, radius);
69 | }
70 |
71 | function stackBlurCanvasRGB(canvas, top_x, top_y, width, height, radius) {
72 | if (isNaN(radius) || radius < 1) return;
73 | radius |= 0;
74 |
75 | var context = canvas.getContext("2d");
76 | var imageData;
77 |
78 | try {
79 | imageData = context.getImageData(top_x, top_y, width, height);
80 | } catch (e) {
81 | throw new Error("unable to access image data: " + e);
82 | }
83 |
84 | var pixels = imageData.data;
85 |
86 | var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum,
87 | r_out_sum, g_out_sum, b_out_sum,
88 | r_in_sum, g_in_sum, b_in_sum,
89 | pr, pg, pb, rbs;
90 |
91 | var div = radius + radius + 1;
92 | var w4 = width << 2;
93 | var widthMinus1 = width - 1;
94 | var heightMinus1 = height - 1;
95 | var radiusPlus1 = radius + 1;
96 | var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
97 |
98 | var stackStart = new BlurStack();
99 | var stack = stackStart;
100 | for (i = 1; i < div; i++) {
101 | stack = stack.next = new BlurStack();
102 | if (i == radiusPlus1) var stackEnd = stack;
103 | }
104 | stack.next = stackStart;
105 | var stackIn = null;
106 | var stackOut = null;
107 |
108 | yw = yi = 0;
109 |
110 | var mul_sum = mul_table[radius];
111 | var shg_sum = shg_table[radius];
112 |
113 | for (y = 0; y < height; y++) {
114 | r_in_sum = g_in_sum = b_in_sum = r_sum = g_sum = b_sum = 0;
115 |
116 | r_out_sum = radiusPlus1 * (pr = pixels[yi]);
117 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
118 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
119 |
120 | r_sum += sumFactor * pr;
121 | g_sum += sumFactor * pg;
122 | b_sum += sumFactor * pb;
123 |
124 | stack = stackStart;
125 |
126 | for (i = 0; i < radiusPlus1; i++) {
127 | stack.r = pr;
128 | stack.g = pg;
129 | stack.b = pb;
130 | stack = stack.next;
131 | }
132 |
133 | for (i = 1; i < radiusPlus1; i++) {
134 | p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
135 | r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
136 | g_sum += (stack.g = (pg = pixels[p + 1])) * rbs;
137 | b_sum += (stack.b = (pb = pixels[p + 2])) * rbs;
138 |
139 | r_in_sum += pr;
140 | g_in_sum += pg;
141 | b_in_sum += pb;
142 |
143 | stack = stack.next;
144 | }
145 |
146 |
147 | stackIn = stackStart;
148 | stackOut = stackEnd;
149 | for (x = 0; x < width; x++) {
150 | pixels[yi] = (r_sum * mul_sum) >> shg_sum;
151 | pixels[yi + 1] = (g_sum * mul_sum) >> shg_sum;
152 | pixels[yi + 2] = (b_sum * mul_sum) >> shg_sum;
153 |
154 | r_sum -= r_out_sum;
155 | g_sum -= g_out_sum;
156 | b_sum -= b_out_sum;
157 |
158 | r_out_sum -= stackIn.r;
159 | g_out_sum -= stackIn.g;
160 | b_out_sum -= stackIn.b;
161 |
162 | p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
163 |
164 | r_in_sum += (stackIn.r = pixels[p]);
165 | g_in_sum += (stackIn.g = pixels[p + 1]);
166 | b_in_sum += (stackIn.b = pixels[p + 2]);
167 |
168 | r_sum += r_in_sum;
169 | g_sum += g_in_sum;
170 | b_sum += b_in_sum;
171 |
172 | stackIn = stackIn.next;
173 |
174 | r_out_sum += (pr = stackOut.r);
175 | g_out_sum += (pg = stackOut.g);
176 | b_out_sum += (pb = stackOut.b);
177 |
178 | r_in_sum -= pr;
179 | g_in_sum -= pg;
180 | b_in_sum -= pb;
181 |
182 | stackOut = stackOut.next;
183 |
184 | yi += 4;
185 | }
186 | yw += width;
187 | }
188 |
189 |
190 | for (x = 0; x < width; x++) {
191 | g_in_sum = b_in_sum = r_in_sum = g_sum = b_sum = r_sum = 0;
192 |
193 | yi = x << 2;
194 | r_out_sum = radiusPlus1 * (pr = pixels[yi]);
195 | g_out_sum = radiusPlus1 * (pg = pixels[yi + 1]);
196 | b_out_sum = radiusPlus1 * (pb = pixels[yi + 2]);
197 |
198 | r_sum += sumFactor * pr;
199 | g_sum += sumFactor * pg;
200 | b_sum += sumFactor * pb;
201 |
202 | stack = stackStart;
203 |
204 | for (i = 0; i < radiusPlus1; i++) {
205 | stack.r = pr;
206 | stack.g = pg;
207 | stack.b = pb;
208 | stack = stack.next;
209 | }
210 |
211 | yp = width;
212 |
213 | for (i = 1; i <= radius; i++) {
214 | yi = (yp + x) << 2;
215 |
216 | r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
217 | g_sum += (stack.g = (pg = pixels[yi + 1])) * rbs;
218 | b_sum += (stack.b = (pb = pixels[yi + 2])) * rbs;
219 |
220 | r_in_sum += pr;
221 | g_in_sum += pg;
222 | b_in_sum += pb;
223 |
224 | stack = stack.next;
225 |
226 | if (i < heightMinus1) {
227 | yp += width;
228 | }
229 | }
230 |
231 | yi = x;
232 | stackIn = stackStart;
233 | stackOut = stackEnd;
234 | for (y = 0; y < height; y++) {
235 | p = yi << 2;
236 | pixels[p] = (r_sum * mul_sum) >> shg_sum;
237 | pixels[p + 1] = (g_sum * mul_sum) >> shg_sum;
238 | pixels[p + 2] = (b_sum * mul_sum) >> shg_sum;
239 |
240 | r_sum -= r_out_sum;
241 | g_sum -= g_out_sum;
242 | b_sum -= b_out_sum;
243 |
244 | r_out_sum -= stackIn.r;
245 | g_out_sum -= stackIn.g;
246 | b_out_sum -= stackIn.b;
247 |
248 | p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
249 |
250 | r_sum += (r_in_sum += (stackIn.r = pixels[p]));
251 | g_sum += (g_in_sum += (stackIn.g = pixels[p + 1]));
252 | b_sum += (b_in_sum += (stackIn.b = pixels[p + 2]));
253 |
254 | stackIn = stackIn.next;
255 |
256 | r_out_sum += (pr = stackOut.r);
257 | g_out_sum += (pg = stackOut.g);
258 | b_out_sum += (pb = stackOut.b);
259 |
260 | r_in_sum -= pr;
261 | g_in_sum -= pg;
262 | b_in_sum -= pb;
263 |
264 | stackOut = stackOut.next;
265 |
266 | yi += width;
267 | }
268 | }
269 | context.putImageData(imageData, top_x, top_y);
270 | }
271 |
272 | function BlurStack() {
273 | this.r = 0;
274 | this.g = 0;
275 | this.b = 0;
276 | this.a = 0;
277 | this.next = null;
278 | }
279 |
280 | module.exports = stackBlurImage;
281 |
--------------------------------------------------------------------------------