texture element improvements

This commit is contained in:
Gyuri Horák 2023-02-23 16:59:04 +01:00
parent 9b2d99e5a0
commit 5c137b4d23
Signed by: dyuri
GPG Key ID: 4993F07B3EAE8D38
3 changed files with 292 additions and 215 deletions

View File

@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>&lt;repa-shader&gt; demo</title> <title>&lt;repa-shader&gt; demo</title>
<script type="module" src="../src/repa-texture.js"></script>
<script type="module" src="../src/repa-shader.js"></script> <script type="module" src="../src/repa-shader.js"></script>
<style> <style>
body { body {
@ -26,6 +27,7 @@
<repa-texture src="avatar.png"></repa-texture> <repa-texture src="avatar.png"></repa-texture>
<repa-texture src="futas.mp4"></repa-texture> <repa-texture src="futas.mp4"></repa-texture>
<!-- repa-texture src="webcam"></repa-texture --> <!-- repa-texture src="webcam"></repa-texture -->
<!-- repa-texture ref="demoshader"></repa-texture -->
<script type="x-shader/x-fragmen[DELETE]t"> <script type="x-shader/x-fragmen[DELETE]t">
// https://twigl.app/?ol=true&ss=-NOAlYulOVLklxMdxBDx // https://twigl.app/?ol=true&ss=-NOAlYulOVLklxMdxBDx
void main() { void main() {

View File

@ -7,14 +7,6 @@ const createLogger = pfx => {
}; };
}; };
const isImage = (src) => {
return /\w+\.(jpg|png|jpeg|svg|webp)(?=\?|$)/i.test(src);
};
const isVideo = (src) => {
return /\w+\.(mp4|3gp|webm|ogv|avif)(?=\?|$)/i.test(src);
};
const CHUNKS = { const CHUNKS = {
es300: '#version 300 es\n', es300: '#version 300 es\n',
geeker: ` geeker: `
@ -608,213 +600,6 @@ void main() {
} }
} }
/* TODO
* - display content on canvas
* - src change
* - ready promise
**
* types: image, [video, webcam], [canvas, shader]
*/
class RepaTexture extends HTMLElement {
constructor() {
super();
this.logger = createLogger(["%c[repa-texture]", "background: #282828; color: #fabd2f"]);
this.ready = false;
this._forceUpdate = false;
}
connectedCallback() {
// TODO ?
}
disconnectedCallback() {
// TODO ?
}
static get observedAttributes() {
return ['src', 'type', 'name', 'mag-filter', 'min-filter', 'wrap-s', 'wrap-t'];
}
attributeChangedCallback(name, oldValue, newValue) {
// TODO
this.logger.info(`Attribute changed: ${name} ${oldValue} -> ${newValue}`);
this._load();
}
async _load() {
this.ready = false;
const ref = this.getAttribute('ref');
if (ref) {
const refEl = document.getElementById(ref);
if (refEl) {
this.ref = refEl;
}
}
if (!this.ref) {
const src = this.getAttribute('src');
if (src) {
this.ref = await this._loadSource(src);
}
}
if (this.ref) {
this.ready = true;
this._forceUpdate = true;
} else {
this.logger.error('Source cannot be loaded');
}
}
async _loadSource(src) {
const type = this.getAttribute('type') || this._guessType(this.src);
this._type = type;
if (!type) {
this.logger.error(`Unknown type: ${src}`);
} else {
this.logger.log(`Loading ${src} as ${type}`);
}
let ref = null;
switch (type) {
case 'image':
ref = await this._loadImage(src);
break;
case 'video':
ref = await this._loadVideo(src);
break;
case 'webcam':
ref = await this._createWebcam();
break;
}
return ref;
}
_hideInDOM(el) {
const hiddenEl = document.createElement('div');
hiddenEl.style.width = hiddenEl.style.height = '1px';
hiddenEl.style.overflow = 'hidden';
hiddenEl.style.position = 'absolute';
hiddenEl.style.top = hiddenEl.style.left = '-100px';
hiddenEl.style.zIndex = '-100';
hiddenEl.style.opacity = '0';
hiddenEl.style.pointerEvents = 'none';
hiddenEl.appendChild(el);
document.body.appendChild(hiddenEl);
}
async _loadImage(src) {
const imgEl = document.createElement('img');
imgEl.crossOrigin = 'anonymous';
const loader = new Promise((resolve, reject) => {
imgEl.onload = () => resolve(imgEl);
imgEl.onerror = reject;
imgEl.src = src;
});
this._hideInDOM(imgEl);
return await loader;
}
async _loadVideo(src) {
const videoEl = document.createElement('video');
videoEl.crossOrigin = 'anonymous';
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.src = src;
this._hideInDOM(videoEl);
return videoEl;
}
async _createWebcam() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const videoEl = document.createElement('video');
videoEl.autoplay = true;
videoEl.width = 640;
videoEl.height = 480;
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
videoEl.srcObject = stream;
this._hideInDOM(videoEl);
return videoEl;
}
this.logger.error('Webcam is not supported');
return null;
}
get src() {
return this.getAttribute('src');
}
get type() {
if (this._type) {
return this._type;
}
if (this.ref) {
if (this.ref instanceof HTMLImageElement) {
return 'image';
} else if (this.ref instanceof HTMLVideoElement) {
return 'video';
} else if (this.ref instanceof HTMLCanvasElement) {
return 'canvas';
} else if (this.ref instanceof RepaShader) {
return 'shader';
}
}
return null;
}
_guessType(src) {
if (src.toLowerCase() === 'webcam') {
return 'webcam';
}
if (isImage(src)) {
return 'image';
}
if (isVideo(src)) {
return 'video';
}
return null;
}
get shouldUpdate() {
return this._forceUpdate || (this.ref && this.ref instanceof HTMLVideoElement && this.ref.readyState === this.ref.HAVE_ENOUGH_DATA);
}
get width() {
return this.ref?.videoWidth || this.ref?.width || 0;
}
get height() {
return this.ref?.videoHeight || this.ref?.height || 0;
}
}
customElements.define("repa-shader", RepaShader); customElements.define("repa-shader", RepaShader);
customElements.define("repa-texture", RepaTexture);
export default RepaShader; export default RepaShader;
export {
RepaShader,
RepaTexture,
};

290
src/repa-texture.js Normal file
View File

@ -0,0 +1,290 @@
const createLogger = pfx => {
return {
info: (...args) => console.info(...pfx, ...args),
log: (...args) => console.log(...pfx, ...args),
warn: (...args) => console.warn(...pfx, ...args),
error: (...args) => console.error(...pfx, ...args),
};
};
const isImage = (src) => {
return /\w+\.(jpg|png|jpeg|svg|webp)(?=\?|$)/i.test(src);
};
const isVideo = (src) => {
return /\w+\.(mp4|3gp|webm|ogv|avif)(?=\?|$)/i.test(src);
};
/* TODO
* - display content on canvas
* - src change
* - ready promise
**
* types: image, [video, webcam], [canvas, shader]
*/
class RepaTexture extends HTMLElement {
constructor() {
super();
this.logger = createLogger(["%c[repa-texture]", "background: #282828; color: #fabd2f"]);
this.ready = false;
this._forceUpdate = false;
}
connectedCallback() {
this._load();
}
disconnectedCallback() {
// TODO ?
}
static get observedAttributes() {
return ['src', 'type', 'name', 'mag-filter', 'min-filter', 'wrap-s', 'wrap-t'];
}
attributeChangedCallback(name, oldValue, newValue) {
this.logger.info(`Attribute changed: ${name} ${oldValue} -> ${newValue}`);
if (this.ready) {
if (name === 'src') {
this._load(true);
} else {
this._forceUpdate = true;
}
}
}
async _load(reload = false) {
if (this.ready && !reload) {
return;
}
this.ready = false;
// drop old content if we created it
if (this._hiddenEl) {
this._hiddenEl.remove();
this.ref = null;
}
// load new ref
const ref = this.getAttribute('ref');
if (ref) {
const refEl = document.getElementById(ref);
if (refEl) {
this.ref = refEl;
}
}
if (!this.ref) {
const src = this.getAttribute('src');
if (src) {
try {
this.ref = await this._loadSource(src);
} catch (e) {
this.logger.error(`Source ${src} cannot be loaded:`, e);
this.ready = true;
}
}
}
if (this.ref) {
this.ready = true;
this._forceUpdate = true;
} else {
this.logger.error('Source cannot be loaded');
}
}
async _loadSource(src) {
const type = this.getAttribute('type') || this._guessType(this.src);
this._type = type;
if (!type) {
this.logger.error(`Unknown type: ${src}`);
} else {
this.logger.log(`Loading ${src} as ${type}`);
}
let ref = null;
switch (type) {
case 'image':
ref = await this._loadImage(src);
break;
case 'video':
ref = await this._loadVideo(src);
break;
case 'webcam':
ref = await this._createWebcam();
break;
}
return ref;
}
_hideInDOM(el) {
const hiddenEl = document.createElement('div');
hiddenEl.setAttribute('hidden', '');
hiddenEl.style.width = hiddenEl.style.height = '1px';
hiddenEl.style.overflow = 'hidden';
hiddenEl.style.position = 'absolute';
hiddenEl.style.top = hiddenEl.style.left = '-100px';
hiddenEl.style.zIndex = '-100';
hiddenEl.style.opacity = '0';
hiddenEl.style.pointerEvents = 'none';
hiddenEl.appendChild(el);
this._hiddenEl = hiddenEl;
document.body.appendChild(this._hiddenEl);
}
async _loadImage(src) {
const imgEl = document.createElement('img');
imgEl.crossOrigin = 'anonymous';
const loader = new Promise((resolve, reject) => {
imgEl.onload = () => resolve(imgEl);
imgEl.onerror = reject;
imgEl.src = src;
});
this._hideInDOM(imgEl);
return await loader;
}
async _loadVideo(src) {
const videoEl = document.createElement('video');
videoEl.crossOrigin = 'anonymous';
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.src = src;
this._hideInDOM(videoEl);
return videoEl;
}
async _createWebcam() {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const videoEl = document.createElement('video');
videoEl.autoplay = true;
videoEl.width = 640;
videoEl.height = 480;
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
videoEl.srcObject = stream;
this._hideInDOM(videoEl);
return videoEl;
}
this.logger.error('Webcam is not supported');
return null;
}
get src() {
return this.getAttribute('src');
}
get type() {
if (this._type) {
return this._type;
}
if (this.ref) {
if (this.ref instanceof HTMLImageElement) {
return 'image';
} else if (this.ref instanceof HTMLVideoElement) {
return 'video';
} else if (this.ref instanceof HTMLCanvasElement) {
return 'canvas';
} else if (this.ref.nodeName === 'REPA-SHADER') {
return 'shader';
}
}
return null;
}
get content() {
if (this.ref) {
if (this.type === 'shader') {
return this.ref.target;
} else {
return this.ref;
}
}
return null;
}
_guessType(src) {
if (src.toLowerCase() === 'webcam') {
return 'webcam';
}
if (isImage(src)) {
return 'image';
}
if (isVideo(src)) {
return 'video';
}
return null;
}
get shouldUpdate() {
return this._forceUpdate || (this.ref && this.ref instanceof HTMLVideoElement && this.ref.readyState === this.ref.HAVE_ENOUGH_DATA);
}
get width() {
return this.ref?.videoWidth || this.ref?.width || 0;
}
get height() {
return this.ref?.videoHeight || this.ref?.height || 0;
}
get magFilter() {
return this.getAttribute('mag-filter') || 'linear';
}
get minFilter() {
return this.getAttribute('min-filter') || 'linear';
}
get wrapS() {
return this.getAttribute('wrap-s') || 'clamp-to-edge';
}
get wrapT() {
return this.getAttribute('wrap-t') || 'clamp-to-edge';
}
get name() {
if (!this._name) {
let name = this.getAttribute('name');
if (!name) {
const textures = this.parentNode.querySelectorAll('repa-texture');
textures.forEach((texture, i) => {
if (texture === this) {
name = `texture${i}`;
}
});
}
this._name = name;
}
return this._name;
}
}
customElements.define("repa-texture", RepaTexture);
export default RepaTexture;