如何在three.js插件中从UInt8Array加载JPEG 360纹理?
我正尝试为JPEG 360格式实现一个three.js纹理加载器。该格式实际是将JPEG图像嵌入在一组嵌套的"box"中,与MP4/HEIF/Quicktime所用结构十分相似。
我已成功从嵌套的JUMBF box中解析出JPEG码流。此时原本希望直接调用现有的TextureLoader传入JPEG码流内容,但该调用仅返回undefined。
目前的开发中(WIP)实验代码如下:
class JPEG360Loader extends DataTextureLoader { /** * Constructs a new JPEG 360 loader. * * @param {LoadingManager} [manager] - The loading manager. */ constructor(manager) { super(manager); } /** * Parses the given JPEG 360 texture data. * * @param {ArrayBuffer} buffer - The raw texture data. * @return {DataTextureLoader~TexData} An object representing the parsed texture data. */ parse(buffer) { let codestream = null; let stream = new DataStream(buffer); let size = stream.readUint32(); console.log(size); // TODO: check size is consistent with buffer size let type = stream.readString(4); console.log(type); // TODO: check type == "jumb" while (stream.hasRemaining()) { let size = stream.readUint32(); console.log(size); let type = stream.readString(4); console.log(type); if (type === "jumd") { let toggles = stream.readUint8(); console.log("JUMD TOGGLE: " + toggles.toString(16)); // TODO: parse out remainder stream.seek_relative(size - 9); } else if (type === "jumb") { console.log("second level JUMBF parsing"); let jumbf_end_position = stream._position + (size - 8); let second_level_jumd_size = stream.readUint32(); let second_level_jumd_fourcc = stream.readString(4); console.log("JUMBF descriptor 4CC: " + second_level_jumd_fourcc); if (second_level_jumd_fourcc !== "jumd") { console.log("unexpected first entry, expected jumd but got " + second_level_jumd_fourcc); } // TODO: parse toggles and optional entries instead of skipping over it. stream.seek_relative(second_level_jumd_size - 8); while (stream._position < jumbf_end_position) { let child_size = stream.readUint32(); let child_type = stream.readString(4); if (child_type === "xml ") { let xml_body = stream.readString(child_size - 8); console.log(xml_body); } else if (child_type === "jp2c") { codestream = stream.readUint8Array(child_size - 8); } else { console.log("unhandled JUMBF child: " + child_type + ", size: " + child_size); stream.seek_relative(child_size - 8); } } } else { console.log("Unsupported second level box"); stream.seek_relative(size - 8); } } if (codestream !== null) { console.log("found codestream in JUMBF wrappers"); let texture = new TextureLoader().parse(codestream); console.log(texture); return texture; } } }
请问此时有没有合理的方法从该JPEG码流中获取纹理?
核心问题说明
TextureLoader.parse() 不支持直接传入Uint8Array格式的原始JPEG/JP2码流,它的设计目标是处理浏览器已解析完成的图像数据,而非二进制原始码流。需要将解析出的码流转为浏览器可识别的图像源,再创建Three.js纹理。
具体实现步骤
将Uint8Array转为Blob对象
把解析得到的codestream包装成Blob,注意根据实际格式指定正确的MIME类型(从jp2c标签判断应为JPEG 2000,对应image/jp2):const blob = new Blob([codestream], { type: 'image/jp2' });将Blob转为可加载的图像源
可以通过URL.createObjectURL()生成临时URL,或使用FileReader转为DataURL,再用TextureLoader加载该URL。如果需要适配DataTextureLoader的parse方法返回值,更稳妥的方式是先转为Image对象:// 修改parse方法为异步函数 async parse(buffer) { // 保留你原有的box解析逻辑,最终得到codestream let codestream = null; let stream = new DataStream(buffer); // ... 你的解析代码 ... if (codestream !== null) { const blob = new Blob([codestream], { type: 'image/jp2' }); const url = URL.createObjectURL(blob); // 异步加载Image对象 const img = await new Promise((resolve, reject) => { const image = new Image(); image.onload = () => resolve(image); image.onerror = reject; image.src = url; }); URL.revokeObjectURL(url); // 释放临时URL资源 // 返回符合DataTextureLoader要求的TexData结构 return { image: img, width: img.width, height: img.height, format: THREE.RGBFormat, // 根据图像实际通道调整(如RGBAFormat) type: THREE.UnsignedByteType }; } }替代方案:直接自定义Loader加载流程
如果不想依赖DataTextureLoader的异步parse,可以自定义Loader的load方法,直接处理二进制数据:class JPEG360Loader extends THREE.Loader { constructor(manager) { super(manager); } load(url, onLoad, onProgress, onError) { const loader = new THREE.FileLoader(this.manager); loader.setPath(this.path); loader.setResponseType('arraybuffer'); loader.load(url, (buffer) => { // 解析box结构得到codestream let codestream = null; let stream = new DataStream(buffer); // ... 你的解析代码 ... if (codestream) { const blob = new Blob([codestream], { type: 'image/jp2' }); const imgUrl = URL.createObjectURL(blob); const texLoader = new THREE.TextureLoader(this.manager); texLoader.load(imgUrl, (texture) => { URL.revokeObjectURL(imgUrl); onLoad(texture); }, onProgress, onError); } else { onError(new Error('Failed to extract JPEG 360 codestream')); } }, onProgress, onError); } }
关键注意点
- 确认
codestream是完整的图像码流,JPEG 2000码流开头应有固定标识(0x0000000C6A5020200D0A870A),如果解析出的码流不完整,Image会加载失败。 - 若使用Three.js旧版本,
DataTextureLoader的parse方法可能不支持异步返回,此时推荐使用自定义Loader的load方法实现异步加载流程。
内容的提问来源于stack exchange,提问作者BradHards

