diff --git a/src/function.js b/src/function.js index 35ffc09..f93bf31 100644 --- a/src/function.js +++ b/src/function.js @@ -326,6 +326,10 @@ function get_exposed(random) { return value => value; }, + paint() { + return value => value; + }, + path() { return value => value; }, diff --git a/src/generator.js b/src/generator.js index 6ceb64b..ccab946 100644 --- a/src/generator.js +++ b/src/generator.js @@ -38,6 +38,7 @@ class Rules { this.coords = []; this.doodles = {}; this.canvas = {}; + this.paint = {}; this.shaders = {}; this.paths = {}; this.reset(); @@ -58,6 +59,7 @@ class Rules { this.coords = []; this.doodles = {}; this.canvas = {}; + this.paint = {}; this.shaders = {}; for (let key in this.rules) { if (key.startsWith('#c')) { @@ -112,7 +114,7 @@ class Rules { } is_composable(name) { - return ['doodle', 'shaders', 'canvas'].includes(name); + return ['doodle', 'shaders', 'canvas', 'paint'].includes(name); } compose_argument(argument, coords, extra = []) { @@ -138,6 +140,8 @@ class Rules { return this.compose_shaders(value, coords); case 'canvas': return this.compose_canvas(value, coords); + case 'paint': + return this.compose_paint(value, arg.arguments.slice(1)); } } } @@ -187,6 +191,17 @@ class Rules { return '${' + id + '}'; } + compose_paint(code, rest = []) { + let commands = code; + let result = rest.map(group => get_value(group[0])).join(','); + if (result.length) { + commands = code + ',' + result; + } + let id = this.unique_id('paint'); + this.paint[id] = { code: commands }; + return '${' + id + '}'; + } + compose_path(commands) { let id = this.unique_id('path'); this.paths[id] = { @@ -223,6 +238,8 @@ class Rules { result += this.compose_shaders(value, coords); break; case 'canvas': result += this.compose_canvas(value, coords); break; + case 'paint': + result += this.compose_paint(value, val.arguments.slice(1)); break; } } } else { @@ -316,7 +333,7 @@ class Rules { } } - if (prop === 'background' && (value.includes('shader') || value.includes('canvas'))) { + if (prop === 'background' && (value.includes('shader') || value.includes('canvas') || value.includes('paint'))) { rule += 'background-size: 100% 100%;'; } @@ -544,6 +561,7 @@ class Rules { shaders: this.shaders, paths: this.paths, canvas: this.canvas, + paint: this.paint, definitions: definitions, uniforms: this.uniforms } diff --git a/src/index.js b/src/index.js index d51d92e..651bbe4 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import seedrandom from './lib/seedrandom'; import { svg_to_png } from './svg'; import { draw_shader } from './shader.js'; import { draw_canvas } from './canvas.js'; +import { make_paint } from './paint.js'; import { uniform_time } from './uniform'; import get_props from './utils/get-props'; @@ -239,6 +240,10 @@ class Doodle extends HTMLElement { draw_canvas(code, width, height, this.random).then(fn); } + paint_to_image({ code, cell }, fn) { + make_paint(code, this.random).then(fn); + } + shader_to_image({ shader, cell }, fn) { let parsed = parse_shaders(shader); let element = this.doodle.getElementById(cell); @@ -287,16 +292,16 @@ class Doodle extends HTMLElement { this.build_grid(compiled, this.grid_size); } - replace({ doodles, shaders, paths, canvas }) { + replace({ doodles, shaders, paths, canvas, paint }) { let doodle_ids = Object.keys(doodles); let shader_ids = Object.keys(shaders); let path_ids = Object.keys(paths); let canvas_ids = Object.keys(canvas); + let paint_ids = Object.keys(paint); return input => { - if (!doodle_ids.length && !shader_ids.length && !path_ids.length && !canvas_ids.length) { + if (!doodle_ids.length && !shader_ids.length && !path_ids.length && !canvas_ids.length && !paint_ids.length) { return Promise.resolve(input); } - let mappings = [].concat( doodle_ids.map(id => { if (input.includes(id)) { @@ -325,6 +330,15 @@ class Doodle extends HTMLElement { return Promise.resolve(''); } }), + paint_ids.map(id => { + if (input.includes(id)) { + return new Promise(resolve => { + this.paint_to_image(paint[id], value => resolve({ id, value })); + }); + } else { + return Promise.resolve(''); + } + }), path_ids.map(id => { if (input.includes(id)) { return Promise.resolve({ id, value: '#' + id }); @@ -337,11 +351,17 @@ class Doodle extends HTMLElement { return Promise.all(mappings).then(mapping => { if (input.replaceAll) { mapping.forEach(({ id, value }) => { - input = input.replaceAll('${' + id + '}', `url(${value})`); + input = input.replaceAll( + '${' + id + '}', + /^paint/.test(id) ? value : `url(${value})` + ); }); } else { mapping.forEach(({ id, value }) => { - input = input.replace('${' + id + '}', `url(${value})`); + input = input.replace( + '${' + id + '}', + /^paint/.test(id) ? value : `url(${value})` + ) }); } return input; diff --git a/src/paint.js b/src/paint.js new file mode 100644 index 0000000..ff332d1 --- /dev/null +++ b/src/paint.js @@ -0,0 +1,29 @@ +import { un_entity } from './utils/index'; + +let counter = 1; +let cache = {}; + +function make_paint(code, random) { + if (cache[code]) { + return Promise.resolve(cache[code]); + } + + let name = 'css-doodle-paint-' + counter++; + let wrapped = ` + registerPaint('${name}', class { + ${ un_entity(code) } + }) + `; + let blob = new Blob([wrapped], { type: 'text/javascript' }); + try { + if (CSS.paintWorklet) { + CSS.paintWorklet.addModule(URL.createObjectURL(blob)); + } + } catch(e) {} + + return Promise.resolve(cache[code] = `paint(${name})`); +} + +export { + make_paint +}