Skip to content

Commit b22c67b

Browse files
committed
image texture node report error + handle exr loading + removed preview + allow for the texture loader to be customizable by the user
1 parent 36373c8 commit b22c67b

File tree

3 files changed

+113
-36
lines changed

3 files changed

+113
-36
lines changed

src/export/Script.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as TSL from 'three/tsl';
22
import * as THREE from 'three/webgpu';
3-
3+
import * as ADDONS from 'three/examples/jsm/Addons.js';
44

55
export type UniformType = "boolean" | "number" | "Color" | "Vector2" | "Vector3" | "Vector4" | "Matrix3" | "Matrix4";
66

@@ -19,7 +19,7 @@ export class Script {
1919
*/
2020
protected uniforms:[ name:string, type:UniformType, initialValue:string ][]=[];
2121

22-
protected imagePaths:[ path:string, previewImage?:string, textureSetup?:( refName:string)=>string ][] ;
22+
protected imagePaths:[ path:string, mime:string, previewImage?:string, textureSetup?:( refName:string)=>string ][] ;
2323
protected moduleName2Ref:Record<string, unknown>;
2424

2525
constructor( ) {
@@ -95,7 +95,7 @@ export class Script {
9595
* Tells that the script will need this texture loaded before even running...
9696
* @param imagePath
9797
*/
98-
loadTexture( imagePath?:string, previewImageSrc?:string, textureSetup?:( refName:string)=>string )
98+
loadTexture( imagePath?:string, mimeType?:string, previewImageSrc?:string, textureSetup?:( refName:string)=>string )
9999
{
100100
if( !imagePath )
101101
{
@@ -119,17 +119,23 @@ export class Script {
119119
return "no_texture";
120120
}
121121

122+
if( mimeType?.includes("exr") )
123+
{
124+
this.importModule("EXRLoader","three/examples/jsm/Addons.js", ADDONS);
125+
}
126+
122127
let index = this.imagePaths.findIndex( paths=>paths[1]==previewImageSrc );
123128

124129
if( index<0 )
125-
index = this.imagePaths.push([ imagePath, previewImageSrc!, textureSetup ])-1;
130+
index = this.imagePaths.push([ imagePath, mimeType!, previewImageSrc!, textureSetup ])-1;
126131

127-
return `textureLoader${ index }`;
132+
return `texture${ index }`;
128133
}
129134

130135
toString( lastExpression:string="", forExport = false ) {
131136

132137
let output = `\n`;
138+
let exportKeyword = (forExport?"export":"");
133139

134140
if( forExport )
135141
{
@@ -155,7 +161,7 @@ export class Script {
155161
//
156162
// uniforms
157163
//
158-
output += "\n" + (forExport?"export":"") + " const $uniforms = {\n" +
164+
output += "\n" + exportKeyword + " const $uniforms = {\n" +
159165
this.uniforms.map( uniform => `${uniform[0]} : uniform( ${uniform[2]} )`).join(",")
160166
+"\n}\n";
161167

@@ -167,17 +173,43 @@ export class Script {
167173
//
168174
// utility function to load the texture...
169175
//
170-
output += `\nconst loadTexture = url => new THREE.TextureLoader().load(url);
176+
output += `
177+
let onImageError = (url, err)=>console.error( "Failed to load: "+url, err );
178+
179+
/**
180+
* If an image fails to load this will be called...
181+
* @param {(failedUrl:string, error:unknown)=>void customErrorHandler
182+
* @returns
183+
*/
184+
${exportKeyword} const setCustomImageErrorHanlder = ( customErrorHandler )=>onImageError=customErrorHandler;
185+
186+
let loadTexture = (url, mimeType, onLoaded) => {
187+
if (mimeType.includes("exr")) {
188+
const exrLoader = new EXRLoader();
189+
190+
return exrLoader.load(url, onLoaded, undefined, onImageError.bind(null, url) );
191+
} else {
192+
const textureLoader = new THREE.TextureLoader();
193+
194+
return textureLoader.load(url, onLoaded, undefined, onImageError.bind(null, url));
195+
}
196+
};
197+
198+
/**
199+
* Given a image url (or the name of the image if it was loaded via "frile from disk") should return the corresponding texture
200+
* @param {(url:string, mimeType:string, onLoaded:(data: THREE.DataTexture, texData: object) => void)=>THREE.Texture} customLoader
201+
* @returns
202+
*/
203+
${exportKeyword} const setCustomImageLoader = ( customLoader )=>loadTexture=customLoader;
171204
`;
172205

173206
//
174207
// for each used texture...
175208
//
176-
this.imagePaths.forEach( ([ path, previewUrl, setupTexture ], index)=>{
209+
this.imagePaths.forEach( ([ path, mime, previewUrl, setupTexture ], index)=>{
177210

178-
output += `\nconst textureLoader${ index } = loadTexture('${ forExport? path : previewUrl }');
179-
${ setupTexture? setupTexture(`textureLoader${ index }`) :"" }
180-
`;
211+
output += `
212+
const texture${ index } = loadTexture('${ forExport? path : previewUrl }', '${mime}'${ setupTexture? `, texture => { ${setupTexture(`texture`)} }` :"" });`;
181213

182214
});
183215
}

src/nodes/texture/ImageTextureNode.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,21 @@ export class ImageTextureNode extends TextureTypeNode {
3636
script.importModule("texture");
3737

3838

39-
const map = script.loadTexture( this.imageProp.imagePath, this.imageProp.imageSrc, texture => `
40-
${texture}.wrapS = ${ this.extensionPolicy.extensionMode };
41-
${texture}.wrapT = ${ this.extensionPolicy.extensionMode };
42-
${texture}.mapping = ${ this.mappingPolicy.mappingType };
43-
${texture}.flipY = false;
39+
const map = script.loadTexture(
40+
this.imageProp.imagePath,
41+
this.imageProp.imageType,
42+
this.imageProp.imageSrc,
43+
44+
45+
texture => `
46+
${texture}.wrapS = ${ this.extensionPolicy.extensionMode };
47+
${texture}.wrapT = ${ this.extensionPolicy.extensionMode };
48+
${texture}.mapping = ${ this.mappingPolicy.mappingType };
49+
${texture}.flipY = false;
50+
`
51+
) ;
4452

45-
` ) ;
46-
47-
const uv = this.uv.writeScript(script);
48-
53+
const uv = this.uv.writeScript(script);
4954

5055
return script.define( this.nodeName, `texture( ${map}, ${uv} )`);
5156

@@ -69,4 +74,9 @@ export class ImageTextureNode extends TextureTypeNode {
6974
this.extensionPolicy.extensionMode = data.extension;
7075
this.mappingPolicy.mappingType = data.mapping;
7176
}
77+
78+
override onRemoved(): void {
79+
super.onRemoved();
80+
this.imageProp.dispose();
81+
}
7282
}

src/properties/TextureProperty.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11

2-
import { Theme } from "../colors/Theme";
32
import { Button } from "../components/Button";
4-
import { ImagePreview } from "../components/ImagePreview";
5-
import { TextLabel } from "../components/TextLabel";
6-
import { IOutlet } from "../core/IOutlet";
3+
//import { ImagePreview } from "../components/ImagePreview";
4+
import { TextLabel } from "../components/TextLabel";
75
import { Layout, Row } from "../layout/Layout";
8-
import { LayoutElement } from "../layout/LayoutElement";
9-
import { Input } from "./Input";
6+
import { LayoutElement } from "../layout/LayoutElement";
107

118
export class TextureProperty extends LayoutElement {
129

1310
private initial : Layout;
1411
private overwritten : Layout;
1512
private _imageDisplayLayout : Layout;
13+
private currentLoadedImage? : {
14+
url:string,
15+
mime:string,
16+
dispose:VoidFunction
17+
};
1618

1719
/**
1820
* The path/url used to load the image. In case of a file selected from disk this will only be the filename.
@@ -23,7 +25,8 @@ export class TextureProperty extends LayoutElement {
2325
private _isFromDisk = false;
2426
get isFromDisk() { return this._isFromDisk }
2527

26-
private imgPreview:ImagePreview;
28+
//private imgPreview:ImagePreview;
29+
private filenameLabel:TextLabel;
2730

2831
get imagePath() {
2932
return this._filePath;
@@ -34,6 +37,10 @@ export class TextureProperty extends LayoutElement {
3437
this.loadImage( imageSrc )
3538
}
3639

40+
get imageType() {
41+
return this.currentLoadedImage?.mime;
42+
}
43+
3744

3845
constructor() {
3946
super()
@@ -47,15 +54,16 @@ export class TextureProperty extends LayoutElement {
4754
justify:"space-around"
4855
});
4956

50-
this.imgPreview = new ImagePreview();
57+
//this.imgPreview = new ImagePreview();
58+
this.filenameLabel = new TextLabel("");
5159

5260
//"column","start","stretch",
5361
this._imageDisplayLayout = new Layout( [
5462

5563
//"row","space-around","center",
5664
new Layout( [
5765

58-
this.imgPreview,
66+
this.filenameLabel,
5967
new Button("X", ()=>this.reset() ),
6068

6169
], {
@@ -86,7 +94,7 @@ export class TextureProperty extends LayoutElement {
8694
return new Promise<File|null>((resolve) => {
8795
const input = document.createElement('input');
8896
input.type = 'file';
89-
input.accept = 'image/*'; // Restrict to image files
97+
input.accept = 'image/*,.exr,image/x-exr'; // Restrict to image files
9098
input.style.display = 'none';
9199

92100
input.addEventListener('change', (event: Event) => {
@@ -117,18 +125,37 @@ export class TextureProperty extends LayoutElement {
117125
const reader = new FileReader();
118126

119127
reader.onload = e => {
120-
const dataURL = e.target!.result as string; // dataURL is a string
121-
122-
this.loadImage( dataURL );
128+
const arrayBuffer = e.target!.result as ArrayBuffer;
129+
// Create a Blob from the ArrayBuffer
130+
const imageBlob = new Blob([arrayBuffer], { type: file.type }); // Use original file type
131+
132+
// Create an Object URL from the Blob
133+
const imageURL = URL.createObjectURL(imageBlob);
134+
135+
this.currentLoadedImage = {
136+
url: imageURL,
137+
mime: file.type,
138+
dispose: ()=>URL.revokeObjectURL(imageURL)
139+
}
140+
//image/x-exr
141+
142+
this.loadImage( imageURL );
123143
};
124144

125-
reader.readAsDataURL(file);
145+
reader.onerror = (e) => {
146+
console.error("FileReader error:", e);
147+
alert("Oops! Error loading the image...")
148+
};
149+
150+
reader.readAsArrayBuffer(file);
126151
}
127152

128153
protected loadImage( url:string ) {
129154

130155
this._imageSrc = url;
131-
this.imgPreview.show( url );
156+
//this.imgPreview.show( url );
157+
this.filenameLabel.label = this.imagePath!;
158+
132159
this.layout = this._imageDisplayLayout;
133160
this.singleLine = false;
134161

@@ -150,11 +177,19 @@ export class TextureProperty extends LayoutElement {
150177

151178

152179
protected reset() {
153-
this.imgPreview.reset();
180+
181+
this.dispose();
182+
183+
// this.imgPreview.reset();
154184
this.layout = this.initial;
155185
this.singleLine = true;
156186

157187
this.root.update()
158188
}
189+
190+
dispose() {
191+
this.currentLoadedImage?.dispose();
192+
this.currentLoadedImage = undefined;
193+
}
159194

160195
}

0 commit comments

Comments
 (0)