智慧水务管理系统 - 精河县供水工程综合管理平台

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { HEADER_BYTE_LENGTH, KTX2_ID, KTX_WRITER, NUL } from './constants-internal.js';
  2. import {
  3. KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT,
  4. KHR_DF_SAMPLE_DATATYPE_SIGNED,
  5. KHR_SUPERCOMPRESSION_NONE,
  6. } from './constants.js';
  7. import type { KTX2Container } from './container.js';
  8. import { concat, encodeText, getBlockByteLength, getPadding, leastCommonMultiple } from './util.js';
  9. interface WriteOptions {
  10. keepWriter?: boolean;
  11. }
  12. const DEFAULT_OPTIONS: WriteOptions = { keepWriter: false };
  13. /**
  14. * Serializes a {@link KTX2Container} instance to a KTX 2.0 file. Mip levels and other binary data
  15. * are copied into the resulting Uint8Array, so the original container can safely be edited or
  16. * destroyed after it is serialized.
  17. *
  18. * Options:
  19. * - keepWriter: If true, 'KTXWriter' key/value field is written as provided by the container.
  20. * Otherwise, a string for the current ktx-parse version is generated. Default: false.
  21. *
  22. * @param container
  23. * @param options
  24. */
  25. export function write(container: KTX2Container, options: WriteOptions = {}): Uint8Array {
  26. // biome-ignore lint/style/noParameterAssign: Merging defaults only.
  27. options = { ...DEFAULT_OPTIONS, ...options };
  28. ///////////////////////////////////////////////////
  29. // Supercompression Global Data (SGD).
  30. ///////////////////////////////////////////////////
  31. let sgdBuffer = new ArrayBuffer(0);
  32. if (container.globalData) {
  33. const sgdHeaderBuffer = new ArrayBuffer(20 + container.globalData.imageDescs.length * 5 * 4);
  34. const sgdHeaderView = new DataView(sgdHeaderBuffer);
  35. sgdHeaderView.setUint16(0, container.globalData.endpointCount, true);
  36. sgdHeaderView.setUint16(2, container.globalData.selectorCount, true);
  37. sgdHeaderView.setUint32(4, container.globalData.endpointsData.byteLength, true);
  38. sgdHeaderView.setUint32(8, container.globalData.selectorsData.byteLength, true);
  39. sgdHeaderView.setUint32(12, container.globalData.tablesData.byteLength, true);
  40. sgdHeaderView.setUint32(16, container.globalData.extendedData.byteLength, true);
  41. for (let i = 0; i < container.globalData.imageDescs.length; i++) {
  42. const imageDesc = container.globalData.imageDescs[i];
  43. sgdHeaderView.setUint32(20 + i * 5 * 4 + 0, imageDesc.imageFlags, true);
  44. sgdHeaderView.setUint32(20 + i * 5 * 4 + 4, imageDesc.rgbSliceByteOffset, true);
  45. sgdHeaderView.setUint32(20 + i * 5 * 4 + 8, imageDesc.rgbSliceByteLength, true);
  46. sgdHeaderView.setUint32(20 + i * 5 * 4 + 12, imageDesc.alphaSliceByteOffset, true);
  47. sgdHeaderView.setUint32(20 + i * 5 * 4 + 16, imageDesc.alphaSliceByteLength, true);
  48. }
  49. sgdBuffer = concat([
  50. sgdHeaderBuffer,
  51. container.globalData.endpointsData,
  52. container.globalData.selectorsData,
  53. container.globalData.tablesData,
  54. container.globalData.extendedData,
  55. ]);
  56. }
  57. ///////////////////////////////////////////////////
  58. // Key/Value Data (KVD).
  59. ///////////////////////////////////////////////////
  60. const keyValueData: Uint8Array[] = [];
  61. const keyValueList = Object.entries({
  62. ...container.keyValue,
  63. ...(!options.keepWriter && { KTXwriter: KTX_WRITER }),
  64. });
  65. keyValueList.sort((a, b) => (a[0] > b[0] ? 1 : -1));
  66. for (const [key, value] of keyValueList) {
  67. const keyData = encodeText(key);
  68. const valueData = typeof value === 'string' ? concat([encodeText(value), NUL]) : value;
  69. const kvByteLength = keyData.byteLength + 1 + valueData.byteLength;
  70. const kvPadding = getPadding(kvByteLength, 4); // align(4)
  71. keyValueData.push(
  72. concat([
  73. new Uint32Array([kvByteLength]),
  74. keyData,
  75. NUL,
  76. valueData,
  77. new Uint8Array(kvPadding).fill(0x00), // align(4)
  78. ]),
  79. );
  80. }
  81. const kvdBuffer = concat(keyValueData);
  82. ///////////////////////////////////////////////////
  83. // Data Format Descriptor (DFD).
  84. ///////////////////////////////////////////////////
  85. if (
  86. container.dataFormatDescriptor.length !== 1 ||
  87. container.dataFormatDescriptor[0].descriptorType !== KHR_DF_KHR_DESCRIPTORTYPE_BASICFORMAT
  88. ) {
  89. throw new Error('Only BASICFORMAT Data Format Descriptor output supported.');
  90. }
  91. const dfd = container.dataFormatDescriptor[0];
  92. const dfdBuffer = new ArrayBuffer(28 + dfd.samples.length * 16);
  93. const dfdView = new DataView(dfdBuffer);
  94. const descriptorBlockSize = 24 + dfd.samples.length * 16;
  95. dfdView.setUint32(0, dfdBuffer.byteLength, true);
  96. dfdView.setUint16(4, dfd.vendorId, true);
  97. dfdView.setUint16(6, dfd.descriptorType, true);
  98. dfdView.setUint16(8, dfd.versionNumber, true);
  99. dfdView.setUint16(10, descriptorBlockSize, true);
  100. dfdView.setUint8(12, dfd.colorModel);
  101. dfdView.setUint8(13, dfd.colorPrimaries);
  102. dfdView.setUint8(14, dfd.transferFunction);
  103. dfdView.setUint8(15, dfd.flags);
  104. if (!Array.isArray(dfd.texelBlockDimension)) {
  105. throw new Error('texelBlockDimension is now an array. For dimensionality `d`, set `d - 1`.');
  106. }
  107. dfdView.setUint8(16, dfd.texelBlockDimension[0]);
  108. dfdView.setUint8(17, dfd.texelBlockDimension[1]);
  109. dfdView.setUint8(18, dfd.texelBlockDimension[2]);
  110. dfdView.setUint8(19, dfd.texelBlockDimension[3]);
  111. for (let i = 0; i < 8; i++) dfdView.setUint8(20 + i, dfd.bytesPlane[i]);
  112. for (let i = 0; i < dfd.samples.length; i++) {
  113. const sample = dfd.samples[i];
  114. const sampleByteOffset = 28 + i * 16;
  115. dfdView.setUint16(sampleByteOffset + 0, sample.bitOffset, true);
  116. dfdView.setUint8(sampleByteOffset + 2, sample.bitLength);
  117. dfdView.setUint8(sampleByteOffset + 3, sample.channelType);
  118. dfdView.setUint8(sampleByteOffset + 4, sample.samplePosition[0]);
  119. dfdView.setUint8(sampleByteOffset + 5, sample.samplePosition[1]);
  120. dfdView.setUint8(sampleByteOffset + 6, sample.samplePosition[2]);
  121. dfdView.setUint8(sampleByteOffset + 7, sample.samplePosition[3]);
  122. if (sample.channelType & KHR_DF_SAMPLE_DATATYPE_SIGNED) {
  123. dfdView.setInt32(sampleByteOffset + 8, sample.sampleLower, true);
  124. dfdView.setInt32(sampleByteOffset + 12, sample.sampleUpper, true);
  125. } else {
  126. dfdView.setUint32(sampleByteOffset + 8, sample.sampleLower, true);
  127. dfdView.setUint32(sampleByteOffset + 12, sample.sampleUpper, true);
  128. }
  129. }
  130. ///////////////////////////////////////////////////
  131. // Data alignment.
  132. ///////////////////////////////////////////////////
  133. const dfdByteOffset = KTX2_ID.length + HEADER_BYTE_LENGTH + container.levels.length * 3 * 8;
  134. const kvdByteOffset = dfdByteOffset + dfdBuffer.byteLength;
  135. let sgdByteOffset = sgdBuffer.byteLength > 0 ? kvdByteOffset + kvdBuffer.byteLength : 0;
  136. if (sgdByteOffset % 8) sgdByteOffset += 8 - (sgdByteOffset % 8); // align(8)
  137. ///////////////////////////////////////////////////
  138. // Level Index.
  139. ///////////////////////////////////////////////////
  140. const levelData: Uint8Array[] = [];
  141. const levelIndex = new DataView(new ArrayBuffer(container.levels.length * 3 * 8));
  142. const levelDataByteOffsets = new Uint32Array(container.levels.length);
  143. let levelAlign = 0;
  144. if (container.supercompressionScheme === KHR_SUPERCOMPRESSION_NONE) {
  145. levelAlign = leastCommonMultiple(getBlockByteLength(container), 4);
  146. }
  147. // Level data is ordered small → large.
  148. let levelDataByteOffset = (sgdByteOffset || kvdByteOffset + kvdBuffer.byteLength) + sgdBuffer.byteLength;
  149. for (let i = container.levels.length - 1; i >= 0; i--) {
  150. // Level padding.
  151. if (levelDataByteOffset % levelAlign) {
  152. const paddingBytes = getPadding(levelDataByteOffset, levelAlign);
  153. levelData.push(new Uint8Array(paddingBytes));
  154. levelDataByteOffset += paddingBytes;
  155. }
  156. // Level data.
  157. const level = container.levels[i];
  158. levelData.push(level.levelData);
  159. levelDataByteOffsets[i] = levelDataByteOffset;
  160. levelDataByteOffset += level.levelData.byteLength;
  161. }
  162. // Level index is ordered large → small.
  163. for (let i = 0; i < container.levels.length; i++) {
  164. const level = container.levels[i];
  165. levelIndex.setBigUint64(i * 24 + 0, BigInt(levelDataByteOffsets[i]), true);
  166. levelIndex.setBigUint64(i * 24 + 8, BigInt(level.levelData.byteLength), true);
  167. levelIndex.setBigUint64(i * 24 + 16, BigInt(level.uncompressedByteLength), true);
  168. }
  169. ///////////////////////////////////////////////////
  170. // Header.
  171. ///////////////////////////////////////////////////
  172. const headerBuffer = new ArrayBuffer(HEADER_BYTE_LENGTH);
  173. const headerView = new DataView(headerBuffer);
  174. headerView.setUint32(0, container.vkFormat, true);
  175. headerView.setUint32(4, container.typeSize, true);
  176. headerView.setUint32(8, container.pixelWidth, true);
  177. headerView.setUint32(12, container.pixelHeight, true);
  178. headerView.setUint32(16, container.pixelDepth, true);
  179. headerView.setUint32(20, container.layerCount, true);
  180. headerView.setUint32(24, container.faceCount, true);
  181. headerView.setUint32(28, container.levelCount, true);
  182. headerView.setUint32(32, container.supercompressionScheme, true);
  183. headerView.setUint32(36, dfdByteOffset, true);
  184. headerView.setUint32(40, dfdBuffer.byteLength, true);
  185. headerView.setUint32(44, kvdByteOffset, true);
  186. headerView.setUint32(48, kvdBuffer.byteLength, true);
  187. headerView.setBigUint64(52, BigInt(sgdBuffer.byteLength > 0 ? sgdByteOffset : 0), true);
  188. headerView.setBigUint64(60, BigInt(sgdBuffer.byteLength), true);
  189. ///////////////////////////////////////////////////
  190. // Compose.
  191. ///////////////////////////////////////////////////
  192. return new Uint8Array(
  193. concat([
  194. new Uint8Array(KTX2_ID).buffer,
  195. headerBuffer,
  196. levelIndex.buffer,
  197. dfdBuffer,
  198. kvdBuffer,
  199. sgdByteOffset > 0
  200. ? new ArrayBuffer(sgdByteOffset - (kvdByteOffset + kvdBuffer.byteLength)) // align(8)
  201. : new ArrayBuffer(0),
  202. sgdBuffer,
  203. ...levelData,
  204. ]),
  205. );
  206. }