UNPKG

grade-js

Version:

This JavaScript library produces complimentary gradients generated from the top 2 dominant colours in supplied images

180 lines (155 loc) 5.67 kB
const prefixes = ['webkit']; class Grade { constructor(container, img_selector, callback) { this.callback = callback || null this.container = container; this.image = this.container.querySelector(img_selector) || this.container.querySelector('img') this.gradientData = [] if(!this.image || !this.container){ return } this.canvas = document.createElement('canvas'); this.ctx = this.canvas.getContext('2d'); this.imageDimensions = { width: 0, height: 0 }; this.imageData = []; this.readImage() } readImage() { this.imageDimensions.width = this.image.width * 0.1; this.imageDimensions.height = this.image.height * 0.1; this.render() } getImageData() { let imageData = this.ctx.getImageData( 0, 0, this.imageDimensions.width, this.imageDimensions.height ).data; this.imageData = Array.from(imageData) } getChunkedImageData() { const perChunk = 4; let chunked = this.imageData.reduce((ar, it, i) => { const ix = Math.floor(i / perChunk) if (!ar[ix]) { ar[ix] = [] } ar[ix].push(it); return ar }, []); let filtered = chunked.filter(rgba => { return rgba.slice(0, 2).every(val => val < 250) && rgba.slice(0, 2).every(val => val > 0) }); return filtered } getRGBAGradientValues(top) { return top.map((color, index) => { return `rgb(${color.rgba.slice(0, 3).join(',')}) ${index == 0 ? '0%' : '75%'}` }).join(',') } getCSSGradientProperty(top) { const val = this.getRGBAGradientValues(top); return prefixes.map(prefix => { return `background-image: -${prefix}-linear-gradient( 135deg, ${val} )` }).concat([`background-image: linear-gradient( 135deg, ${val} )`]).join(';') } getMiddleRGB(start, end) { let w = 0.5 * 2 - 1; let w1 = (w + 1) / 2.0; let w2 = 1 - w1; let rgb = [parseInt(start[0] * w1 + end[0] * w2), parseInt(start[1] * w1 + end[1] * w2), parseInt(start[2] * w1 + end[2] * w2)]; return rgb; } getSortedValues(uniq) { const occurs = Object.keys(uniq).map(key => { const rgbaKey = key; let components = key.split('|'), brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000 return { rgba: rgbaKey.split('|'), occurs: uniq[key], brightness } }).sort((a, b) => a.occurs - b.occurs).reverse().slice(0, 10); return occurs.sort((a, b) => a.brightness - b.brightness).reverse() } getTextProperty(top) { let rgb = this.getMiddleRGB(top[0].rgba.slice(0,3), top[1].rgba.slice(0,3)); let o = Math.round(((parseInt(rgb[0]) * 299) + (parseInt(rgb[1]) * 587) + (parseInt(rgb[2]) * 114)) /1000); if (o > 125) { return 'color: #000'; } else { return 'color: #fff'; } } getTopValues(uniq) { let sorted = this.getSortedValues(uniq); return [sorted[0], sorted[sorted.length - 1]] } getUniqValues(chunked) { return chunked.reduce((accum, current) => { let key = current.join('|'); if (!accum[key]) { accum[key] = 1; return accum } accum[key] = ++(accum[key]); return accum }, {}) } renderGradient() { const ls = window.localStorage; const item_name = `grade-${this.image.getAttribute('src')}`; let top = null; if (ls && ls.getItem(item_name)) { top = JSON.parse(ls.getItem(item_name)); } else { let chunked = this.getChunkedImageData(); top = this.getTopValues(this.getUniqValues(chunked)); if (ls) { ls.setItem(item_name, JSON.stringify(top)); } } if(this.callback){ this.gradientData = top return } let gradientProperty = this.getCSSGradientProperty(top); let textProperty = this.getTextProperty(top); let style = `${this.container.getAttribute('style') || ''}; ${gradientProperty}; ${textProperty}`; this.container.setAttribute('style', style) } render() { this.canvas.width = this.imageDimensions.width; this.canvas.height = this.imageDimensions.height; this.ctx.drawImage(this.image, 0, 0, this.imageDimensions.width, this.imageDimensions.height); this.getImageData(); this.renderGradient(); } } module.exports = (containers, img_selector, callback) => { const init = (container, img_selector, callback) => { let grade = new Grade(container, img_selector, callback), gradientData = grade.gradientData if(!gradientData.length){ return null } return { element: container, gradientData } } let results = (NodeList.prototype.isPrototypeOf(containers) ? Array.from(containers).map(container => init(container, img_selector, callback)) : [init(containers, img_selector, callback)]).filter(Boolean) if(results.length){ return callback(results) } };