Comparing version 0.0.1 to 0.0.3
@@ -11,2 +11,3 @@ export declare class BufferReader { | ||
_skip(bytes: number): this; | ||
_scan(maxByteLength: number, term?: number): Uint8Array; | ||
} |
@@ -1,20 +0,94 @@ | ||
import { KTX2DataFormatDescriptor, KTX2GlobalData, KTX2KeyValue, KTX2Level, KTX2SupercompressionScheme } from './ktx2-schema'; | ||
export declare class Container { | ||
import { KTX2SupercompressionScheme } from './constants'; | ||
/** | ||
* Represents an unpacked KTX 2.0 texture container. Data for individual mip levels are stored in | ||
* the `.levels` array, typically compressed in Basis Universal formats. Additional properties | ||
* provide metadata required to process, transcode, and upload these textures. | ||
*/ | ||
export declare class KTX2Container { | ||
/** | ||
* Specifies the image format using Vulkan VkFormat enum values. When using Basis Universal | ||
* texture formats, `vkFormat` must be VK_FORMAT_UNDEFINED. | ||
*/ | ||
vkFormat: number; | ||
/** | ||
* Size of the data type in bytes used to upload the data to a graphics API. When `vkFormat` is | ||
* VK_FORMAT_UNDEFINED, `typeSize` must be 1. | ||
*/ | ||
typeSize: number; | ||
/** Width of the texture image for level 0, in pixels. */ | ||
pixelWidth: number; | ||
/** Height of the texture image for level 0, in pixels. */ | ||
pixelHeight: number; | ||
/** Depth of the texture image for level 0, in pixels (3D textures only). */ | ||
pixelDepth: number; | ||
/** Number of array elements (array textures only). */ | ||
layerCount: number; | ||
/** | ||
* Number of cubemap faces. For cubemaps and cubemap arrays, `faceCount` must be 6. For all | ||
* other textures, `faceCount` must be 1. Cubemap faces are stored in +X, -X, +Y, -Y, +Z, -Z | ||
* order. | ||
*/ | ||
faceCount: number; | ||
levelCount: number; | ||
/** Indicates which supercompression scheme has been applied to mip level images, if any. */ | ||
supercompressionScheme: KTX2SupercompressionScheme; | ||
/** Mip Levels. */ | ||
levelIndex: KTX2Level[]; | ||
/** Mip levels, ordered largest (original) to smallest (~1px). */ | ||
levels: KTX2Level[]; | ||
/** Data Format Descriptor. */ | ||
dataFormatDescriptor: KTX2DataFormatDescriptor[]; | ||
dataFormatDescriptor: KTX2DataFormatDescriptorBasicFormat[]; | ||
/** Key/Value Data. */ | ||
keyValue: KTX2KeyValue[]; | ||
keyValue: { | ||
[key: string]: string | Uint8Array; | ||
}; | ||
/** Supercompression Global Data. */ | ||
globalData: KTX2GlobalData | null; | ||
globalData: KTX2GlobalDataBasisLZ | null; | ||
} | ||
export interface KTX2Level { | ||
/** Compressed data of the mip level. */ | ||
levelData: Uint8Array; | ||
/** Uncompressed size of the mip level. */ | ||
uncompressedByteLength: number; | ||
} | ||
export interface KTX2DataFormatDescriptorBasicFormat { | ||
vendorId: number; | ||
descriptorType: number; | ||
versionNumber: number; | ||
descriptorBlockSize: number; | ||
colorModel: number; | ||
colorPrimaries: number; | ||
transferFunction: number; | ||
flags: number; | ||
texelBlockDimension: KTX2BasicFormatTexelBlockDimensions; | ||
bytesPlane: number[]; | ||
samples: KTX2BasicFormatSample[]; | ||
} | ||
export interface KTX2BasicFormatTexelBlockDimensions { | ||
x: number; | ||
y: number; | ||
z: number; | ||
w: number; | ||
} | ||
export interface KTX2BasicFormatSample { | ||
bitOffset: number; | ||
bitLength: number; | ||
channelID: number; | ||
samplePosition: number[]; | ||
sampleLower: number; | ||
sampleUpper: number; | ||
} | ||
export interface KTX2GlobalDataBasisLZ { | ||
endpointCount: number; | ||
selectorCount: number; | ||
imageDescs: KTX2GlobalDataBasisLZImageDesc[]; | ||
endpointsData: Uint8Array; | ||
selectorsData: Uint8Array; | ||
tablesData: Uint8Array; | ||
extendedData: Uint8Array; | ||
} | ||
interface KTX2GlobalDataBasisLZImageDesc { | ||
imageFlags: number; | ||
rgbSliceByteOffset: number; | ||
rgbSliceByteLength: number; | ||
alphaSliceByteOffset: number; | ||
alphaSliceByteLength: number; | ||
} | ||
export {}; |
@@ -1,2 +0,2 @@ | ||
var t;!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(t||(t={}));var e=function(){this.vkFormat=0,this.typeSize=-1,this.pixelWidth=-1,this.pixelHeight=-1,this.pixelDepth=-1,this.layerCount=-1,this.faceCount=-1,this.levelCount=-1,this.supercompressionScheme=t.NONE,this.levelIndex=[],this.dataFormatDescriptor=[],this.keyValue=[],this.globalData=null},n=function(){function t(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}var e=t.prototype;return e._nextUint8=function(){var t=this._dataView.getUint8(this._offset);return this._offset+=1,t},e._nextUint16=function(){var t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t},e._nextUint32=function(){var t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t},e._nextUint64=function(){var t=this._dataView.getUint32(this._offset,this._littleEndian),e=this._dataView.getUint32(this._offset+4,this._littleEndian),n=t+Math.pow(2,32)*e;return this._offset+=8,n},e._skip=function(t){return this._offset+=t,this},t}();exports.Container=e,exports.read=function(t){var i=new Uint8Array(t,0,12);if(171!==i[0]||75!==i[1]||84!==i[2]||88!==i[3]||32!==i[4]||50!==i[5]||48!==i[6]||187!==i[7]||13!==i[8]||10!==i[9]||26!==i[10]||10!==i[11])throw new Error("Missing KTX 2.0 identifier.");var s=new e,r=17*Uint32Array.BYTES_PER_ELEMENT,a=new n(t,12,r,!0);s.vkFormat=a._nextUint32(),s.typeSize=a._nextUint32(),s.pixelWidth=a._nextUint32(),s.pixelHeight=a._nextUint32(),s.pixelDepth=a._nextUint32(),s.layerCount=a._nextUint32(),s.faceCount=a._nextUint32(),s.levelCount=a._nextUint32(),s.supercompressionScheme=a._nextUint32();for(var o=a._nextUint32(),_=a._nextUint32(),l=(a._nextUint32(),a._nextUint32(),a._nextUint64()),x=a._nextUint64(),U=new n(t,12+r,3*s.levelCount*8,!0),h=0;h<s.levelCount;h++)s.levelIndex.push({data:new Uint8Array(t,U._nextUint64(),U._nextUint64()),uncompressedByteLength:U._nextUint64()});var f=new n(t,o,_,!0),u={vendorId:f._skip(4)._nextUint16(),versionNumber:f._skip(2)._nextUint16(),descriptorBlockSize:f._nextUint16(),colorModel:f._nextUint8(),colorPrimaries:f._nextUint8(),transferFunction:f._nextUint8(),flags:f._nextUint8(),texelBlockDimension:{x:f._nextUint8()+1,y:f._nextUint8()+1,z:f._nextUint8()+1,w:f._nextUint8()+1},bytesPlane0:f._nextUint8(),numSamples:0,samples:[]};u.numSamples=(u.descriptorBlockSize/4-6)/4,f._skip(7);for(var p=0;p<u.numSamples;p++)u.samples[p]={channelID:f._skip(3)._nextUint8()},f._skip(12);if(x<=0)return s;for(var c=new n(t,l,x,!0),d=c._nextUint16(),y=c._nextUint16(),v=c._nextUint32(),w=c._nextUint32(),g=c._nextUint32(),m=c._nextUint32(),S=[],B=0;B<s.levelCount;B++)S.push({imageFlags:c._nextUint32(),rgbSliceByteOffset:c._nextUint32(),rgbSliceByteLength:c._nextUint32(),alphaSliceByteOffset:c._nextUint32(),alphaSliceByteLength:c._nextUint32()});var D=l+c._offset,E=D+v,k=E+w,C=k+g,L=new Uint8Array(t,D,v),b=new Uint8Array(t,E,w),A=new Uint8Array(t,k,g),I=new Uint8Array(t,C,m);return s.globalData={endpointCount:d,selectorCount:y,endpointsByteLength:v,selectorsByteLength:w,tablesByteLength:g,extendedByteLength:m,imageDescs:S,endpointsData:L,selectorsData:b,tablesData:A,extendedData:I},s},exports.write=function(t){return new Uint8Array}; | ||
var t,e,n,i,r,a,s,o,l=new Uint8Array([0]),f=[171,75,84,88,32,50,48,187,13,10,26,10];!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(t||(t={})),function(t){t[t.BASICFORMAT=0]="BASICFORMAT"}(e||(e={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.ETC1S=163]="ETC1S",t[t.UASTC=166]="UASTC"}(n||(n={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.SRGB=1]="SRGB"}(i||(i={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.LINEAR=1]="LINEAR",t[t.SRGB=2]="SRGB",t[t.ITU=3]="ITU",t[t.NTSC=4]="NTSC",t[t.SLOG=5]="SLOG",t[t.SLOG2=6]="SLOG2"}(r||(r={})),function(t){t[t.ALPHA_STRAIGHT=0]="ALPHA_STRAIGHT",t[t.ALPHA_PREMULTIPLIED=1]="ALPHA_PREMULTIPLIED"}(a||(a={})),function(t){t[t.RGB=0]="RGB",t[t.RRR=3]="RRR",t[t.GGG=4]="GGG",t[t.AAA=15]="AAA"}(s||(s={})),function(t){t[t.RGB=0]="RGB",t[t.RGBA=3]="RGBA",t[t.RRR=4]="RRR",t[t.RRRG=5]="RRRG"}(o||(o={}));var U=function(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=t.NONE,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:e.BASICFORMAT,versionNumber:2,descriptorBlockSize:40,colorModel:n.UNSPECIFIED,colorPrimaries:i.SRGB,transferFunction:i.SRGB,flags:a.ALPHA_STRAIGHT,texelBlockDimension:{x:4,y:4,z:1,w:1},bytesPlane:[],samples:[]}],this.keyValue={},this.globalData=null},h=function(){function t(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}var e=t.prototype;return e._nextUint8=function(){var t=this._dataView.getUint8(this._offset);return this._offset+=1,t},e._nextUint16=function(){var t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t},e._nextUint32=function(){var t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t},e._nextUint64=function(){var t=this._dataView.getUint32(this._offset,this._littleEndian),e=this._dataView.getUint32(this._offset+4,this._littleEndian),n=t+Math.pow(2,32)*e;return this._offset+=8,n},e._skip=function(t){return this._offset+=t,this},e._scan=function(t,e){void 0===e&&(e=0);for(var n=this._offset,i=0;this._dataView.getUint8(this._offset)!==e&&i<t;)i++,this._offset++;return i<t&&this._offset++,new Uint8Array(this._dataView.buffer,this._dataView.byteOffset+n,i)},t}();function c(){return(c=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t}).apply(this,arguments)}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n<e;n++)i[n]=t[n];return i}function _(t,e){var n;if("undefined"==typeof Symbol||null==t[Symbol.iterator]){if(Array.isArray(t)||(n=function(t,e){if(t){if("string"==typeof t)return u(t,void 0);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(t,void 0):void 0}}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var i=0;return function(){return i>=t.length?{done:!0}:{done:!1,value:t[i++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}return(n=t[Symbol.iterator]()).next.bind(n)}function p(t){return"undefined"!=typeof TextEncoder?(new TextEncoder).encode(t):Buffer.from(t)}function g(t){return"undefined"!=typeof TextDecoder?(new TextDecoder).decode(t):Buffer.from(t).toString("utf8")}function y(t){for(var e,n=0,i=_(t);!(e=i()).done;)n+=e.value.byteLength;for(var r,a=new Uint8Array(n),s=0,o=_(t);!(r=o()).done;){var l=r.value;a.set(new Uint8Array(l),s),s+=l.byteLength}return a}var x={keepWriter:!1};exports.KTX2Container=U,exports.read=function(t){var e=new Uint8Array(t.buffer,t.byteOffset,f.length);if(e[0]!==f[0]||e[1]!==f[1]||e[2]!==f[2]||e[3]!==f[3]||e[4]!==f[4]||e[5]!==f[5]||e[6]!==f[6]||e[7]!==f[7]||e[8]!==f[8]||e[9]!==f[9]||e[10]!==f[10]||e[11]!==f[11])throw new Error("Missing KTX 2.0 identifier.");var n=new U,i=17*Uint32Array.BYTES_PER_ELEMENT,r=new h(t,f.length,i,!0);n.vkFormat=r._nextUint32(),n.typeSize=r._nextUint32(),n.pixelWidth=r._nextUint32(),n.pixelHeight=r._nextUint32(),n.pixelDepth=r._nextUint32(),n.layerCount=r._nextUint32(),n.faceCount=r._nextUint32();var a=r._nextUint32();n.supercompressionScheme=r._nextUint32();for(var s=r._nextUint32(),o=r._nextUint32(),l=r._nextUint32(),c=r._nextUint32(),u=r._nextUint64(),_=r._nextUint64(),p=new h(t,f.length+i,3*a*8,!0),y=0;y<a;y++)n.levels.push({levelData:new Uint8Array(t.buffer,t.byteOffset+p._nextUint64(),p._nextUint64()),uncompressedByteLength:p._nextUint64()});for(var x=new h(t,s,o,!0),b={vendorId:x._skip(4)._nextUint16(),descriptorType:x._nextUint16(),versionNumber:x._nextUint16(),descriptorBlockSize:x._nextUint16(),colorModel:x._nextUint8(),colorPrimaries:x._nextUint8(),transferFunction:x._nextUint8(),flags:x._nextUint8(),texelBlockDimension:{x:x._nextUint8()+1,y:x._nextUint8()+1,z:x._nextUint8()+1,w:x._nextUint8()+1},bytesPlane:[x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8()],samples:[]},d=(b.descriptorBlockSize/4-6)/4,v=0;v<d;v++)b.samples[v]={bitOffset:x._nextUint16(),bitLength:x._nextUint8(),channelID:x._nextUint8(),samplePosition:[x._nextUint8(),x._nextUint8(),x._nextUint8(),x._nextUint8()],sampleLower:x._nextUint32(),sampleUpper:x._nextUint32()};n.dataFormatDescriptor.length=0,n.dataFormatDescriptor.push(b);for(var D=new h(t,l,c,!0);D._offset<c;){var m=D._nextUint32(),A=D._scan(m),w=g(A),S=D._scan(m-A.byteLength);n.keyValue[w]=w.match(/^ktx/i)?g(S):S,m%4&&D._skip(4-m%4)}if(_<=0)return n;for(var B=new h(t,u,_,!0),L=B._nextUint16(),I=B._nextUint16(),R=B._nextUint32(),E=B._nextUint32(),T=B._nextUint32(),O=B._nextUint32(),C=[],P=0;P<a;P++)C.push({imageFlags:B._nextUint32(),rgbSliceByteOffset:B._nextUint32(),rgbSliceByteLength:B._nextUint32(),alphaSliceByteOffset:B._nextUint32(),alphaSliceByteLength:B._nextUint32()});var F=u+B._offset,G=F+R,k=G+E,N=k+T,V=new Uint8Array(t.buffer,t.byteOffset+F,R),M=new Uint8Array(t.buffer,t.byteOffset+G,E),H=new Uint8Array(t.buffer,t.byteOffset+k,T),z=new Uint8Array(t.buffer,t.byteOffset+N,O);return n.globalData={endpointCount:L,selectorCount:I,imageDescs:C,endpointsData:V,selectorsData:M,tablesData:H,extendedData:z},n},exports.write=function(t,n){void 0===n&&(n={}),n=c({},x,n);var i=new ArrayBuffer(0);if(t.globalData){var r=new ArrayBuffer(20+5*t.globalData.imageDescs.length*4),a=new DataView(r);a.setUint16(0,t.globalData.endpointCount,!0),a.setUint16(2,t.globalData.selectorCount,!0),a.setUint32(4,t.globalData.endpointsData.byteLength,!0),a.setUint32(8,t.globalData.selectorsData.byteLength,!0),a.setUint32(12,t.globalData.tablesData.byteLength,!0),a.setUint32(16,t.globalData.extendedData.byteLength,!0);for(var s=0;s<t.globalData.imageDescs.length;s++){var o=t.globalData.imageDescs[s];a.setUint32(20+5*s*4+0,o.imageFlags,!0),a.setUint32(20+5*s*4+4,o.rgbSliceByteOffset,!0),a.setUint32(20+5*s*4+8,o.rgbSliceByteLength,!0),a.setUint32(20+5*s*4+12,o.alphaSliceByteOffset,!0),a.setUint32(20+5*s*4+16,o.alphaSliceByteLength,!0)}i=y([r,t.globalData.endpointsData,t.globalData.selectorsData,t.globalData.tablesData,t.globalData.extendedData])}var U=[],h=t.keyValue;for(var u in n.keepWriter||(h=c({},t.keyValue,{KTXwriter:"KTX-Parse v0.0.3"})),h){var _=h[u],g=p(u),b="string"==typeof _?p(_):_,d=g.byteLength+1+b.byteLength+1,v=d%4?4-d%4:0;U.push(y([new Uint32Array([d]),g,l,b,l,new Uint8Array(v).fill(0)]))}var D=y(U),m=new ArrayBuffer(44),A=new DataView(m);if(1!==t.dataFormatDescriptor.length||t.dataFormatDescriptor[0].descriptorType!==e.BASICFORMAT)throw new Error("Only BASICFORMAT Data Format Descriptor output supported.");var w=t.dataFormatDescriptor[0];A.setUint32(0,44,!0),A.setUint16(4,w.vendorId,!0),A.setUint16(6,w.descriptorType,!0),A.setUint16(8,w.versionNumber,!0),A.setUint16(10,w.descriptorBlockSize,!0),A.setUint8(12,w.colorModel),A.setUint8(13,w.colorPrimaries),A.setUint8(14,w.transferFunction),A.setUint8(15,w.flags),A.setUint8(16,w.texelBlockDimension.x-1),A.setUint8(17,w.texelBlockDimension.y-1),A.setUint8(18,w.texelBlockDimension.z-1),A.setUint8(19,w.texelBlockDimension.w-1);for(var S=0;S<8;S++)A.setUint8(20+S,w.bytesPlane[S]);for(var B=0;B<w.samples.length;B++){var L=w.samples[B],I=28+16*B;A.setUint16(I+0,L.bitOffset,!0),A.setUint8(I+2,L.bitLength),A.setUint8(I+3,L.channelID),A.setUint8(I+4,L.samplePosition[0]),A.setUint8(I+5,L.samplePosition[1]),A.setUint8(I+6,L.samplePosition[2]),A.setUint8(I+7,L.samplePosition[3]),A.setUint32(I+8,L.sampleLower,!0),A.setUint32(I+12,L.sampleUpper,!0)}var R=f.length+68+3*t.levels.length*8,E=R+m.byteLength,T=E+D.byteLength;T%8&&(T+=8-T%8);for(var O=[],C=new DataView(new ArrayBuffer(3*t.levels.length*8)),P=T+i.byteLength,F=0;F<t.levels.length;F++){var G=t.levels[F];O.push(G.levelData),C.setBigUint64(24*F+0,BigInt(P),!0),C.setBigUint64(24*F+8,BigInt(G.levelData.byteLength),!0),C.setBigUint64(24*F+16,BigInt(G.uncompressedByteLength),!0),P+=G.levelData.byteLength}var k=new ArrayBuffer(68),N=new DataView(k);return N.setUint32(0,t.vkFormat,!0),N.setUint32(4,t.typeSize,!0),N.setUint32(8,t.pixelWidth,!0),N.setUint32(12,t.pixelHeight,!0),N.setUint32(16,t.pixelDepth,!0),N.setUint32(20,t.layerCount,!0),N.setUint32(24,t.faceCount,!0),N.setUint32(28,t.levels.length,!0),N.setUint32(32,t.supercompressionScheme,!0),N.setUint32(36,R,!0),N.setUint32(40,m.byteLength,!0),N.setUint32(44,E,!0),N.setUint32(48,D.byteLength,!0),N.setBigUint64(52,BigInt(T),!0),N.setBigUint64(60,BigInt(i.byteLength),!0),new Uint8Array(y([new Uint8Array(f).buffer,k,C.buffer,m,D,new ArrayBuffer(T-(E+D.byteLength)),i].concat(O)))}; | ||
//# sourceMappingURL=ktx-parse.js.map |
@@ -1,2 +0,2 @@ | ||
var t;!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(t||(t={}));class e{constructor(){this.vkFormat=0,this.typeSize=-1,this.pixelWidth=-1,this.pixelHeight=-1,this.pixelDepth=-1,this.layerCount=-1,this.faceCount=-1,this.levelCount=-1,this.supercompressionScheme=t.NONE,this.levelIndex=[],this.dataFormatDescriptor=[],this.keyValue=[],this.globalData=null}}class n{constructor(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_skip(t){return this._offset+=t,this}}function i(t){var i=new Uint8Array(t,0,12);if(171!==i[0]||75!==i[1]||84!==i[2]||88!==i[3]||32!==i[4]||50!==i[5]||48!==i[6]||187!==i[7]||13!==i[8]||10!==i[9]||26!==i[10]||10!==i[11])throw new Error("Missing KTX 2.0 identifier.");const s=new e,r=17*Uint32Array.BYTES_PER_ELEMENT,_=new n(t,12,r,!0);s.vkFormat=_._nextUint32(),s.typeSize=_._nextUint32(),s.pixelWidth=_._nextUint32(),s.pixelHeight=_._nextUint32(),s.pixelDepth=_._nextUint32(),s.layerCount=_._nextUint32(),s.faceCount=_._nextUint32(),s.levelCount=_._nextUint32(),s.supercompressionScheme=_._nextUint32();const a=_._nextUint32(),o=_._nextUint32(),l=(_._nextUint32(),_._nextUint32(),_._nextUint64()),x=_._nextUint64(),U=new n(t,12+r,3*s.levelCount*8,!0);for(let e=0;e<s.levelCount;e++)s.levelIndex.push({data:new Uint8Array(t,U._nextUint64(),U._nextUint64()),uncompressedByteLength:U._nextUint64()});const h=new n(t,a,o,!0),f={vendorId:h._skip(4)._nextUint16(),versionNumber:h._skip(2)._nextUint16(),descriptorBlockSize:h._nextUint16(),colorModel:h._nextUint8(),colorPrimaries:h._nextUint8(),transferFunction:h._nextUint8(),flags:h._nextUint8(),texelBlockDimension:{x:h._nextUint8()+1,y:h._nextUint8()+1,z:h._nextUint8()+1,w:h._nextUint8()+1},bytesPlane0:h._nextUint8(),numSamples:0,samples:[]};f.numSamples=(f.descriptorBlockSize/4-6)/4,h._skip(7);for(let t=0;t<f.numSamples;t++)f.samples[t]={channelID:h._skip(3)._nextUint8()},h._skip(12);if(x<=0)return s;const c=new n(t,l,x,!0),p=c._nextUint16(),u=c._nextUint16(),d=c._nextUint32(),y=c._nextUint32(),g=c._nextUint32(),w=c._nextUint32(),m=[];for(let t=0;t<s.levelCount;t++)m.push({imageFlags:c._nextUint32(),rgbSliceByteOffset:c._nextUint32(),rgbSliceByteLength:c._nextUint32(),alphaSliceByteOffset:c._nextUint32(),alphaSliceByteLength:c._nextUint32()});const S=l+c._offset,B=S+d,D=B+y,E=D+g,v=new Uint8Array(t,S,d),k=new Uint8Array(t,B,y),L=new Uint8Array(t,D,g),C=new Uint8Array(t,E,w);return s.globalData={endpointCount:p,selectorCount:u,endpointsByteLength:d,selectorsByteLength:y,tablesByteLength:g,extendedByteLength:w,imageDescs:m,endpointsData:v,selectorsData:k,tablesData:L,extendedData:C},s}function s(t){return new Uint8Array}export{e as Container,i as read,s as write}; | ||
const t=new Uint8Array([0]),e=[171,75,84,88,32,50,48,187,13,10,26,10];var n,i,s,a,r,o,l,f;!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(n||(n={})),function(t){t[t.BASICFORMAT=0]="BASICFORMAT"}(i||(i={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.ETC1S=163]="ETC1S",t[t.UASTC=166]="UASTC"}(s||(s={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.SRGB=1]="SRGB"}(a||(a={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.LINEAR=1]="LINEAR",t[t.SRGB=2]="SRGB",t[t.ITU=3]="ITU",t[t.NTSC=4]="NTSC",t[t.SLOG=5]="SLOG",t[t.SLOG2=6]="SLOG2"}(r||(r={})),function(t){t[t.ALPHA_STRAIGHT=0]="ALPHA_STRAIGHT",t[t.ALPHA_PREMULTIPLIED=1]="ALPHA_PREMULTIPLIED"}(o||(o={})),function(t){t[t.RGB=0]="RGB",t[t.RRR=3]="RRR",t[t.GGG=4]="GGG",t[t.AAA=15]="AAA"}(l||(l={})),function(t){t[t.RGB=0]="RGB",t[t.RGBA=3]="RGBA",t[t.RRR=4]="RRR",t[t.RRRG=5]="RRRG"}(f||(f={}));class U{constructor(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=n.NONE,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:i.BASICFORMAT,versionNumber:2,descriptorBlockSize:40,colorModel:s.UNSPECIFIED,colorPrimaries:a.SRGB,transferFunction:a.SRGB,flags:o.ALPHA_STRAIGHT,texelBlockDimension:{x:4,y:4,z:1,w:1},bytesPlane:[],samples:[]}],this.keyValue={},this.globalData=null}}class c{constructor(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_skip(t){return this._offset+=t,this}_scan(t,e=0){const n=this._offset;let i=0;for(;this._dataView.getUint8(this._offset)!==e&&i<t;)i++,this._offset++;return i<t&&this._offset++,new Uint8Array(this._dataView.buffer,this._dataView.byteOffset+n,i)}}function h(t){return"undefined"!=typeof TextEncoder?(new TextEncoder).encode(t):Buffer.from(t)}function _(t){return"undefined"!=typeof TextDecoder?(new TextDecoder).decode(t):Buffer.from(t).toString("utf8")}function g(t){let e=0;for(const n of t)e+=n.byteLength;const n=new Uint8Array(e);let i=0;for(const e of t)n.set(new Uint8Array(e),i),i+=e.byteLength;return n}function p(t){const n=new Uint8Array(t.buffer,t.byteOffset,e.length);if(n[0]!==e[0]||n[1]!==e[1]||n[2]!==e[2]||n[3]!==e[3]||n[4]!==e[4]||n[5]!==e[5]||n[6]!==e[6]||n[7]!==e[7]||n[8]!==e[8]||n[9]!==e[9]||n[10]!==e[10]||n[11]!==e[11])throw new Error("Missing KTX 2.0 identifier.");const i=new U,s=17*Uint32Array.BYTES_PER_ELEMENT,a=new c(t,e.length,s,!0);i.vkFormat=a._nextUint32(),i.typeSize=a._nextUint32(),i.pixelWidth=a._nextUint32(),i.pixelHeight=a._nextUint32(),i.pixelDepth=a._nextUint32(),i.layerCount=a._nextUint32(),i.faceCount=a._nextUint32();const r=a._nextUint32();i.supercompressionScheme=a._nextUint32();const o=a._nextUint32(),l=a._nextUint32(),f=a._nextUint32(),h=a._nextUint32(),g=a._nextUint64(),p=a._nextUint64(),x=new c(t,e.length+s,3*r*8,!0);for(let e=0;e<r;e++)i.levels.push({levelData:new Uint8Array(t.buffer,t.byteOffset+x._nextUint64(),x._nextUint64()),uncompressedByteLength:x._nextUint64()});const u=new c(t,o,l,!0),y={vendorId:u._skip(4)._nextUint16(),descriptorType:u._nextUint16(),versionNumber:u._nextUint16(),descriptorBlockSize:u._nextUint16(),colorModel:u._nextUint8(),colorPrimaries:u._nextUint8(),transferFunction:u._nextUint8(),flags:u._nextUint8(),texelBlockDimension:{x:u._nextUint8()+1,y:u._nextUint8()+1,z:u._nextUint8()+1,w:u._nextUint8()+1},bytesPlane:[u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8()],samples:[]},D=(y.descriptorBlockSize/4-6)/4;for(let t=0;t<D;t++)y.samples[t]={bitOffset:u._nextUint16(),bitLength:u._nextUint8(),channelID:u._nextUint8(),samplePosition:[u._nextUint8(),u._nextUint8(),u._nextUint8(),u._nextUint8()],sampleLower:u._nextUint32(),sampleUpper:u._nextUint32()};i.dataFormatDescriptor.length=0,i.dataFormatDescriptor.push(y);const b=new c(t,f,h,!0);for(;b._offset<h;){const t=b._nextUint32(),e=b._scan(t),n=_(e),s=b._scan(t-e.byteLength);i.keyValue[n]=n.match(/^ktx/i)?_(s):s,t%4&&b._skip(4-t%4)}if(p<=0)return i;const d=new c(t,g,p,!0),B=d._nextUint16(),w=d._nextUint16(),A=d._nextUint32(),S=d._nextUint32(),m=d._nextUint32(),L=d._nextUint32(),I=[];for(let t=0;t<r;t++)I.push({imageFlags:d._nextUint32(),rgbSliceByteOffset:d._nextUint32(),rgbSliceByteLength:d._nextUint32(),alphaSliceByteOffset:d._nextUint32(),alphaSliceByteLength:d._nextUint32()});const R=g+d._offset,E=R+A,T=E+S,O=T+m,P=new Uint8Array(t.buffer,t.byteOffset+R,A),C=new Uint8Array(t.buffer,t.byteOffset+E,S),F=new Uint8Array(t.buffer,t.byteOffset+T,m),G=new Uint8Array(t.buffer,t.byteOffset+O,L);return i.globalData={endpointCount:B,selectorCount:w,imageDescs:I,endpointsData:P,selectorsData:C,tablesData:F,extendedData:G},i}function x(){return(x=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t}).apply(this,arguments)}const u={keepWriter:!1};function y(n,s={}){s=x({},u,s);let a=new ArrayBuffer(0);if(n.globalData){const t=new ArrayBuffer(20+5*n.globalData.imageDescs.length*4),e=new DataView(t);e.setUint16(0,n.globalData.endpointCount,!0),e.setUint16(2,n.globalData.selectorCount,!0),e.setUint32(4,n.globalData.endpointsData.byteLength,!0),e.setUint32(8,n.globalData.selectorsData.byteLength,!0),e.setUint32(12,n.globalData.tablesData.byteLength,!0),e.setUint32(16,n.globalData.extendedData.byteLength,!0);for(let t=0;t<n.globalData.imageDescs.length;t++){const i=n.globalData.imageDescs[t];e.setUint32(20+5*t*4+0,i.imageFlags,!0),e.setUint32(20+5*t*4+4,i.rgbSliceByteOffset,!0),e.setUint32(20+5*t*4+8,i.rgbSliceByteLength,!0),e.setUint32(20+5*t*4+12,i.alphaSliceByteOffset,!0),e.setUint32(20+5*t*4+16,i.alphaSliceByteLength,!0)}a=g([t,n.globalData.endpointsData,n.globalData.selectorsData,n.globalData.tablesData,n.globalData.extendedData])}const r=[];let o=n.keyValue;s.keepWriter||(o=x({},n.keyValue,{KTXwriter:"KTX-Parse v0.0.3"}));for(const e in o){const n=o[e],i=h(e),s="string"==typeof n?h(n):n,a=i.byteLength+1+s.byteLength+1,l=a%4?4-a%4:0;r.push(g([new Uint32Array([a]),i,t,s,t,new Uint8Array(l).fill(0)]))}const l=g(r),f=new ArrayBuffer(44),U=new DataView(f);if(1!==n.dataFormatDescriptor.length||n.dataFormatDescriptor[0].descriptorType!==i.BASICFORMAT)throw new Error("Only BASICFORMAT Data Format Descriptor output supported.");const c=n.dataFormatDescriptor[0];U.setUint32(0,44,!0),U.setUint16(4,c.vendorId,!0),U.setUint16(6,c.descriptorType,!0),U.setUint16(8,c.versionNumber,!0),U.setUint16(10,c.descriptorBlockSize,!0),U.setUint8(12,c.colorModel),U.setUint8(13,c.colorPrimaries),U.setUint8(14,c.transferFunction),U.setUint8(15,c.flags),U.setUint8(16,c.texelBlockDimension.x-1),U.setUint8(17,c.texelBlockDimension.y-1),U.setUint8(18,c.texelBlockDimension.z-1),U.setUint8(19,c.texelBlockDimension.w-1);for(let t=0;t<8;t++)U.setUint8(20+t,c.bytesPlane[t]);for(let t=0;t<c.samples.length;t++){const e=c.samples[t],n=28+16*t;U.setUint16(n+0,e.bitOffset,!0),U.setUint8(n+2,e.bitLength),U.setUint8(n+3,e.channelID),U.setUint8(n+4,e.samplePosition[0]),U.setUint8(n+5,e.samplePosition[1]),U.setUint8(n+6,e.samplePosition[2]),U.setUint8(n+7,e.samplePosition[3]),U.setUint32(n+8,e.sampleLower,!0),U.setUint32(n+12,e.sampleUpper,!0)}const _=e.length+68+3*n.levels.length*8,p=_+f.byteLength;let y=p+l.byteLength;y%8&&(y+=8-y%8);const D=[],b=new DataView(new ArrayBuffer(3*n.levels.length*8));let d=y+a.byteLength;for(let t=0;t<n.levels.length;t++){const e=n.levels[t];D.push(e.levelData),b.setBigUint64(24*t+0,BigInt(d),!0),b.setBigUint64(24*t+8,BigInt(e.levelData.byteLength),!0),b.setBigUint64(24*t+16,BigInt(e.uncompressedByteLength),!0),d+=e.levelData.byteLength}const B=new ArrayBuffer(68),w=new DataView(B);return w.setUint32(0,n.vkFormat,!0),w.setUint32(4,n.typeSize,!0),w.setUint32(8,n.pixelWidth,!0),w.setUint32(12,n.pixelHeight,!0),w.setUint32(16,n.pixelDepth,!0),w.setUint32(20,n.layerCount,!0),w.setUint32(24,n.faceCount,!0),w.setUint32(28,n.levels.length,!0),w.setUint32(32,n.supercompressionScheme,!0),w.setUint32(36,_,!0),w.setUint32(40,f.byteLength,!0),w.setUint32(44,p,!0),w.setUint32(48,l.byteLength,!0),w.setBigUint64(52,BigInt(y),!0),w.setBigUint64(60,BigInt(a.byteLength),!0),new Uint8Array(g([new Uint8Array(e).buffer,B,b.buffer,f,l,new ArrayBuffer(y-(p+l.byteLength)),a,...D]))}export{U as KTX2Container,p as read,y as write}; | ||
//# sourceMappingURL=ktx-parse.modern.js.map |
@@ -0,1 +1,3 @@ | ||
export declare const KTX2_ID: number[]; | ||
export declare const KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT = 0; | ||
export interface KTX2Level { | ||
@@ -36,6 +38,12 @@ data: Uint8Array; | ||
export interface KTX2DataFormatDescriptorSample { | ||
bitOffset: number; | ||
bitLength: number; | ||
channelID: number; | ||
samplePosition: number[]; | ||
sampleLower: number; | ||
sampleUpper: number; | ||
} | ||
export interface KTX2DataFormatDescriptor { | ||
vendorId: number; | ||
descriptorType: number; | ||
versionNumber: number; | ||
@@ -48,7 +56,8 @@ descriptorBlockSize: number; | ||
texelBlockDimension: KTX2DataFormatDescriptorTexelBlockDimensions; | ||
bytesPlane0: number; | ||
numSamples: number; | ||
bytesPlane: number[]; | ||
samples: KTX2DataFormatDescriptorSample[]; | ||
} | ||
export interface KTX2KeyValue { | ||
key: string; | ||
value: string | Uint8Array; | ||
} | ||
@@ -65,6 +74,2 @@ interface KTX2GlobalDataImageDescription { | ||
selectorCount: number; | ||
endpointsByteLength: number; | ||
selectorsByteLength: number; | ||
tablesByteLength: number; | ||
extendedByteLength: number; | ||
imageDescs: KTX2GlobalDataImageDescription[]; | ||
@@ -71,0 +76,0 @@ endpointsData: Uint8Array; |
@@ -1,2 +0,9 @@ | ||
import { Container } from './container'; | ||
export declare function read(data: Uint8Array): Container; | ||
import { KTX2Container } from './container'; | ||
/** | ||
* Parses a KTX 2.0 file, returning an unpacked {@link KTX2Container} instance with all associated | ||
* data. The container's mip levels and other binary data are pointers into the original file, not | ||
* copies, so the original file should not be overwritten after reading. | ||
* | ||
* @param data Bytes of KTX 2.0 file, as Uint8Array or Buffer. | ||
*/ | ||
export declare function read(data: Uint8Array): KTX2Container; |
@@ -1,2 +0,18 @@ | ||
import { Container } from './container'; | ||
export declare function write(container: Container): Uint8Array; | ||
import { KTX2Container } from './container'; | ||
interface WriteOptions { | ||
keepWriter?: boolean; | ||
} | ||
/** | ||
* Serializes a {@link KTX2Container} instance to a KTX 2.0 file. Mip levels and other binary data | ||
* are copied into the resulting Uint8Array, so the original container can safely be edited or | ||
* destroyed after it is serialized. | ||
* | ||
* Options: | ||
* - keepWriter: If true, 'KTXWriter' key/value field is written as provided by the container. | ||
* Otherwise, a string for the current ktx-parse version is generated. Default: false. | ||
* | ||
* @param container | ||
* @param options | ||
*/ | ||
export declare function write(container: KTX2Container, options?: WriteOptions): Uint8Array; | ||
export {}; |
{ | ||
"name": "ktx-parse", | ||
"version": "0.0.1", | ||
"version": "0.0.3", | ||
"description": "KTX 2.0 (.ktx2) parser and serializer.", | ||
@@ -9,3 +9,3 @@ "source": "src/index.ts", | ||
"types": "dist/index.d.ts", | ||
"repository": "https://github.com/github:donmccurdy/ktx-parse", | ||
"repository": "github:donmccurdy/ktx-parse", | ||
"author": "Don McCurdy <dm@donmccurdy.com>", | ||
@@ -19,16 +19,22 @@ "license": "MIT", | ||
"coverage": "nyc --reporter=lcov --reporter=text ts-node node_modules/tape/bin/tape test/test.ts", | ||
"coverage:report": "nyc report --reporter=text-lcov | coveralls", | ||
"docs": "typedoc --plugin typedoc-plugin-markdown --out ./docs --hideProjectName --hideBreadcrumbs && ts-node docs.ts && rm -rf ./docs", | ||
"preversion": "yarn dist && yarn test", | ||
"postpublish": "git push && git push --tags" | ||
"version": "yarn dist && yarn docs && git add -u", | ||
"postversion": "git push && git push --tags && npm publish && yarn coverage:report" | ||
}, | ||
"devDependencies": { | ||
"@types/tape": "^4.13.0", | ||
"@typescript-eslint/eslint-plugin": "^4.10.0", | ||
"coveralls": "^3.1.0", | ||
"eslint": "^7.15.0", | ||
"microbundle": "^0.12.4", | ||
"nyc": "^15.1.0", | ||
"source-map-support": "^0.5.19", | ||
"tap-spec": "^5.0.0", | ||
"tape": "^5.0.1", | ||
"ts-node": "^9.1.1", | ||
"typescript": "^4.1.3", | ||
"@typescript-eslint/eslint-plugin": "^4.10.0", | ||
"eslint": "^7.15.0", | ||
"source-map-support": "^0.5.19" | ||
"typedoc": "^0.19.2", | ||
"typedoc-plugin-markdown": "^3.1.1", | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -35,0 +41,0 @@ "files": [ |
241
README.md
# ktx-parse | ||
![Status](https://img.shields.io/badge/status-incomplete-orange.svg) | ||
[![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/donmccurdy/KTX-Parse/blob/master/LICENSE) | ||
[![Latest NPM release](https://img.shields.io/npm/v/ktx-parse.svg)](https://www.npmjs.com/package/ktx-parse) | ||
[![Minzipped size](https://badgen.net/bundlephobia/minzip/ktx-parse)](https://bundlephobia.com/result?p=ktx-parse) | ||
[![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/donmccurdy/KTX-Parse/blob/main/LICENSE) | ||
[![CI](https://github.com/donmccurdy/KTX-parse/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/donmccurdy/KTX-parse/actions?query=workflow%3ACI) | ||
[![Coverage Status](https://coveralls.io/repos/github/donmccurdy/KTX-Parse/badge.svg?branch=main)](https://coveralls.io/github/donmccurdy/KTX-Parse?branch=main) | ||
@@ -13,2 +16,4 @@ *KTX 2.0 (.ktx2) parser and serializer.* | ||
Install: | ||
``` | ||
@@ -18,27 +23,231 @@ npm install --save ktx-parse | ||
Import: | ||
```js | ||
const fs = require('fs'); | ||
// ES Modules: | ||
import { read, write } from 'ktx-parse'; | ||
// CommonJS: | ||
const { read, write } = require('ktx-parse'); | ||
``` | ||
// Read data. | ||
const inData = fs.readFileSync('./input.ktx2'); // (a) Node.js | ||
const inData = await fetch('./input.ktx2') // (b) Web | ||
.then((r) => r.arrayBuffer()) | ||
.then((buffer) => new Uint8Array(buffer)); | ||
Usage: | ||
// Parse. | ||
const container = read(inData); | ||
```js | ||
// Parse texture container from file: | ||
const texture = read(data /* ← Uint8Array or Buffer */); | ||
// Serialize. | ||
const outData = write(container); // → Uint8Array | ||
// Write texture container to file: | ||
const data = write(texture); // → Uint8Array | ||
``` | ||
## API | ||
See [API documentation](#api-documentation) for more details. | ||
TODO | ||
## Encoding / Decoding | ||
This library reads/writes KTX 2.0 containers, and provides access to the compressed texture data within the container. To decompress that data, or to compress existing texture data into GPU texture formats used by KTX 2.0, you'll need to use additional libraries, described below. | ||
KTX-Parse reads/writes KTX 2.0 containers, and provides access to the compressed texture data within the container. To decompress that texture data, or to compress existing texture data into GPU texture formats used by KTX 2.0, you'll need to use additional libraries such as encoders or transcoders. | ||
> **TODO:** Describe options for compressing, transcoding, and decompressing KTX 2.0 payloads using Basis Universal. | ||
**Encoding:** | ||
Encoding GPU textures is a slow process, and should be completed at development/authoring time so that the compressed texture can be transmitted to the viewing device. GPU textures require much less GPU memory than image formats like PNG or JPEG, and can be uploaded to the GPU quickly with less impact on framerate. GPU textures can also have smaller filesizes in many, but not all, cases. See the [Basis documentation](https://github.com/BinomialLLC/basis_universal/) for details on this process. | ||
- [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal/) provides C++ and WebAssembly encoders, reading PNG files or raw pixel data, and outputing compressed texture data (and supercompression global data, if applicable). Use of these encoders is somewhat advanced, and the simpler `basisu` CLI tool does not provide the data necessary for writing a KTX 2.0 file. | ||
- [KhronosGroup/KTX-Software](https://github.com/KhronosGroup/KTX-Software) provides CLI, C++, and WebAssembly encoders for reading PNG or JPEG textures and outputing a complete KTX 2.0 file, which `ktx-parse` can then read or edit. While probably easier than using the basis_universal encoders directly, the KTX-Software library is somewhat larger and has more dependencies. | ||
**Transcoding / Decoding:** | ||
Basis Universal texture formats (ETC1S and UASTC) cannot be directly read by a GPU, but are designed to be very efficiently rewritten into many of the specific GPU texture formats that different GPUs require. This process is called _transcoding_, and typically happens on the viewing device after a target output format (e.g. ETC1, ASTC, BC1, ...) is chosen. These transcoders can also fully _decode_ texture data to uncompressed RGBA formats, if raw pixel data is required. | ||
- [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal/) provides official C++ and WebAssembly transcoders, which support all Basis Universal input formats and can transcode to any output format (with appropriate compilation flags). With common settings, a transcoder will likely be > 200kb on web. | ||
- [KhronosGroup/Universal-Texture-Transcoders](https://github.com/KhronosGroup/Universal-Texture-Transcoders) provides very small, fast WebAssembly transcoders each supporting only a single output texture format. Each transcoder is roughly 10-20kb, and the viewing device can choose which transcoder to download, as appropriate. *Only UASTC texture formats currently supported.* | ||
The transcoders above cannot read KTX 2.0 files directly. Instead, unpack the KTX 2.0 files with `ktx-parse` first, then transcode the mip levels using a low-level transcoder. | ||
## API Documentation | ||
### Functions | ||
#### read | ||
<!-- begin:read --> | ||
▸ **read**(`data`: Uint8Array): `KTX2Container` | ||
*Defined in [src/read.ts:13](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/read.ts#L13)* | ||
Parses a KTX 2.0 file, returning an unpacked `KTX2Container` instance with all associated | ||
data. The container's mip levels and other binary data are pointers into the original file, not | ||
copies, so the original file should not be overwritten after reading. | ||
###### Parameters: | ||
Name | Type | Description | | ||
------ | ------ | ------ | | ||
`data` | Uint8Array | Bytes of KTX 2.0 file, as Uint8Array or Buffer. | | ||
**Returns:** `KTX2Container` | ||
<!-- end:read --> | ||
#### write | ||
<!-- begin:write --> | ||
▸ **write**(`container`: `KTX2Container`, `options?`: `WriteOptions`): Uint8Array | ||
*Defined in [src/write.ts:20](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/write.ts#L20)* | ||
Serializes a `KTX2Container` instance to a KTX 2.0 file. Mip levels and other binary data | ||
are copied into the resulting Uint8Array, so the original container can safely be edited or | ||
destroyed after it is serialized. | ||
Options: | ||
- keepWriter: If true, 'KTXWriter' key/value field is written as provided by the container. | ||
Otherwise, a string for the current ktx-parse version is generated. Default: false. | ||
###### Parameters: | ||
Name | Type | Default value | Description | | ||
------ | ------ | ------ | ------ | | ||
`container` | `KTX2Container` | - | | | ||
`options` | `WriteOptions` | {} | | | ||
**Returns:** Uint8Array | ||
<!-- end:write --> | ||
<!-- begin:KTX2Container --> | ||
### Class: KTX2Container | ||
Represents an unpacked KTX 2.0 texture container. Data for individual mip levels are stored in | ||
the `.levels` array, typically compressed in Basis Universal formats. Additional properties | ||
provide metadata required to process, transcode, and upload these textures. | ||
#### Properties | ||
##### dataFormatDescriptor | ||
• **dataFormatDescriptor**: `KTX2DataFormatDescriptorBasicFormat`[] = [{ vendorId: KHR\_DF\_VENDORID\_KHRONOS, descriptorType: KTX2DataFormatType.BASICFORMAT, versionNumber: KHR\_DF\_VERSION, descriptorBlockSize: KHR\_DF\_BLOCKSIZE, colorModel: KTX2DataFormatModel.UNSPECIFIED, colorPrimaries: KTX2DataFormatPrimaries.SRGB, transferFunction: KTX2DataFormatPrimaries.SRGB, flags: KTX2DataFormatFlags.ALPHA\_STRAIGHT, texelBlockDimension: {x: 4, y: 4, z: 1, w: 1}, bytesPlane: [], samples: [], }] | ||
*Defined in [src/container.ts:47](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L47)* | ||
Data Format Descriptor. | ||
___ | ||
##### faceCount | ||
• **faceCount**: number = 1 | ||
*Defined in [src/container.ts:38](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L38)* | ||
Number of cubemap faces. For cubemaps and cubemap arrays, `faceCount` must be 6. For all | ||
other textures, `faceCount` must be 1. Cubemap faces are stored in +X, -X, +Y, -Y, +Z, -Z | ||
order. | ||
___ | ||
##### globalData | ||
• **globalData**: `KTX2GlobalDataBasisLZ` \| null = null | ||
*Defined in [src/container.ts:65](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L65)* | ||
Supercompression Global Data. | ||
___ | ||
##### keyValue | ||
• **keyValue**: { [key:string]: string \| Uint8Array; } | ||
*Defined in [src/container.ts:62](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L62)* | ||
Key/Value Data. | ||
___ | ||
##### layerCount | ||
• **layerCount**: number = 0 | ||
*Defined in [src/container.ts:31](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L31)* | ||
Number of array elements (array textures only). | ||
___ | ||
##### levels | ||
• **levels**: `KTX2Level`[] = [] | ||
*Defined in [src/container.ts:44](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L44)* | ||
Mip levels, ordered largest (original) to smallest (~1px). | ||
___ | ||
##### pixelDepth | ||
• **pixelDepth**: number = 0 | ||
*Defined in [src/container.ts:28](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L28)* | ||
Depth of the texture image for level 0, in pixels (3D textures only). | ||
___ | ||
##### pixelHeight | ||
• **pixelHeight**: number = 0 | ||
*Defined in [src/container.ts:25](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L25)* | ||
Height of the texture image for level 0, in pixels. | ||
___ | ||
##### pixelWidth | ||
• **pixelWidth**: number = 0 | ||
*Defined in [src/container.ts:22](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L22)* | ||
Width of the texture image for level 0, in pixels. | ||
___ | ||
##### supercompressionScheme | ||
• **supercompressionScheme**: `KTX2SupercompressionScheme` = KTX2SupercompressionScheme.NONE | ||
*Defined in [src/container.ts:41](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L41)* | ||
Indicates which supercompression scheme has been applied to mip level images, if any. | ||
___ | ||
##### typeSize | ||
• **typeSize**: number = 1 | ||
*Defined in [src/container.ts:19](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L19)* | ||
Size of the data type in bytes used to upload the data to a graphics API. When `vkFormat` is | ||
VK_FORMAT_UNDEFINED, `typeSize` must be 1. | ||
___ | ||
##### vkFormat | ||
• **vkFormat**: number = VK\_FORMAT\_UNDEFINED | ||
*Defined in [src/container.ts:13](https://github.com/donmccurdy/KTX-Parse/blob/a6f24b4/src/container.ts#L13)* | ||
Specifies the image format using Vulkan VkFormat enum values. When using Basis Universal | ||
texture formats, `vkFormat` must be VK_FORMAT_UNDEFINED. | ||
<!-- end:KTX2Container --> |
@@ -33,2 +33,3 @@ export class BufferReader { | ||
const right = this._dataView.getUint32(this._offset + 4, this._littleEndian); | ||
// TODO(cleanup): Just test this... | ||
// const value = this._littleEndian ? left + (2 ** 32 * right) : (2 ** 32 * left) + right; | ||
@@ -44,2 +45,19 @@ const value = left + (2 ** 32 * right); | ||
} | ||
_scan(maxByteLength: number, term: number = 0x00): Uint8Array { | ||
const byteOffset = this._offset; | ||
let byteLength = 0; | ||
while (this._dataView.getUint8(this._offset) !== term && byteLength < maxByteLength) { | ||
byteLength++; | ||
this._offset++; | ||
} | ||
if (byteLength < maxByteLength) this._offset++; | ||
return new Uint8Array( | ||
this._dataView.buffer, | ||
this._dataView.byteOffset + byteOffset, | ||
byteLength | ||
); | ||
} | ||
} |
@@ -1,26 +0,137 @@ | ||
import { KTX2DataFormatDescriptor, KTX2GlobalData, KTX2KeyValue, KTX2Level, KTX2SupercompressionScheme } from './ktx2-schema'; | ||
import { KHR_DF_BLOCKSIZE, KHR_DF_VENDORID_KHRONOS, KHR_DF_VERSION, KTX2DataFormatFlags, KTX2DataFormatModel, KTX2DataFormatPrimaries, KTX2DataFormatType, KTX2SupercompressionScheme, VK_FORMAT_UNDEFINED } from './constants'; | ||
export class Container { | ||
// Header. | ||
public vkFormat: number = 0x00; | ||
public typeSize: number = -1; | ||
public pixelWidth: number = -1; | ||
public pixelHeight: number = -1; | ||
public pixelDepth: number = -1; | ||
public layerCount: number = -1; | ||
public faceCount: number = -1; | ||
public levelCount: number = -1; | ||
/** | ||
* Represents an unpacked KTX 2.0 texture container. Data for individual mip levels are stored in | ||
* the `.levels` array, typically compressed in Basis Universal formats. Additional properties | ||
* provide metadata required to process, transcode, and upload these textures. | ||
*/ | ||
export class KTX2Container { | ||
/** | ||
* Specifies the image format using Vulkan VkFormat enum values. When using Basis Universal | ||
* texture formats, `vkFormat` must be VK_FORMAT_UNDEFINED. | ||
*/ | ||
public vkFormat = VK_FORMAT_UNDEFINED; | ||
/** | ||
* Size of the data type in bytes used to upload the data to a graphics API. When `vkFormat` is | ||
* VK_FORMAT_UNDEFINED, `typeSize` must be 1. | ||
*/ | ||
public typeSize: number = 1; | ||
/** Width of the texture image for level 0, in pixels. */ | ||
public pixelWidth: number = 0; | ||
/** Height of the texture image for level 0, in pixels. */ | ||
public pixelHeight: number = 0; | ||
/** Depth of the texture image for level 0, in pixels (3D textures only). */ | ||
public pixelDepth: number = 0; | ||
/** Number of array elements (array textures only). */ | ||
public layerCount: number = 0; | ||
/** | ||
* Number of cubemap faces. For cubemaps and cubemap arrays, `faceCount` must be 6. For all | ||
* other textures, `faceCount` must be 1. Cubemap faces are stored in +X, -X, +Y, -Y, +Z, -Z | ||
* order. | ||
*/ | ||
public faceCount: number = 1; | ||
/** Indicates which supercompression scheme has been applied to mip level images, if any. */ | ||
public supercompressionScheme = KTX2SupercompressionScheme.NONE; | ||
/** Mip Levels. */ | ||
public levelIndex: KTX2Level[] = []; | ||
/** Mip levels, ordered largest (original) to smallest (~1px). */ | ||
public levels: KTX2Level[] = []; | ||
/** Data Format Descriptor. */ | ||
public dataFormatDescriptor: KTX2DataFormatDescriptor[] = []; | ||
public dataFormatDescriptor: KTX2DataFormatDescriptorBasicFormat[] = [{ | ||
vendorId: KHR_DF_VENDORID_KHRONOS, | ||
descriptorType: KTX2DataFormatType.BASICFORMAT, | ||
versionNumber: KHR_DF_VERSION, | ||
descriptorBlockSize: KHR_DF_BLOCKSIZE, | ||
colorModel: KTX2DataFormatModel.UNSPECIFIED, | ||
colorPrimaries: KTX2DataFormatPrimaries.SRGB, | ||
transferFunction: KTX2DataFormatPrimaries.SRGB, | ||
flags: KTX2DataFormatFlags.ALPHA_STRAIGHT, | ||
texelBlockDimension: {x: 4, y: 4, z: 1, w: 1}, | ||
bytesPlane: [], | ||
samples: [], | ||
}]; | ||
/** Key/Value Data. */ | ||
public keyValue: KTX2KeyValue[] = []; | ||
public keyValue: {[key: string]: string | Uint8Array} = {}; | ||
/** Supercompression Global Data. */ | ||
public globalData: KTX2GlobalData | null = null; | ||
public globalData: KTX2GlobalDataBasisLZ | null = null; | ||
} | ||
/////////////////////////////////////////////////// | ||
// Mip Levels. | ||
/////////////////////////////////////////////////// | ||
export interface KTX2Level { | ||
/** Compressed data of the mip level. */ | ||
levelData: Uint8Array; | ||
/** Uncompressed size of the mip level. */ | ||
uncompressedByteLength: number; | ||
}; | ||
/////////////////////////////////////////////////// | ||
// Data Format Descriptor (DFD). | ||
/////////////////////////////////////////////////// | ||
export interface KTX2DataFormatDescriptorBasicFormat { | ||
vendorId: number; | ||
descriptorType: number; | ||
versionNumber: number; | ||
descriptorBlockSize: number; | ||
colorModel: number; | ||
colorPrimaries: number; | ||
transferFunction: number; | ||
flags: number; | ||
texelBlockDimension: KTX2BasicFormatTexelBlockDimensions; | ||
bytesPlane: number[]; | ||
samples: KTX2BasicFormatSample[], | ||
}; | ||
export interface KTX2BasicFormatTexelBlockDimensions { | ||
x: number; | ||
y: number; | ||
z: number; | ||
w: number; | ||
}; | ||
export interface KTX2BasicFormatSample { | ||
bitOffset: number; | ||
bitLength: number; | ||
channelID: number; | ||
samplePosition: number[]; | ||
sampleLower: number; | ||
sampleUpper: number; | ||
}; | ||
/////////////////////////////////////////////////// | ||
// Supercompression Global Data. | ||
/////////////////////////////////////////////////// | ||
export interface KTX2GlobalDataBasisLZ { | ||
endpointCount: number; | ||
selectorCount: number; | ||
imageDescs: KTX2GlobalDataBasisLZImageDesc[]; | ||
endpointsData: Uint8Array; | ||
selectorsData: Uint8Array; | ||
tablesData: Uint8Array; | ||
extendedData: Uint8Array; | ||
}; | ||
interface KTX2GlobalDataBasisLZImageDesc { | ||
imageFlags: number; | ||
rgbSliceByteOffset: number; | ||
rgbSliceByteLength: number; | ||
alphaSliceByteOffset: number; | ||
alphaSliceByteLength: number; | ||
}; |
155
src/read.ts
import { BufferReader } from './buffer-reader'; | ||
import { Container } from './container'; | ||
import { KTX2DataFormatDescriptor } from './ktx2-schema'; | ||
import { KTX2_ID } from './constants'; | ||
import { KTX2Container, KTX2DataFormatDescriptorBasicFormat } from './container'; | ||
import { decodeText } from './util'; | ||
export function read(data: Uint8Array): Container { | ||
/** | ||
* Parses a KTX 2.0 file, returning an unpacked {@link KTX2Container} instance with all associated | ||
* data. The container's mip levels and other binary data are pointers into the original file, not | ||
* copies, so the original file should not be overwritten after reading. | ||
* | ||
* @param data Bytes of KTX 2.0 file, as Uint8Array or Buffer. | ||
*/ | ||
export function read(data: Uint8Array): KTX2Container { | ||
// Confirm this is a KTX 2.0 file, based on the identifier in the first 12 bytes. | ||
var idByteLength = 12; | ||
var id = new Uint8Array(data, 0, idByteLength); | ||
if (id[0] !== 0xAB || // '´' | ||
id[ 1 ] !== 0x4B || // 'K' | ||
id[ 2 ] !== 0x54 || // 'T' | ||
id[ 3 ] !== 0x58 || // 'X' | ||
id[ 4 ] !== 0x20 || // ' ' | ||
id[ 5 ] !== 0x32 || // '2' | ||
id[ 6 ] !== 0x30 || // '0' | ||
id[ 7 ] !== 0xBB || // 'ª' | ||
id[ 8 ] !== 0x0D || // '\r' | ||
id[ 9 ] !== 0x0A || // '\n' | ||
id[ 10 ] !== 0x1A || // '\x1A' | ||
id[ 11 ] !== 0x0A // '\n' | ||
/////////////////////////////////////////////////// | ||
// KTX 2.0 Identifier. | ||
/////////////////////////////////////////////////// | ||
const id = new Uint8Array(data.buffer, data.byteOffset, KTX2_ID.length); | ||
if (id[0] !== KTX2_ID[0] || // '´' | ||
id[1] !== KTX2_ID[1] || // 'K' | ||
id[2] !== KTX2_ID[2] || // 'T' | ||
id[3] !== KTX2_ID[3] || // 'X' | ||
id[4] !== KTX2_ID[4] || // ' ' | ||
id[5] !== KTX2_ID[5] || // '2' | ||
id[6] !== KTX2_ID[6] || // '0' | ||
id[7] !== KTX2_ID[7] || // 'ª' | ||
id[8] !== KTX2_ID[8] || // '\r' | ||
id[9] !== KTX2_ID[9] || // '\n' | ||
id[10] !== KTX2_ID[10] || // '\x1A' | ||
id[11] !== KTX2_ID[11] // '\n' | ||
) { | ||
throw new Error( 'Missing KTX 2.0 identifier.' ); | ||
throw new Error('Missing KTX 2.0 identifier.'); | ||
} | ||
const container = new Container(); | ||
const container = new KTX2Container(); | ||
@@ -33,3 +43,3 @@ /////////////////////////////////////////////////// | ||
const headerByteLength = 17 * Uint32Array.BYTES_PER_ELEMENT; | ||
const headerReader = new BufferReader( data, idByteLength, headerByteLength, true ); | ||
const headerReader = new BufferReader(data, KTX2_ID.length, headerByteLength, true); | ||
@@ -43,3 +53,5 @@ container.vkFormat = headerReader._nextUint32(); | ||
container.faceCount = headerReader._nextUint32(); | ||
container.levelCount = headerReader._nextUint32(); | ||
const levelCount = headerReader._nextUint32(); | ||
container.supercompressionScheme = headerReader._nextUint32(); | ||
@@ -55,11 +67,11 @@ | ||
/////////////////////////////////////////////////// | ||
// Level index | ||
// Level Index. | ||
/////////////////////////////////////////////////// | ||
const levelByteLength = container.levelCount * 3 * 8; | ||
const levelReader = new BufferReader(data, idByteLength + headerByteLength, levelByteLength, true); | ||
const levelByteLength = levelCount * 3 * 8; | ||
const levelReader = new BufferReader(data, KTX2_ID.length + headerByteLength, levelByteLength, true); | ||
for (let i = 0; i < container.levelCount; i ++) { | ||
container.levelIndex.push({ | ||
data: new Uint8Array(data, levelReader._nextUint64(), levelReader._nextUint64()), | ||
for (let i = 0; i < levelCount; i ++) { | ||
container.levels.push({ | ||
levelData: new Uint8Array(data.buffer, data.byteOffset + levelReader._nextUint64(), levelReader._nextUint64()), | ||
uncompressedByteLength: levelReader._nextUint64(), | ||
@@ -71,3 +83,3 @@ }); | ||
/////////////////////////////////////////////////// | ||
// Data Format Descriptor (DFD) | ||
// Data Format Descriptor (DFD). | ||
/////////////////////////////////////////////////// | ||
@@ -77,8 +89,6 @@ | ||
const sampleStart = 6; | ||
const sampleWords = 4; | ||
const dfd: KTX2DataFormatDescriptor = { | ||
const dfd: KTX2DataFormatDescriptorBasicFormat = { | ||
vendorId: dfdReader._skip(4 /* totalSize */)._nextUint16(), | ||
versionNumber: dfdReader._skip(2 /* descriptorType */)._nextUint16(), | ||
descriptorType: dfdReader._nextUint16(), | ||
versionNumber: dfdReader._nextUint16(), | ||
descriptorBlockSize: dfdReader._nextUint16(), | ||
@@ -95,29 +105,59 @@ colorModel: dfdReader._nextUint8(), | ||
}, | ||
bytesPlane0: dfdReader._nextUint8(), | ||
numSamples: 0, | ||
bytesPlane: [ | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
], | ||
samples: [], | ||
}; | ||
dfd.numSamples = (dfd.descriptorBlockSize / 4 - sampleStart) / sampleWords; | ||
const sampleStart = 6; | ||
const sampleWords = 4; | ||
const numSamples = (dfd.descriptorBlockSize / 4 - sampleStart) / sampleWords; | ||
dfdReader._skip(7 /* bytesPlane[1-7] */); | ||
for (let i = 0; i < dfd.numSamples; i ++) { | ||
for (let i = 0; i < numSamples; i ++) { | ||
dfd.samples[ i ] = { | ||
channelID: dfdReader._skip(3 /* bitOffset + bitLength */)._nextUint8(), | ||
// ... remainder not implemented. | ||
bitOffset: dfdReader._nextUint16(), | ||
bitLength: dfdReader._nextUint8(), | ||
channelID: dfdReader._nextUint8(), | ||
samplePosition: [ | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
dfdReader._nextUint8(), | ||
], | ||
sampleLower: dfdReader._nextUint32(), | ||
sampleUpper: dfdReader._nextUint32(), | ||
}; | ||
dfdReader._skip(12 /* samplePosition[0-3], lower, upper */); | ||
} | ||
container.dataFormatDescriptor.length = 0; | ||
container.dataFormatDescriptor.push(dfd); | ||
/////////////////////////////////////////////////// | ||
// Key/Value Data (KVD) | ||
// Key/Value Data (KVD). | ||
/////////////////////////////////////////////////// | ||
// Not implemented. | ||
const kvd = {}; | ||
const kvdReader = new BufferReader(data, kvdByteOffset, kvdByteLength, true); | ||
while (kvdReader._offset < kvdByteLength) { | ||
const keyValueByteLength = kvdReader._nextUint32(); | ||
const keyData = kvdReader._scan(keyValueByteLength); | ||
const key = decodeText(keyData); | ||
const valueData = kvdReader._scan(keyValueByteLength - keyData.byteLength); | ||
container.keyValue[key] = key.match(/^ktx/i) ? decodeText(valueData) : valueData; | ||
// 4-byte alignment. | ||
if (keyValueByteLength % 4) kvdReader._skip(4 - (keyValueByteLength % 4)); | ||
} | ||
/////////////////////////////////////////////////// | ||
// Supercompression Global Data (SGD) | ||
// Supercompression Global Data (SGD). | ||
/////////////////////////////////////////////////// | ||
@@ -127,8 +167,3 @@ | ||
const sgdReader = new BufferReader( | ||
data, | ||
sgdByteOffset, | ||
sgdByteLength, | ||
true | ||
); | ||
const sgdReader = new BufferReader(data, sgdByteOffset, sgdByteLength, true); | ||
@@ -143,3 +178,3 @@ const endpointCount = sgdReader._nextUint16(); | ||
const imageDescs = []; | ||
for (let i = 0; i < container.levelCount; i ++) { | ||
for (let i = 0; i < levelCount; i ++) { | ||
imageDescs.push({ | ||
@@ -159,6 +194,6 @@ imageFlags: sgdReader._nextUint32(), | ||
const endpointsData = new Uint8Array(data, endpointsByteOffset, endpointsByteLength); | ||
const selectorsData = new Uint8Array(data, selectorsByteOffset, selectorsByteLength); | ||
const tablesData = new Uint8Array(data, tablesByteOffset, tablesByteLength); | ||
const extendedData = new Uint8Array(data, extendedByteOffset, extendedByteLength); | ||
const endpointsData = new Uint8Array(data.buffer, data.byteOffset + endpointsByteOffset, endpointsByteLength); | ||
const selectorsData = new Uint8Array(data.buffer, data.byteOffset + selectorsByteOffset, selectorsByteLength); | ||
const tablesData = new Uint8Array(data.buffer, data.byteOffset + tablesByteOffset, tablesByteLength); | ||
const extendedData = new Uint8Array(data.buffer, data.byteOffset + extendedByteOffset, extendedByteLength); | ||
@@ -168,6 +203,2 @@ container.globalData = { | ||
selectorCount, | ||
endpointsByteLength, | ||
selectorsByteLength, | ||
tablesByteLength, | ||
extendedByteLength, | ||
imageDescs, | ||
@@ -174,0 +205,0 @@ endpointsData, |
205
src/write.ts
@@ -1,5 +0,204 @@ | ||
import { Container } from './container'; | ||
import { HEADER_BYTE_LENGTH, KTX2DataFormatType, KTX2_ID, KTX_WRITER, NUL } from './constants'; | ||
import { KTX2Container } from './container'; | ||
import { concat, encodeText } from './util'; | ||
export function write(container: Container): Uint8Array { | ||
return new Uint8Array(); | ||
interface WriteOptions {keepWriter?: boolean}; | ||
const DEFAULT_OPTIONS: WriteOptions = {keepWriter: false}; | ||
/** | ||
* Serializes a {@link KTX2Container} instance to a KTX 2.0 file. Mip levels and other binary data | ||
* are copied into the resulting Uint8Array, so the original container can safely be edited or | ||
* destroyed after it is serialized. | ||
* | ||
* Options: | ||
* - keepWriter: If true, 'KTXWriter' key/value field is written as provided by the container. | ||
* Otherwise, a string for the current ktx-parse version is generated. Default: false. | ||
* | ||
* @param container | ||
* @param options | ||
*/ | ||
export function write(container: KTX2Container, options: WriteOptions = {}): Uint8Array { | ||
options = {...DEFAULT_OPTIONS, ...options}; | ||
/////////////////////////////////////////////////// | ||
// Supercompression Global Data (SGD). | ||
/////////////////////////////////////////////////// | ||
let sgdBuffer = new ArrayBuffer(0); | ||
if (container.globalData) { | ||
const sgdHeaderBuffer = new ArrayBuffer(20 + container.globalData.imageDescs.length * 5 * 4); | ||
const sgdHeaderView = new DataView(sgdHeaderBuffer); | ||
sgdHeaderView.setUint16(0, container.globalData.endpointCount, true); | ||
sgdHeaderView.setUint16(2, container.globalData.selectorCount, true); | ||
sgdHeaderView.setUint32(4, container.globalData.endpointsData.byteLength, true); | ||
sgdHeaderView.setUint32(8, container.globalData.selectorsData.byteLength, true); | ||
sgdHeaderView.setUint32(12, container.globalData.tablesData.byteLength, true); | ||
sgdHeaderView.setUint32(16, container.globalData.extendedData.byteLength, true); | ||
for (let i = 0; i < container.globalData.imageDescs.length; i++) { | ||
const imageDesc = container.globalData.imageDescs[i]; | ||
sgdHeaderView.setUint32(20 + i * 5 * 4 + 0, imageDesc.imageFlags, true); | ||
sgdHeaderView.setUint32(20 + i * 5 * 4 + 4, imageDesc.rgbSliceByteOffset, true); | ||
sgdHeaderView.setUint32(20 + i * 5 * 4 + 8, imageDesc.rgbSliceByteLength, true); | ||
sgdHeaderView.setUint32(20 + i * 5 * 4 + 12, imageDesc.alphaSliceByteOffset, true); | ||
sgdHeaderView.setUint32(20 + i * 5 * 4 + 16, imageDesc.alphaSliceByteLength, true); | ||
} | ||
sgdBuffer = concat([ | ||
sgdHeaderBuffer, | ||
container.globalData.endpointsData, | ||
container.globalData.selectorsData, | ||
container.globalData.tablesData, | ||
container.globalData.extendedData, | ||
]); | ||
} | ||
/////////////////////////////////////////////////// | ||
// Key/Value Data (KVD). | ||
/////////////////////////////////////////////////// | ||
const keyValueData: Uint8Array[] = []; | ||
let keyValue = container.keyValue; | ||
if (!options.keepWriter) { | ||
keyValue = {...container.keyValue, 'KTXwriter': KTX_WRITER}; | ||
} | ||
for (const key in keyValue) { | ||
const value = keyValue[key]; | ||
const keyData = encodeText(key); | ||
const valueData = typeof value === 'string' ? encodeText(value) : value; | ||
const kvByteLength = keyData.byteLength + 1 + valueData.byteLength + 1; | ||
const kvPadding = kvByteLength % 4 ? (4 - (kvByteLength % 4)) : 0; // align(4) | ||
keyValueData.push(concat([ | ||
new Uint32Array([kvByteLength]), | ||
keyData, | ||
NUL, | ||
valueData, | ||
NUL, | ||
new Uint8Array(kvPadding).fill(0x00), // align(4) | ||
])); | ||
} | ||
const kvdBuffer = concat(keyValueData); | ||
/////////////////////////////////////////////////// | ||
// Data Format Descriptor (DFD). | ||
/////////////////////////////////////////////////// | ||
const dfdBuffer = new ArrayBuffer(44); | ||
const dfdView = new DataView(dfdBuffer); | ||
if (container.dataFormatDescriptor.length !== 1 | ||
|| container.dataFormatDescriptor[0].descriptorType !== KTX2DataFormatType.BASICFORMAT) { | ||
throw new Error('Only BASICFORMAT Data Format Descriptor output supported.'); | ||
} | ||
const dfd = container.dataFormatDescriptor[0]; | ||
dfdView.setUint32(0, 44, true); | ||
dfdView.setUint16(4, dfd.vendorId, true); | ||
dfdView.setUint16(6, dfd.descriptorType, true); | ||
dfdView.setUint16(8, dfd.versionNumber, true); | ||
dfdView.setUint16(10, dfd.descriptorBlockSize, true); | ||
dfdView.setUint8(12, dfd.colorModel); | ||
dfdView.setUint8(13, dfd.colorPrimaries); | ||
dfdView.setUint8(14, dfd.transferFunction); | ||
dfdView.setUint8(15, dfd.flags); | ||
dfdView.setUint8(16, dfd.texelBlockDimension.x - 1); | ||
dfdView.setUint8(17, dfd.texelBlockDimension.y - 1); | ||
dfdView.setUint8(18, dfd.texelBlockDimension.z - 1); | ||
dfdView.setUint8(19, dfd.texelBlockDimension.w - 1); | ||
for (let i = 0; i < 8; i++) dfdView.setUint8(20 + i, dfd.bytesPlane[i]); | ||
for (let i = 0; i < dfd.samples.length; i++) { | ||
const sample = dfd.samples[i]; | ||
const sampleByteOffset = 28 + i * 16; | ||
dfdView.setUint16(sampleByteOffset + 0, sample.bitOffset, true); | ||
dfdView.setUint8(sampleByteOffset + 2, sample.bitLength); | ||
dfdView.setUint8(sampleByteOffset + 3, sample.channelID); | ||
dfdView.setUint8(sampleByteOffset + 4, sample.samplePosition[0]); | ||
dfdView.setUint8(sampleByteOffset + 5, sample.samplePosition[1]); | ||
dfdView.setUint8(sampleByteOffset + 6, sample.samplePosition[2]); | ||
dfdView.setUint8(sampleByteOffset + 7, sample.samplePosition[3]); | ||
dfdView.setUint32(sampleByteOffset + 8, sample.sampleLower, true); | ||
dfdView.setUint32(sampleByteOffset + 12, sample.sampleUpper, true); | ||
} | ||
/////////////////////////////////////////////////// | ||
// Data alignment. | ||
/////////////////////////////////////////////////// | ||
const dfdByteOffset = KTX2_ID.length + HEADER_BYTE_LENGTH + container.levels.length * 3 * 8; | ||
const kvdByteOffset = dfdByteOffset + dfdBuffer.byteLength; | ||
let sgdByteOffset = kvdByteOffset + kvdBuffer.byteLength; | ||
if (sgdByteOffset % 8) sgdByteOffset += 8 - (sgdByteOffset % 8); // align(8) | ||
/////////////////////////////////////////////////// | ||
// Level Index. | ||
/////////////////////////////////////////////////// | ||
const levelData: Uint8Array[] = []; | ||
const levelIndex = new DataView(new ArrayBuffer(container.levels.length * 3 * 8)); | ||
let levelDataByteOffset = sgdByteOffset + sgdBuffer.byteLength; | ||
for (let i = 0; i < container.levels.length; i++) { | ||
const level = container.levels[i]; | ||
levelData.push(level.levelData); | ||
levelIndex.setBigUint64(i * 24 + 0, BigInt(levelDataByteOffset), true); | ||
levelIndex.setBigUint64(i * 24 + 8, BigInt(level.levelData.byteLength), true); | ||
levelIndex.setBigUint64(i * 24 + 16, BigInt(level.uncompressedByteLength), true); | ||
levelDataByteOffset += level.levelData.byteLength; | ||
} | ||
/////////////////////////////////////////////////// | ||
// Header. | ||
/////////////////////////////////////////////////// | ||
const headerBuffer = new ArrayBuffer(HEADER_BYTE_LENGTH); | ||
const headerView = new DataView(headerBuffer); | ||
headerView.setUint32(0, container.vkFormat, true); | ||
headerView.setUint32(4, container.typeSize, true); | ||
headerView.setUint32(8, container.pixelWidth, true); | ||
headerView.setUint32(12, container.pixelHeight, true); | ||
headerView.setUint32(16, container.pixelDepth, true); | ||
headerView.setUint32(20, container.layerCount, true); | ||
headerView.setUint32(24, container.faceCount, true); | ||
headerView.setUint32(28, container.levels.length, true); | ||
headerView.setUint32(32, container.supercompressionScheme, true); | ||
headerView.setUint32(36, dfdByteOffset, true); | ||
headerView.setUint32(40, dfdBuffer.byteLength, true); | ||
headerView.setUint32(44, kvdByteOffset, true); | ||
headerView.setUint32(48, kvdBuffer.byteLength, true); | ||
headerView.setBigUint64(52, BigInt(sgdByteOffset), true); | ||
headerView.setBigUint64(60, BigInt(sgdBuffer.byteLength), true); | ||
/////////////////////////////////////////////////// | ||
// Compose. | ||
/////////////////////////////////////////////////// | ||
return new Uint8Array(concat([ | ||
new Uint8Array(KTX2_ID).buffer, | ||
headerBuffer, | ||
levelIndex.buffer, | ||
dfdBuffer, | ||
kvdBuffer, | ||
new ArrayBuffer(sgdByteOffset - (kvdByteOffset + kvdBuffer.byteLength)), // align(8) | ||
sgdBuffer, | ||
...levelData, | ||
])); | ||
} | ||
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
174747
26
964
252
13
1