You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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纹理。

具体实现步骤

  1. 将Uint8Array转为Blob对象
    把解析得到的codestream包装成Blob,注意根据实际格式指定正确的MIME类型(从jp2c标签判断应为JPEG 2000,对应image/jp2):

    const blob = new Blob([codestream], { type: 'image/jp2' });
    
  2. 将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
            };
        }
    }
    
  3. 替代方案:直接自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.02 07:47:27