Skip to content

Instantly share code, notes, and snippets.

@idiotWu
Last active November 13, 2016 18:54
Show Gist options
  • Save idiotWu/2bd4a9c24f48ceaacaa65bd2a01ec1b9 to your computer and use it in GitHub Desktop.
Save idiotWu/2bd4a9c24f48ceaacaa65bd2a01ec1b9 to your computer and use it in GitHub Desktop.
DOM shadow blur algorithm: http://codepen.io/idiotWu/pen/YpwBdQ
// demo config
const color = 'rgba(255, 0, 255, 1)';
const radius = 50;
const offsetX = 50;
const offsetY = 50;
const width = 200;
const height = 100;
const dx = 100;
const dy = 100;
class Shadow {
static SQRT2PI = Math.sqrt(2 * Math.PI);
static getKernel1D(radius) {
const kernel = [];
const length = radius * 2 + 1;
const sigma = (radius / 2) || 1;
const sigma2 = 2 * sigma * sigma;
const t = 1 / (sigma * Shadow.SQRT2PI);
let sum = 0;
for (let i = 0; i < length; i++) {
const r = i - radius;
const weight = t * Math.exp(-(r * r) / sigma2);
sum += weight;
kernel[i] = weight;
}
if (sum < 1) {
for (let i = 0; i < length; i++) {
kernel[i] /= sum;
}
}
return kernel;
}
static normalizeColor(str) {
const colors = [];
const alphaIdx = 3;
if (/^#/.test(str)) {
(str.length === 4 ? str.replace(/[0-9a-f]/ig, '$&$&') : str)
.match(/[0-9a-f]{2}/ig).forEach(hex => {
colors.push(parseInt(hex, 16));
});
}
if (/^rgba?/.test(str)) {
str.match(/0?\.\d*|\d{1,3}/g).forEach((val, idx) => {
colors.push((idx === alphaIdx ? val * 255 : val) | 0);
});
}
if (colors[alphaIdx] === undefined) {
colors[alphaIdx] = 255;
}
return colors;
}
constructor(width, height, color) {
this.width = width;
this.height = height;
const [sr, sg, sb, sa] = Shadow.normalizeColor(color);
this.colors = { sr, sg, sb, sa };
}
isOutOfBlur(x, y, radius) {
const {
width,
height,
} = this;
return y >= radius && y <= height - radius &&
x >= radius && x <= width - radius;
}
iterate(radius, fn) {
const {
width,
height,
} = this;
for (let y = -radius; y < height + radius; y++) {
for (let x = -radius; x < width + radius; x++) {
fn(x, y);
}
}
}
create(radius) {
const {
width,
height,
colors,
} = this;
const rowSize = width + radius * 2;
const colSize = height + radius * 2;
const imageData = new ImageData(rowSize, colSize);
const kernel = Shadow.getKernel1D(radius);
const temp = new Uint16Array(rowSize * colSize);
const { length } = kernel;
this.iterate(radius, (x, y) => {
let weight = 0;
if (this.isOutOfBlur(x, y, radius)) {
weight = 1;
} else {
const j0 = Math.max(0, radius - x);
const mj = Math.min(length, radius + width - x);
for (let j = j0; j < mj; j++) {
weight += kernel[j];
}
}
const offset = rowSize * (y + radius) + (x + radius);
temp[offset] = colors.sa * weight;
});
this.iterate(radius, (x, y) => {
const tempOffset = rowSize * (y + radius) + (x + radius);
let alpha = 0;
if (this.isOutOfBlur(x, y, radius)) {
alpha = temp[tempOffset];
} else {
const i0 = Math.max(0, radius - y);
const mi = Math.min(length, radius + height - y);
for (let i = i0; i < mi; i++) {
alpha += temp[tempOffset - (radius - i) * rowSize] * kernel[i];
}
}
const offset = tempOffset * 4;
imageData.data[offset] = colors.sr;
imageData.data[offset + 1] = colors.sg;
imageData.data[offset + 2] = colors.sb;
imageData.data[offset + 3] = alpha;
});
return imageData;
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = canvas.height = 500;
console.time(`blur ${radius}px`);
const shadow = new Shadow(width, height, color);
ctx.putImageData(shadow.create(radius), dx + offsetX - radius, dy + offsetY - radius);
ctx.fillStyle = 'blue';
ctx.fillRect(100, 100, width, height);
console.timeEnd(`blur ${radius}px`);
document.body.appendChild(canvas);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment