├── .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 | [![Build Status](https://travis-ci.org/Gattermeier/LazyImage.svg?branch=master)](https://travis-ci.org/Gattermeier/LazyImage) 5 | [![npm version](https://badge.fury.io/js/hapi-ff.svg)](https://badge.fury.io/js/hapi-ff) 6 | [![Dependency Status](https://david-dm.org/gattermeier/lazyimage.svg)](https://david-dm.org/Gattermeier/lazyimage) 7 | [![devDependency Status](https://david-dm.org/Gattermeier/lazyimage/dev-status.svg)](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 | --------------------------------------------------------------------------------