diff options
| author | Samuel Degueldre <sad@odoo.com> | 2020-06-22 08:35:45 +0000 |
|---|---|---|
| committer | qsm-odoo <qsm@odoo.com> | 2020-08-19 08:32:23 +0000 |
| commit | 42abf8f49639439d992c43ddf40f7f04e8f7e7d5 (patch) | |
| tree | d47fe2f5f4b9ca736c0c07a404df2efd5032b686 /addons/web_editor/static/src/js | |
| parent | 4575c188c4e24e07897719b31d124af5c3c30e06 (diff) | |
[IMP] web_editor: add webgl image filters
This commit adds webgl image filter to the existing image options,
including a custom filter that lets the user access color-correction
options (saturation, brightness, contrast, sepia, ...) as well as
blending modes for color filters.
Part of https://github.com/odoo/odoo/pull/53114
task-2279190
closes odoo/odoo#53114
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Diffstat (limited to 'addons/web_editor/static/src/js')
| -rw-r--r-- | addons/web_editor/static/src/js/editor/image_processing.js | 202 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/editor/snippets.options.js | 35 |
2 files changed, 232 insertions, 5 deletions
diff --git a/addons/web_editor/static/src/js/editor/image_processing.js b/addons/web_editor/static/src/js/editor/image_processing.js index e775dfde421..baafd5f9903 100644 --- a/addons/web_editor/static/src/js/editor/image_processing.js +++ b/addons/web_editor/static/src/js/editor/image_processing.js @@ -8,11 +8,188 @@ const modifierFields = [ 'filter', 'quality', 'mimetype', + 'glFilter', 'originalId', 'originalSrc', 'resizeWidth', 'aspectRatio', ]; + +// webgl color filters +const _applyAll = (result, filter, filters) => { + filters.forEach(f => { + if (f[0] === 'blend') { + const cv = f[1]; + const ctx = result.getContext('2d'); + ctx.globalCompositeOperation = f[2]; + ctx.globalAlpha = f[3]; + ctx.drawImage(cv, 0, 0); + ctx.globalCompositeOperation = 'source-over'; + ctx.globalAlpha = 1.0; + } else { + filter.addFilter(...f); + } + }); +}; +let applyAll; + +const glFilters = { + blur: filter => filter.addFilter('blur', 10), + + '1977': (filter, cv) => { + const ctx = cv.getContext('2d'); + ctx.fillStyle = 'rgb(243, 106, 188)'; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'screen', .3], + ['brightness', .1], + ['contrast', .1], + ['saturation', .3], + ]); + }, + + aden: (filter, cv) => { + const ctx = cv.getContext('2d'); + ctx.fillStyle = 'rgb(66, 10, 14)'; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'darken', .2], + ['brightness', .2], + ['contrast', -.1], + ['saturation', -.15], + ['hue', 20], + ]); + }, + + brannan: (filter, cv) => { + const ctx = cv.getContext('2d'); + ctx.fillStyle = 'rgb(161, 44, 191)'; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'lighten', .31], + ['sepia', .5], + ['contrast', .4], + ]); + }, + + earlybird: (filter, cv) => { + const ctx = cv.getContext('2d'); + const gradient = ctx.createRadialGradient( + cv.width / 2, cv.height / 2, 0, + cv.width / 2, cv.height / 2, Math.hypot(cv.width, cv.height) / 2 + ); + gradient.addColorStop(.2, '#D0BA8E'); + gradient.addColorStop(1, '#1D0210'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'overlay', .2], + ['sepia', .2], + ['contrast', -.1], + ]); + }, + + inkwell: (filter, cv) => { + applyAll(filter, [ + ['sepia', .3], + ['brightness', .1], + ['contrast', -.1], + ['desaturateLuminance'], + ]); + }, + + // Needs hue blending mode for perfect reproduction. Close enough? + maven: (filter, cv) => { + applyAll(filter, [ + ['sepia', .25], + ['brightness', -.05], + ['contrast', -.05], + ['saturation', .5], + ]); + }, + + toaster: (filter, cv) => { + const ctx = cv.getContext('2d'); + const gradient = ctx.createRadialGradient( + cv.width / 2, cv.height / 2, 0, + cv.width / 2, cv.height / 2, Math.hypot(cv.width, cv.height) / 2 + ); + gradient.addColorStop(0, '#0F4E80'); + gradient.addColorStop(1, '#3B003B'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'screen', .5], + ['brightness', -.1], + ['contrast', .5], + ]); + }, + + walden: (filter, cv) => { + const ctx = cv.getContext('2d'); + ctx.fillStyle = '#CC4400'; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'screen', .3], + ['sepia', .3], + ['brightness', .1], + ['saturation', .6], + ['hue', 350], + ]); + }, + + valencia: (filter, cv) => { + const ctx = cv.getContext('2d'); + ctx.fillStyle = '#3A0339'; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'exclusion', .5], + ['sepia', .08], + ['brightness', .08], + ['contrast', .08], + ]); + }, + + xpro: (filter, cv) => { + const ctx = cv.getContext('2d'); + const gradient = ctx.createRadialGradient( + cv.width / 2, cv.height / 2, 0, + cv.width / 2, cv.height / 2, Math.hypot(cv.width, cv.height) / 2 + ); + gradient.addColorStop(.4, '#E0E7E6'); + gradient.addColorStop(1, '#2B2AA1'); + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, cv.width, cv.height); + applyAll(filter, [ + ['blend', cv, 'color-burn', .7], + ['sepia', .3], + ]); + }, + + custom: (filter, cv, filterOptions) => { + const options = Object.assign({ + blend: 'normal', + filterColor: '', + blur: '0', + desaturateLuminance: '0', + saturation: '0', + contrast: '0', + brightness: '0', + sepia: '0', + }, JSON.parse(filterOptions || "{}")); + const filters = []; + if (options.filterColor) { + const ctx = cv.getContext('2d'); + ctx.fillStyle = options.filterColor; + ctx.fillRect(0, 0, cv.width, cv.height); + filters.push(['blend', cv, options.blend, 1]); + } + delete options.blend; + delete options.filterColor; + filters.push(...Object.entries(options).map(([filter, amount]) => [filter, parseInt(amount) / 100])); + applyAll(filter, filters); + }, +}; /** * Applies data-attributes modifications to an img tag and returns a dataURL * containing the result. This function does not modify the original image. @@ -22,10 +199,21 @@ const modifierFields = [ */ async function applyModifications(img) { const data = Object.assign({ + glFilter: '', filter: '#0000', quality: '95', }, img.dataset); - let {width, height, resizeWidth, quality, filter, mimetype, originalSrc} = data; + let { + width, + height, + resizeWidth, + quality, + filter, + mimetype, + originalSrc, + glFilter, + filterOptions, + } = data; [width, height, resizeWidth] = [width, height, resizeWidth].map(s => parseFloat(s)); quality = parseInt(quality); @@ -44,6 +232,18 @@ async function applyModifications(img) { const ctx = result.getContext('2d'); ctx.drawImage(croppedImg, 0, 0, croppedImg.width, croppedImg.height, 0, 0, result.width, result.height); + // GL filter + if (glFilter) { + const glf = new window.WebGLImageFilter(); + const cv = document.createElement('canvas'); + cv.width = result.width; + cv.height = result.height; + applyAll = _applyAll.bind(null, result); + glFilters[glFilter](glf, cv, filterOptions); + const filtered = glf.apply(result); + ctx.drawImage(filtered, 0, 0, filtered.width, filtered.height, 0, 0, result.width, result.height); + } + // Color filter ctx.fillStyle = filter || '#0000'; ctx.fillRect(0, 0, result.width, result.height); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 2aca673d887..7edee505ea1 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -2993,15 +2993,34 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ /** * @see this.selectClass for parameters */ - setFilter(previewMode, widgetValue, params) { - this._getImg().dataset.filter = normalizeColor(widgetValue); + setQuality(previewMode, widgetValue, params) { + this._getImg().dataset.quality = widgetValue; return this._applyOptions(); }, /** * @see this.selectClass for parameters */ - setQuality(previewMode, widgetValue, params) { - this._getImg().dataset.quality = widgetValue; + glFilter(previewMode, widgetValue, params) { + const dataset = this._getImg().dataset; + if (widgetValue) { + dataset.glFilter = widgetValue; + } else { + delete dataset.glFilter; + } + return this._applyOptions(); + }, + /** + * @see this.selectClass for parameters + */ + customFilter(previewMode, widgetValue, params) { + const img = this._getImg(); + const {filterOptions} = img.dataset; + const {filterProperty} = params; + if (filterProperty === 'filterColor') { + widgetValue = normalizeColor(widgetValue); + } + const newOptions = Object.assign(JSON.parse(filterOptions || "{}"), {[filterProperty]: widgetValue}); + img.dataset.filterOptions = JSON.stringify(newOptions); return this._applyOptions(); }, @@ -3026,8 +3045,16 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ return img.naturalWidth; case 'setFilter': return img.dataset.filter; + case 'glFilter': + return img.dataset.glFilter || ""; case 'setQuality': return img.dataset.quality || 95; + case 'customFilter': { + const {filterProperty} = params; + const options = JSON.parse(img.dataset.filterOptions || "{}"); + const defaultValue = filterProperty === 'blend' ? 'normal' : 0; + return options[filterProperty] || defaultValue; + } } return this._super(...arguments); }, |
