在插件中从UInt8Array加载Three.js纹理
我正在尝试为JPEG 360格式实现一个three.js纹理加载器。为此,它其实是一个嵌入在一组“盒子”中的JPEG图像,与MP4 / HEIF / QuickTime中使用的结构非常相似。
我可以从嵌套的JUMBF盒子中解析出JPEG编码流,没问题。
在这一点上,我本来希望能够直接调用现有的TextureLoader,传入JPEG编码流内容。可是结果却只是返回 undefined。
正在进行中的实验代码看起来是这样的:
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编码流中得到一个解析后的纹理对象(即 DataTextureLoader~TexData)返回?
解决方案
更新:
纹理的类型是
Texture,但.parse()的所需返回类型应为DataTextureLoader~TexData。
这里存在利益冲突。你有JPEG数据(它需要 TextureLoader),但你所需要的 DataTextureLoader 是用于加载“原始数据”图像格式的(如RGBA、RGBE/HDR、EXR、TGA)。
选项#1:
制作一个替代函数,将JPEG转换成 Texture 类型,然后按通常方式在你的ThreeJS场景中使用该纹理。它作为返回一个 DataTexture 的替代,返回 Texture。
if( codestream !== null )
{
console.log("found codestream in JUMBF wrappers");
let texture = -1; let url_texture = -1;
url_texture = URL.createObjectURL( new Blob([new Uint8Array( codestream )], { type: 'application/octet-stream' }) );
let loader = new THREE.TextureLoader();
texture = loader.load ( url_texture , onload_texture );
function onload_texture()
{
alert("Got a texture loaded OK");
console.log("Texture loaded is type : " + texture);
}
return texture;
}
选项#2:
如果你确实需要它为 DataTexture,可以尝试跳过 .parse() 部分,而改为仅从已知数据动态创建一个。你在这种情况下获取“数据”的唯一方法是先把Uint8Array数据加载到一个 Image 元素中,然后把该元素的像素绘制到一个 Canvas 中,以便再提取RGBA格式的“原始数据”。
if( codestream !== null )
{
console.log("found codestream in JUMBF wrappers");
let texture = -1; let url_texture = -1;
let canvas_in = document.createElement('canvas');
let context = canvas_in.getContext('2d');
url_texture = URL.createObjectURL( new Blob([new Uint8Array( codestream )], { type: 'application/octet-stream' }) );
let temp_img = new Image(); temp_img.src = url_texture; temp_img.decode();
temp_img.onload = (event) => {
canvas_in.width = temp_img.naturalWidth;
canvas_in.height = temp_img.naturalHeight;
context.drawImage(temp_img, 0, 0 );
let obj_imgData = context.getImageData(0, 0, temp_img.naturalWidth, temp_img.naturalHeight);
//## instead of using .parse() ...
//texture = new TextureLoader().parse(codestream);
//## use DataTexture from a dynamically created instance
//## constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, colorSpace )
texture = new THREE.DataTexture( obj_imgData.data ,
obj_imgData.width , obj_imgData.height ,
THREE.RGBAFormat , THREE.UnsignedByteType , THREE.DEFAULT_MAPPING ,
THREE.ClampToEdgeWrapping , THREE.ClampToEdgeWrapping ,
THREE.NearestFilter , THREE.NearestFilter , 1 , THREE.NoColorSpace
);
//texture.flipY = false;
texture.generateMipmaps = false; //#try as: true ... if image quality is affected
texture.needsUpdate = true;
console.log("Texture loaded v2 is type : " + texture);
return texture;
}
}
如果你仍然无法按你想要的方式获取到 TexData,那么最后一个备选是把 obj_imgData.data 传给一个基于JS的外部编码器(RGBE或 EXR或 TGA图像格式)的实现。此类编码数据(例如EXR格式)应当能与 .parse() 方法一起工作,并有望为你返回一个 DataTextureLoader~TexData 对象。
原始答案:
“现在有没有一个合乎常理的方式从JPEG编码流中取回纹理?”
假设 "codestream" 的类型是 Uint8Array(或者可以转换成这样的类型),那么你可以尝试把它作为一个“虚拟”的图像文件加载:
if (codestream !== null)
{
console.log("found codestream in JUMBF wrappers");
let texture = -1; let url_texture = -1;
let tmp_hex_str = "";
let len_bytes = codestream.length;
//### (1) Check if JPEG data is valid ...
//# check first two bytes of codestream bytes array
tmp_hex_str = ( (codestream[0] << 8) | codestream[1] ).toString(16).toUpperCase();
console.log("JPEG bytes START : 0x" + tmp_hex_str + " ... (expected: 0xFFD8)" );
//# check last two bytes of codestream bytes array
tmp_hex_str = ( (codestream[len_bytes-2] << 8) | codestream[len_bytes-1] ).toString(16).toUpperCase();
console.log("JPEG bytes END : 0x" + tmp_hex_str + " ... (expected: 0xFFD9)" );
//### (2) Try to load as virtual Image file ...
url_texture = URL.createObjectURL( codestream );
//let url_texture = URL.createObjectURL( new Uint8Array( codestream ) ); //# if array type converting is needed
let loader = new THREE.TextureLoader();
texture = loader.load ( url_texture , onload_texture );
function onload_texture()
{
alert("Got a texture loaded OK");
console.log("texture loaded is : " + texture);
}
return texture;
}