mirror of
https://github.com/dyuri/repa-shader.git
synced 2025-12-16 11:14:25 +00:00
texture element improvements
This commit is contained in:
parent
9b2d99e5a0
commit
5c137b4d23
@ -3,6 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title><repa-shader> demo</title>
|
<title><repa-shader> 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() {
|
||||||
|
|||||||
@ -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
290
src/repa-texture.js
Normal 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;
|
||||||
Loading…
Reference in New Issue
Block a user