├── tsconfig.web.json ├── .prettierignore ├── .gitignore ├── website ├── .gitignore ├── public │ ├── favicon.ico │ └── basis │ │ ├── basis_encoder.wasm │ │ └── basis_encoder.d.ts ├── tools │ ├── viewer.md │ └── index.md ├── index.md ├── guide │ ├── gltf-transform.md │ ├── index.md │ ├── advanced.md │ └── api.md ├── api-examples.md ├── markdown-examples.md └── .vitepress │ ├── theme │ └── index.ts │ ├── config.ts │ └── components │ ├── ktx2-viewer.vue │ ├── ktx-encoder.vue │ └── ktx-cube-encoder.vue ├── public ├── tests │ ├── DuckCM.png │ ├── DuckCM-etc1s.ktx2 │ ├── DuckCM-uastc.ktx2 │ ├── cubemap │ │ ├── negx.jpg │ │ ├── negy.jpg │ │ ├── negz.jpg │ │ ├── posx.jpg │ │ ├── posy.jpg │ │ ├── posz.jpg │ │ └── cubemap.ktx2 │ └── pretoria_gardens_1k.hdr └── basis_encoder.wasm ├── src ├── basis │ ├── basis_encoder.wasm │ └── basis_encoder.d.ts ├── gltf-transform │ ├── index.ts │ └── ktx2.ts ├── utils.ts ├── enum.ts ├── node │ ├── index.ts │ └── NodeBasisEncoder.ts ├── web │ ├── index.ts │ ├── decodeImageData.ts │ └── BrowserBasisEncoder.ts ├── applyInputOptions.ts └── type.ts ├── .prettierrc.yml ├── tsconfig.node.json ├── index.html ├── docs ├── globals.md ├── functions │ └── encodeToKTX2.md ├── enumerations │ ├── SourceType.md │ └── BasisTextureType.md ├── interfaces │ ├── IBasisModule.md │ ├── IEncodeOptions.md │ └── IBasisEncoder.md ├── README.md └── _media │ └── IEncodeOptions.md ├── vite.config.ts ├── tsconfig.json ├── .github └── workflows │ ├── release.yml │ ├── website │ └── deploy.yml ├── LICENSE ├── test ├── node.test.ts ├── web.test.ts ├── gltf-transform.web.test.ts └── gltf-transform.node.test.ts ├── README.md ├── package.json └── scene.json /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | types 4 | .DS_Store -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | .vitepress/dist 2 | .vitepress/cache -------------------------------------------------------------------------------- /public/tests/DuckCM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/DuckCM.png -------------------------------------------------------------------------------- /public/basis_encoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/basis_encoder.wasm -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/website/public/favicon.ico -------------------------------------------------------------------------------- /src/basis/basis_encoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/src/basis/basis_encoder.wasm -------------------------------------------------------------------------------- /src/gltf-transform/index.ts: -------------------------------------------------------------------------------- 1 | export { ktx2 } from './ktx2.js'; 2 | export type { KTX2Options } from './ktx2.js'; -------------------------------------------------------------------------------- /public/tests/DuckCM-etc1s.ktx2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/DuckCM-etc1s.ktx2 -------------------------------------------------------------------------------- /public/tests/DuckCM-uastc.ktx2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/DuckCM-uastc.ktx2 -------------------------------------------------------------------------------- /public/tests/cubemap/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/negx.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/negy.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/negz.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/posx.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/posy.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/posz.jpg -------------------------------------------------------------------------------- /public/tests/cubemap/cubemap.ktx2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/cubemap/cubemap.ktx2 -------------------------------------------------------------------------------- /public/tests/pretoria_gardens_1k.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/public/tests/pretoria_gardens_1k.hdr -------------------------------------------------------------------------------- /website/public/basis/basis_encoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gz65555/ktx2-encoder/HEAD/website/public/basis/basis_encoder.wasm -------------------------------------------------------------------------------- /src/basis/basis_encoder.d.ts: -------------------------------------------------------------------------------- 1 | import type { IBasisModule } from "../../type"; 2 | 3 | declare const BASIS: () => IBasisModule; 4 | export default BASIS; 5 | -------------------------------------------------------------------------------- /website/public/basis/basis_encoder.d.ts: -------------------------------------------------------------------------------- 1 | import type { IBasisModule } from "../../type"; 2 | 3 | declare const BASIS: () => IBasisModule; 4 | export default BASIS; 5 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | # More detail:https://prettier.io/docs/en/configuration.html 2 | 3 | --- 4 | printWidth: 120 5 | proseWrap: never 6 | singleQuote: false 7 | trailingComma: none 8 | semi: true 9 | tabWidth: 2 10 | useTabs: false 11 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /website/tools/viewer.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: doc 3 | title: KTX2 Viewer 4 | --- 5 | 6 | # KTX2 Texture Viewer 7 | 8 | Upload a ktx2 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | export const DefaultOptions = { 3 | enableDebug: false, 4 | isUASTC: true, 5 | isKTX2File: true, 6 | isInputSRGB: true, 7 | generateMipmap: true, 8 | needSupercompression: true, 9 | isSetKTX2SRGBTransferFunc: true, 10 | isHDR: false, 11 | qualityLevel: 150 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/enum.ts: -------------------------------------------------------------------------------- 1 | export const enum BasisTextureType { 2 | cBASISTexType2D, 3 | cBASISTexType2DArray, 4 | cBASISTexTypeCubemapArray, 5 | cBASISTexTypeVideoFrames, 6 | cBASISTexTypeVolume 7 | } 8 | 9 | /** image source type */ 10 | export const enum SourceType { 11 | RAW, 12 | PNG 13 | } 14 | 15 | export const enum HDRSourceType { 16 | EXR = 3, 17 | HDR = 4, 18 | } 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | KTX2 Encoder 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/node/index.ts: -------------------------------------------------------------------------------- 1 | import { IEncodeOptions } from "../type.js"; 2 | import { nodeEncoder } from "./NodeBasisEncoder.js"; 3 | 4 | export function encodeToKTX2(imageBuffer: Uint8Array, options: Partial = {}): Promise { 5 | if (!options.imageDecoder) { 6 | throw "imageDecoder is required in Node.js."; 7 | } 8 | globalThis.__KTX2_DEBUG__ = options.enableDebug ?? false; 9 | return nodeEncoder.encode(imageBuffer, options); 10 | } 11 | -------------------------------------------------------------------------------- /docs/globals.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](README.md) 2 | 3 | *** 4 | 5 | # ktx2-encoder 6 | 7 | ## Enumerations 8 | 9 | - [BasisTextureType](enumerations/BasisTextureType.md) 10 | - [SourceType](enumerations/SourceType.md) 11 | 12 | ## Interfaces 13 | 14 | - [IBasisEncoder](interfaces/IBasisEncoder.md) 15 | - [IBasisModule](interfaces/IBasisModule.md) 16 | - [IEncodeOptions](interfaces/IEncodeOptions.md) 17 | 18 | ## Functions 19 | 20 | - [encodeToKTX2](functions/encodeToKTX2.md) 21 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite"; 3 | import react from "@vitejs/plugin-react"; 4 | import { resolve } from "path"; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [react()], 9 | build: { 10 | rollupOptions: { 11 | input: { 12 | main: resolve(__dirname, "example", "index.html"), 13 | preview: resolve(__dirname, "example", "preview.html") 14 | } 15 | } 16 | }, 17 | test: {} 18 | }); 19 | -------------------------------------------------------------------------------- /src/web/index.ts: -------------------------------------------------------------------------------- 1 | import { CubeBufferData, IEncodeOptions } from "../type.js"; 2 | import { browserEncoder } from "./BrowserBasisEncoder.js"; 3 | import { decodeImageBitmap } from "./decodeImageData.js"; 4 | 5 | export * from "../enum.js"; 6 | export * from "../type.js"; 7 | 8 | export function encodeToKTX2(imageBuffer: Uint8Array | CubeBufferData, options: IEncodeOptions): Promise { 9 | options.imageDecoder ??= decodeImageBitmap; 10 | globalThis.__KTX2_DEBUG__ = options.enableDebug ?? false; 11 | return browserEncoder.encode(imageBuffer, options); 12 | } 13 | -------------------------------------------------------------------------------- /docs/functions/encodeToKTX2.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / encodeToKTX2 6 | 7 | # Function: encodeToKTX2() 8 | 9 | > **encodeToKTX2**(`imageBuffer`, `options`): `Promise`\<`Uint8Array`\> 10 | 11 | Defined in: [web/index.ts:8](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/web/index.ts#L8) 12 | 13 | ## Parameters 14 | 15 | ### imageBuffer 16 | 17 | `Uint8Array` 18 | 19 | ### options 20 | 21 | `Partial`\<[`IEncodeOptions`](../interfaces/IEncodeOptions.md)\> = `{}` 22 | 23 | ## Returns 24 | 25 | `Promise`\<`Uint8Array`\> 26 | -------------------------------------------------------------------------------- /website/tools/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: KTX2 Encoder Tool 3 | --- 4 | 5 | 9 | 10 | # KTX2 Encoder Tool 11 | 12 | Upload an image and convert it to KTX2 format with custom encoding options. 13 | 14 | 15 | 16 | ## KTX2 Cubemap Encoder 17 | 18 | Upload 6 images named with posx, negx, posy, negy, posz, negz to create a KTX2 cubemap. 19 | 20 | 21 | 22 | ## Options 23 | 24 | See [Configuration Options](../guide/api.md#configuration-options) for more information. 25 | 26 | -------------------------------------------------------------------------------- /docs/enumerations/SourceType.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / SourceType 6 | 7 | # Enumeration: SourceType 8 | 9 | Defined in: [enum.ts:10](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L10) 10 | 11 | image source type 12 | 13 | ## Enumeration Members 14 | 15 | ### PNG 16 | 17 | > **PNG**: `1` 18 | 19 | Defined in: [enum.ts:12](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L12) 20 | 21 | *** 22 | 23 | ### RAW 24 | 25 | > **RAW**: `0` 26 | 27 | Defined in: [enum.ts:11](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L11) 28 | -------------------------------------------------------------------------------- /docs/interfaces/IBasisModule.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / IBasisModule 6 | 7 | # Interface: IBasisModule 8 | 9 | Defined in: [type.ts:124](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L124) 10 | 11 | ## Properties 12 | 13 | ### BasisEncoder 14 | 15 | > **BasisEncoder**: [`IBasisEncoder`](IBasisEncoder.md) 16 | 17 | Defined in: [type.ts:125](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L125) 18 | 19 | *** 20 | 21 | ### initializeBasis() 22 | 23 | > **initializeBasis**: () => `void` 24 | 25 | Defined in: [type.ts:126](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L126) 26 | 27 | #### Returns 28 | 29 | `void` 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "esModuleInterop": true, 5 | "useDefineForClassFields": true, 6 | "module": "ES2022", 7 | "skipLibCheck": true, 8 | 9 | "moduleResolution": "bundler", 10 | "allowImportingTsExtensions": false, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "forceConsistentCasingInFileNames": true, 14 | 15 | "strict": false, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": false, 18 | "typeRoots": ["./node_modules/@types"], 19 | "noFallthroughCasesInSwitch": true, 20 | "emitDeclarationOnly": false, 21 | "declaration": true, 22 | "declarationDir": "types", 23 | "outDir": "dist", 24 | }, 25 | "include": ["./src/*", "./src/web/*", "./src/node/*", "./src/gltf-transform/*"], 26 | "references": [{ "path": "./tsconfig.node.json" }] 27 | } 28 | -------------------------------------------------------------------------------- /website/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "KTX2-Encoder" 7 | tagline: A lightweight JavaScript library for converting images to KTX2 format 8 | actions: 9 | - theme: brand 10 | text: Get Started 11 | link: /guide/ 12 | - theme: alt 13 | text: View on GitHub 14 | link: https://github.com/gz65555/ktx2-encoder 15 | 16 | features: 17 | - title: Easy to Use 18 | details: Simple API for converting images to KTX2 format in both browser and Node.js environments 19 | - title: glTF Transform Integration 20 | details: Seamless integration with glTF Transform for processing textures in glTF/GLB files 21 | - title: Multiple Compression Options 22 | details: Support for both ETC1S and UASTC compression formats with configurable quality settings 23 | - title: Web-based Tool 24 | details: Built-in browser-based conversion tool for quick testing and conversion 25 | --- 26 | 27 | -------------------------------------------------------------------------------- /website/guide/gltf-transform.md: -------------------------------------------------------------------------------- 1 | # Usage with glTF Transform 2 | 3 | The KTX2-Encoder library provides seamless integration with glTF Transform for converting textures in your glTF/GLB files to KTX2 format. 4 | 5 | ## Configuration 6 | 7 | ```typescript 8 | import { ktx2 } from "ktx2-encoder/gltf-transform"; 9 | 10 | await document.transform( 11 | ktx2({ 12 | // Use UASTC for higher quality (better for normal maps and HDR textures) 13 | isUASTC: true, 14 | 15 | // Generate mipmaps for better rendering at different distances 16 | generateMipmap: true, 17 | 18 | // Path to the WebAssembly module (required) 19 | wasmUrl: "/basis_encoder.wasm", 20 | 21 | // Enable debug logging (optional) 22 | enableDebug: false 23 | }) 24 | ); 25 | ``` 26 | 27 | ## Important Notes 28 | 29 | - Remember to host the `basis_encoder.wasm` file on your server 30 | - UASTC is recommended for normal maps and HDR textures 31 | - ETC1S provides better compression ratios for most other textures -------------------------------------------------------------------------------- /website/guide/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | A lightweight JavaScript library for converting images to KTX2 (.ktx2) format. Powered by [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal). 4 | 5 | ## Installation 6 | 7 | ```shell 8 | npm install ktx2-encoder 9 | ``` 10 | 11 | ## Basic Usage 12 | 13 | ```javascript 14 | import { encodeToKTX2 } from 'ktx2-encoder'; 15 | 16 | // Convert a single image 17 | const ktx2Data = await encodeToKTX2(imageArrayBuffer, { 18 | isUASTC: false, 19 | generateMipmap: true 20 | }); 21 | ``` 22 | 23 | ## glTF Transform Integration 24 | 25 | ```typescript 26 | import { ktx2 } from "ktx2-encoder/gltf-transform"; 27 | 28 | await document.transform( 29 | ktx2({ 30 | isUASTC: true, 31 | enableDebug: false, 32 | generateMipmap: true, 33 | wasmUrl: "/basis_encoder.wasm" 34 | }) 35 | ); 36 | ``` 37 | 38 | ## Web Tool 39 | 40 | Start the development server: 41 | 42 | ```shell 43 | npm run dev 44 | ``` 45 | 46 | Then open http://127.0.0.1:5174 in your browser to use the web-based conversion tool. -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Install pnpm 20 | uses: pnpm/action-setup@v2 21 | with: 22 | run_install: true 23 | 24 | # after pnpm 25 | - name: Use Node.js LTS 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: lts/* 29 | registry-url: https://registry.npmjs.org/ 30 | cache: pnpm 31 | 32 | - run: pnpm run build 33 | # https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 34 | - run: npm publish --access public 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 37 | NPM_CONFIG_PROVENANCE: true 38 | 39 | - run: npx changelogithub 40 | env: 41 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 42 | -------------------------------------------------------------------------------- /website/api-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Runtime API Examples 6 | 7 | This page demonstrates usage of some of the runtime APIs provided by VitePress. 8 | 9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: 10 | 11 | ```md 12 | 17 | 18 | ## Results 19 | 20 | ### Theme Data 21 |
{{ theme }}
22 | 23 | ### Page Data 24 |
{{ page }}
25 | 26 | ### Page Frontmatter 27 |
{{ frontmatter }}
28 | ``` 29 | 30 | 35 | 36 | ## Results 37 | 38 | ### Theme Data 39 |
{{ theme }}
40 | 41 | ### Page Data 42 |
{{ page }}
43 | 44 | ### Page Frontmatter 45 |
{{ frontmatter }}
46 | 47 | ## More 48 | 49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 HU SONG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/web/decodeImageData.ts: -------------------------------------------------------------------------------- 1 | export const decodeImageBitmap = (function () { 2 | const getGlContext = (function () { 3 | let gl: WebGL2RenderingContext | null = null; 4 | return function () { 5 | if (!gl) { 6 | const canvas = new OffscreenCanvas(128, 128); 7 | gl = canvas.getContext("webgl2", { premultipliedAlpha: false }); 8 | } 9 | return gl as WebGL2RenderingContext; 10 | }; 11 | })(); 12 | 13 | return async function webglDecode(imageBuffer: Uint8Array) { 14 | const gl = getGlContext(); 15 | const imageBitmap = await createImageBitmap(new Blob([imageBuffer])); 16 | let texture = gl.createTexture(); 17 | gl.bindTexture(gl.TEXTURE_2D, texture); 18 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageBitmap); 19 | 20 | let framebuffer = gl.createFramebuffer(); 21 | gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); 22 | gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); 23 | 24 | let width = imageBitmap.width; 25 | let height = imageBitmap.height; 26 | let pixels = new Uint8Array(width * height * 4); 27 | gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); 28 | 29 | gl.deleteTexture(texture); 30 | gl.deleteFramebuffer(framebuffer); 31 | 32 | return { 33 | data: new Uint8Array(pixels), 34 | width, 35 | height 36 | }; 37 | }; 38 | })(); 39 | -------------------------------------------------------------------------------- /docs/enumerations/BasisTextureType.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / BasisTextureType 6 | 7 | # Enumeration: BasisTextureType 8 | 9 | Defined in: [enum.ts:1](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L1) 10 | 11 | ## Enumeration Members 12 | 13 | ### cBASISTexType2D 14 | 15 | > **cBASISTexType2D**: `0` 16 | 17 | Defined in: [enum.ts:2](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L2) 18 | 19 | *** 20 | 21 | ### cBASISTexType2DArray 22 | 23 | > **cBASISTexType2DArray**: `1` 24 | 25 | Defined in: [enum.ts:3](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L3) 26 | 27 | *** 28 | 29 | ### cBASISTexTypeCubemapArray 30 | 31 | > **cBASISTexTypeCubemapArray**: `2` 32 | 33 | Defined in: [enum.ts:4](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L4) 34 | 35 | *** 36 | 37 | ### cBASISTexTypeVideoFrames 38 | 39 | > **cBASISTexTypeVideoFrames**: `3` 40 | 41 | Defined in: [enum.ts:5](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L5) 42 | 43 | *** 44 | 45 | ### cBASISTexTypeVolume 46 | 47 | > **cBASISTexTypeVolume**: `4` 48 | 49 | Defined in: [enum.ts:6](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/enum.ts#L6) 50 | -------------------------------------------------------------------------------- /website/markdown-examples.md: -------------------------------------------------------------------------------- 1 | # Markdown Extension Examples 2 | 3 | This page demonstrates some of the built-in markdown extensions provided by VitePress. 4 | 5 | ## Syntax Highlighting 6 | 7 | VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: 8 | 9 | **Input** 10 | 11 | ````md 12 | ```js{4} 13 | export default { 14 | data () { 15 | return { 16 | msg: 'Highlighted!' 17 | } 18 | } 19 | } 20 | ``` 21 | ```` 22 | 23 | **Output** 24 | 25 | ```js{4} 26 | export default { 27 | data () { 28 | return { 29 | msg: 'Highlighted!' 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Custom Containers 36 | 37 | **Input** 38 | 39 | ```md 40 | ::: info 41 | This is an info box. 42 | ::: 43 | 44 | ::: tip 45 | This is a tip. 46 | ::: 47 | 48 | ::: warning 49 | This is a warning. 50 | ::: 51 | 52 | ::: danger 53 | This is a dangerous warning. 54 | ::: 55 | 56 | ::: details 57 | This is a details block. 58 | ::: 59 | ``` 60 | 61 | **Output** 62 | 63 | ::: info 64 | This is an info box. 65 | ::: 66 | 67 | ::: tip 68 | This is a tip. 69 | ::: 70 | 71 | ::: warning 72 | This is a warning. 73 | ::: 74 | 75 | ::: danger 76 | This is a dangerous warning. 77 | ::: 78 | 79 | ::: details 80 | This is a details block. 81 | ::: 82 | 83 | ## More 84 | 85 | Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). 86 | -------------------------------------------------------------------------------- /website/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | // .vitepress/theme/index.js 2 | 3 | import { setup } from "@css-render/vue3-ssr"; 4 | import { NConfigProvider } from "naive-ui"; 5 | import { useRoute } from "vitepress"; 6 | import DefaultTheme from "vitepress/theme"; 7 | import { defineComponent, h, inject } from "vue"; 8 | 9 | const { Layout } = DefaultTheme; 10 | 11 | const CssRenderStyle = defineComponent({ 12 | setup() { 13 | const collect: any = inject("css-render-collect"); 14 | return { 15 | style: collect() 16 | }; 17 | }, 18 | render() { 19 | return h("css-render-style", { 20 | innerHTML: this.style 21 | }); 22 | } 23 | }); 24 | 25 | const VitepressPath = defineComponent({ 26 | setup() { 27 | const route = useRoute(); 28 | return () => { 29 | return h("vitepress-path", null, [route.path]); 30 | }; 31 | } 32 | }); 33 | 34 | const NaiveUIProvider = defineComponent({ 35 | render() { 36 | return h( 37 | NConfigProvider, 38 | { abstract: true, inlineThemeDisabled: true }, 39 | { 40 | default: () => [ 41 | h(Layout, null, { default: this.$slots.default?.() }), 42 | // @ts-ignore 43 | import.meta.env.SSR ? [h(CssRenderStyle), h(VitepressPath)] : null 44 | ] 45 | } 46 | ); 47 | } 48 | }); 49 | 50 | export default { 51 | extends: DefaultTheme, 52 | Layout: NaiveUIProvider, 53 | enhanceApp: ({ app }) => { 54 | // @ts-ignore 55 | if (import.meta.env.SSR) { 56 | const { collect } = setup(app); 57 | app.provide("css-render-collect", collect); 58 | } 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /test/node.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { encodeToKTX2 } from "../src/node"; 3 | import { readFile } from "fs/promises"; 4 | import sharp from "sharp"; 5 | 6 | async function imageDecoder(buffer: Uint8Array) { 7 | const image = sharp(buffer); 8 | const metadata = await image.metadata(); 9 | const { width, height } = metadata; 10 | const rawBuffer = await image.ensureAlpha().raw().toBuffer(); 11 | const data = new Uint8Array(rawBuffer); 12 | 13 | // 创建 imageData 对象 14 | const imageData = { 15 | width: width!, 16 | height: height!, 17 | data 18 | }; 19 | 20 | return imageData; 21 | } 22 | 23 | test("uastc", { timeout: Infinity }, async () => { 24 | const buffer = await readFile("./public/tests/DuckCM.png"); 25 | const result = await encodeToKTX2(new Uint8Array(buffer), { 26 | isUASTC: true, 27 | enableDebug: false, 28 | qualityLevel: 230, 29 | generateMipmap: true, 30 | imageDecoder 31 | }); 32 | 33 | const resultBuffer = await readFile("./public/tests/DuckCM-uastc.ktx2"); 34 | const testArray = Array.from(new Uint8Array(resultBuffer)); 35 | const resultArray = Array.from(result); 36 | expect(testArray).toEqual(resultArray); 37 | }); 38 | 39 | test("etc1s", { timeout: Infinity }, async () => { 40 | const buffer = await readFile("./public/tests/DuckCM.png"); 41 | const result = await encodeToKTX2(new Uint8Array(buffer), { 42 | isUASTC: false, 43 | enableDebug: false, 44 | qualityLevel: 230, 45 | generateMipmap: true, 46 | imageDecoder 47 | }); 48 | 49 | const resultBuffer = await readFile("./public/tests/DuckCM-etc1s.ktx2"); 50 | const testArray = Array.from(new Uint8Array(resultBuffer)); 51 | const resultArray = Array.from(result); 52 | expect(testArray).toEqual(resultArray); 53 | }); 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ktx2-encoder 2 | 3 | [![Latest NPM release](https://img.shields.io/npm/v/ktx2-encoder.svg)](https://www.npmjs.com/package/ktx2-encoder) 4 | [![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/gz65555/ktx2-encoder) 5 | 6 | A lightweight JavaScript library for converting images to KTX2 (.ktx2) format. Powered by [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal). 7 | 8 | ## Features 9 | 10 | - Convert images to KTX2 format 11 | - Support for both 2D images and cubemaps 12 | - Integration with gltf-transform 13 | - Support both Browser and Node.js 14 | 15 | ## Quick Start 16 | 17 | Install: 18 | 19 | ```shell 20 | npm install --save ktx2-encoder 21 | ``` 22 | 23 | Import: 24 | 25 | ```javascript 26 | import { encodeToKTX2, encodeKTX2Cube } from 'ktx2-encoder'; 27 | ``` 28 | 29 | Usage: 30 | 31 | ```javascript 32 | // encode a 2D image 33 | encodeToKTX2(data /** ArrayBuffer of png */, options); 34 | // encode a cube map 35 | encodeKTX2Cube([data, ...] /** ArrayBuffer of png */, options); 36 | ``` 37 | 38 | See [options](./docs/interfaces/IEncodeOptions.md) API documentation for more details. 39 | 40 | ## For gltf-transform 41 | 42 | For the users of [gltf-transform](https://gltf-transform.dev/), you can use the provided function `ktx`. For example: 43 | 44 | ```typescript 45 | import { ktx2 } from "ktx2-encoder/gltf-transform"; 46 | 47 | await document.transform( 48 | ktx2({ 49 | isUASTC: true, 50 | enableDebug: false, 51 | generateMipmap: true, 52 | wasmUrl: "/basis_encoder.wasm" 53 | }) 54 | ); 55 | 56 | ``` 57 | 58 | > **Note:** It's recommended to host the `basis_encoder.wasm` file on your own server. 59 | 60 | ## Tool 61 | 62 | Open the page of [ktx2 encoder tool](https://husong.me/ktx2-encoder/tools), you can use it to encode your image to ktx2. 63 | 64 | -------------------------------------------------------------------------------- /website/guide/advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced Usage 2 | 3 | ## Compression Formats 4 | 5 | ### ETC1S vs UASTC 6 | 7 | KTX2-Encoder supports two compression formats: 8 | 9 | - **ETC1S**: Better compression ratios, good for most textures 10 | - **UASTC**: Higher quality, better for normal maps and HDR textures 11 | 12 | Choose between them using the `isUASTC` option: 13 | 14 | ```typescript 15 | // Use ETC1S compression 16 | const ktx2Data = await encodeToKTX2(imageBuffer, { 17 | isUASTC: false 18 | }); 19 | 20 | // Use UASTC compression 21 | const ktx2Data = await encodeToKTX2(imageBuffer, { 22 | isUASTC: true 23 | }); 24 | ``` 25 | 26 | ### Quality Settings 27 | 28 | For ETC1S compression: 29 | 30 | ```typescript 31 | const options = { 32 | isUASTC: false, 33 | qualityLevel: 128, // Range: 1-255 34 | compressionLevel: 2 // Range: 0-6 35 | }; 36 | ``` 37 | 38 | For UASTC compression: 39 | 40 | ```typescript 41 | const options = { 42 | isUASTC: true, 43 | needSupercompression: true // Enable Zstandard supercompression 44 | }; 45 | ``` 46 | 47 | ## Node.js Usage 48 | 49 | When using in Node.js, you need to provide an image decoder: 50 | 51 | ```typescript 52 | import sharp from 'sharp'; 53 | 54 | const imageDecoder = async (buffer: Uint8Array) => { 55 | const { data, info } = await sharp(buffer) 56 | .raw() 57 | .ensureAlpha() 58 | .toBuffer({ resolveWithObject: true }); 59 | 60 | return { 61 | data: new Uint8Array(data), 62 | width: info.width, 63 | height: info.height 64 | }; 65 | }; 66 | 67 | const ktx2Data = await encodeToKTX2(pngBuffer, { 68 | imageDecoder 69 | }); 70 | ``` 71 | 72 | ## Custom KV Data 73 | 74 | You can include custom key-value metadata in the KTX2 file: 75 | 76 | ```typescript 77 | const ktx2Data = await encodeToKTX2(imageBuffer, { 78 | kvData: { 79 | 'myKey': 'myValue', 80 | 'customData': new Uint8Array([1, 2, 3]) 81 | } 82 | }); -------------------------------------------------------------------------------- /test/web.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { CubeBufferData, encodeToKTX2 } from "../src/web"; 3 | 4 | test("uastc", async () => { 5 | const buffer = await fetch("/tests/DuckCM.png").then((res) => res.arrayBuffer()); 6 | const result = await encodeToKTX2(new Uint8Array(buffer), { 7 | isUASTC: true, 8 | enableDebug: false, 9 | qualityLevel: 230, 10 | generateMipmap: true, 11 | wasmUrl: "/basis_encoder.wasm" 12 | }); 13 | 14 | const resultBuffer = await fetch("/tests/DuckCM-uastc.ktx2").then((res) => res.arrayBuffer()); 15 | expect(result).toEqual(new Uint8Array(resultBuffer)); 16 | }); 17 | 18 | test("etc1s", async () => { 19 | const buffer = await fetch("/tests/DuckCM.png").then((res) => res.arrayBuffer()); 20 | const result = await encodeToKTX2(new Uint8Array(buffer), { 21 | isUASTC: false, 22 | enableDebug: false, 23 | qualityLevel: 230, 24 | generateMipmap: true 25 | }); 26 | 27 | const resultBuffer = await fetch("/tests/DuckCM-etc1s.ktx2").then((res) => res.arrayBuffer()); 28 | expect(result).toEqual(new Uint8Array(resultBuffer)); 29 | }); 30 | 31 | test("textureCube", async () => { 32 | await Promise.all([ 33 | fetch("/tests/cubemap/posx.jpg").then((res) => res.arrayBuffer()), 34 | fetch("/tests/cubemap/negx.jpg").then((res) => res.arrayBuffer()), 35 | fetch("/tests/cubemap/posy.jpg").then((res) => res.arrayBuffer()), 36 | fetch("/tests/cubemap/negy.jpg").then((res) => res.arrayBuffer()), 37 | fetch("/tests/cubemap/posz.jpg").then((res) => res.arrayBuffer()), 38 | fetch("/tests/cubemap/negz.jpg").then((res) => res.arrayBuffer()) 39 | ]).then(async (buffers) => { 40 | const result = await encodeToKTX2(buffers.map((buffer) => new Uint8Array(buffer)) as CubeBufferData, { 41 | isUASTC: false, 42 | enableDebug: false, 43 | qualityLevel: 230, 44 | generateMipmap: false 45 | }); 46 | // TODO: check the result 47 | expect(result).toBeDefined(); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /src/applyInputOptions.ts: -------------------------------------------------------------------------------- 1 | import { IEncodeOptions, IBasisEncoder } from "./type.js"; 2 | import { DefaultOptions } from "./utils.js"; 3 | 4 | export function applyInputOptions(options: Partial = {}, encoder: IBasisEncoder) { 5 | options = { ...DefaultOptions, ...options }; 6 | // basic 7 | options.enableDebug !== undefined && encoder.setDebug(options.enableDebug); 8 | options.isUASTC !== undefined && encoder.setUASTC(options.isUASTC); 9 | options.isKTX2File !== undefined && encoder.setCreateKTX2File(options.isKTX2File); 10 | // extra 11 | options.isSetKTX2SRGBTransferFunc !== undefined && encoder.setKTX2SRGBTransferFunc(options.isSetKTX2SRGBTransferFunc); 12 | options.generateMipmap !== undefined && encoder.setMipGen(options.generateMipmap); 13 | options.isYFlip !== undefined && encoder.setYFlip(options.isYFlip); 14 | options.isNormalMap === true && encoder.setNormalMap(); 15 | // etc1s 16 | options.qualityLevel !== undefined && encoder.setQualityLevel(options.qualityLevel); 17 | options.compressionLevel !== undefined && encoder.setCompressionLevel(options.compressionLevel); 18 | // uastc 19 | options.needSupercompression !== undefined && encoder.setKTX2UASTCSupercompression(options.needSupercompression); 20 | options.enableRDO && encoder.setRDOUASTC(true); 21 | options.rdoQualityLevel !== undefined && encoder.setRDOUASTCQualityScalar(options.rdoQualityLevel); 22 | options.uastcLDRQualityLevel !== undefined && encoder.setPackUASTCFlags(options.uastcLDRQualityLevel); 23 | // hdr, only set HDR, if hdr is true, otherwise the format mode will be etc1s 24 | /** see {@link https://github.com/BinomialLLC/basis_universal/blob/1172d07395a890c782c4f2ef09d2f08606c3f743/webgl/transcoder/basis_wrappers.cpp#L2307-L2321} */ 25 | if (options.isHDR) { 26 | encoder.setHDR(options.isHDR); 27 | options.hdrQualityLevel && encoder.setUASTCHDRQualityLevel(options.hdrQualityLevel); 28 | } 29 | options.isPerceptual !== undefined && encoder.setPerceptual(options.isPerceptual); 30 | } 31 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | **ktx2-encoder** 2 | 3 | *** 4 | 5 | # ktx2-encoder 6 | 7 | [![Latest NPM release](https://img.shields.io/npm/v/ktx2-encoder.svg)](https://www.npmjs.com/package/ktx2-encoder) 8 | [![License](https://img.shields.io/badge/license-MIT-007ec6.svg)](https://github.com/gz65555/ktx2-encoder) 9 | 10 | A lightweight JavaScript library for converting images to KTX2 (.ktx2) format. Powered by [BinomialLLC/basis_universal](https://github.com/BinomialLLC/basis_universal). 11 | 12 | ## Features 13 | 14 | - Convert images to KTX2 format 15 | - Support for both 2D images and cubemaps 16 | - Integration with gltf-transform 17 | - Support both Browser and Node.js 18 | 19 | ## Quick Start 20 | 21 | Install: 22 | 23 | ```shell 24 | npm install --save ktx2-encoder 25 | ``` 26 | 27 | Import: 28 | 29 | ```javascript 30 | import { encodeToKTX2, encodeKTX2Cube } from 'ktx2-encoder'; 31 | ``` 32 | 33 | Usage: 34 | 35 | ```javascript 36 | // encode a 2D image 37 | encodeToKTX2(data /** ArrayBuffer of png */, options); 38 | // encode a cube map 39 | encodeKTX2Cube([data, ...] /** ArrayBuffer of png */, options); 40 | ``` 41 | 42 | See [options](_media/IEncodeOptions.md) API documentation for more details. 43 | 44 | ## For gltf-transform 45 | 46 | For the users of [gltf-transform](https://gltf-transform.dev/), you can use the provided function `ktx`. For example: 47 | 48 | ```typescript 49 | import { ktx2 } from "ktx2-encoder/gltf-transform"; 50 | 51 | await document.transform( 52 | ktx2({ 53 | isUASTC: true, 54 | enableDebug: false, 55 | generateMipmap: true, 56 | wasmUrl: "/basis_encoder.wasm" 57 | }) 58 | ); 59 | 60 | ``` 61 | 62 | > **Note:** It's recommended to host the `basis_encoder.wasm` file on your own server. 63 | 64 | ## Tool 65 | 66 | 1. Start server 67 | 68 | ``` 69 | npm run dev 70 | ``` 71 | 72 | 2. Open the page,default is http://localhost:5174/ 73 | 74 | 3. Use it! Select your images and encode it to ktx2![lock](https://mdn.alipayobjects.com/rms/afts/img/A*himnRpVKEvgAAAAAAAAAAAAAARQnAQ/original/lock.gif) 75 | -------------------------------------------------------------------------------- /src/node/NodeBasisEncoder.ts: -------------------------------------------------------------------------------- 1 | import { HDRSourceType, SourceType } from "../enum.js"; 2 | import { CubeBufferData, IBasisModule, IEncodeOptions } from "../type.js"; 3 | import { applyInputOptions } from "../applyInputOptions.js"; 4 | import BASIS from "../basis/basis_encoder.js"; 5 | 6 | let promise: Promise | null = null; 7 | 8 | class NodeBasisEncoder { 9 | async init(): Promise { 10 | if (!promise) { 11 | promise = BASIS().then((basis: IBasisModule) => { 12 | basis.initializeBasis(); 13 | return basis; 14 | }); 15 | } 16 | return promise as Promise; 17 | } 18 | 19 | async encode(bufferOrBufferArray: Uint8Array | CubeBufferData, options: Partial = {}) { 20 | const basis = await this.init(); 21 | const encoder = new basis.BasisEncoder(); 22 | 23 | const bufferArray = Array.isArray(bufferOrBufferArray) ? bufferOrBufferArray : [bufferOrBufferArray]; 24 | applyInputOptions(options, encoder); 25 | 26 | for (let i = 0; i < bufferArray.length; i++) { 27 | const buffer = bufferArray[i]; 28 | if (options.isHDR) { 29 | encoder.setSliceSourceImageHDR( 30 | i, 31 | buffer, 32 | 0, 33 | 0, 34 | options.imageType === "hdr" ? HDRSourceType.HDR : HDRSourceType.EXR, 35 | true 36 | ); 37 | } else { 38 | const imageData = await options.imageDecoder!(buffer); 39 | encoder.setSliceSourceImage( 40 | i, 41 | new Uint8Array(imageData.data), 42 | imageData.width, 43 | imageData.height, 44 | SourceType.RAW 45 | ); 46 | } 47 | } 48 | 49 | const resultData = new Uint8Array(1024 * 1024 * (options.isHDR ? 24 : 10)); 50 | const resultSize = encoder.encode(resultData); 51 | if (resultSize === 0) { 52 | throw new Error("Encode failed"); 53 | } 54 | return Buffer.from(resultData.subarray(0, resultSize)); 55 | } 56 | } 57 | 58 | export const nodeEncoder = new NodeBasisEncoder(); 59 | -------------------------------------------------------------------------------- /test/gltf-transform.web.test.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "@gltf-transform/core"; 2 | import { ktx2 } from "../src/gltf-transform"; 3 | import { expect, test, describe } from "vitest"; 4 | 5 | describe("ktx2 transform browser", () => { 6 | test("converts png to ktx2", async () => { 7 | const document = new Document(); 8 | const imageResponse = await fetch("/tests/DuckCM.png"); 9 | const imageBuffer = await imageResponse.arrayBuffer(); 10 | 11 | const texture = document.createTexture("test") 12 | .setImage(new Uint8Array(imageBuffer)) 13 | .setMimeType("image/png"); 14 | 15 | await document.transform( 16 | ktx2({ 17 | isUASTC: true, 18 | enableDebug: false, 19 | qualityLevel: 230, 20 | generateMipmap: true, 21 | wasmUrl: "/basis_encoder.wasm" 22 | }) 23 | ); 24 | 25 | expect(texture.getMimeType()).toBe("image/ktx2"); 26 | 27 | const ktx2Data = texture.getImage()!; 28 | const expectedResponse = await fetch("/tests/DuckCM-uastc.ktx2"); 29 | const expectedBuffer = new Uint8Array(await expectedResponse.arrayBuffer()); 30 | expect(ktx2Data).toEqual(expectedBuffer); 31 | }); 32 | 33 | test("filters textures by pattern", async () => { 34 | const document = new Document(); 35 | const imageResponse = await fetch("/tests/DuckCM.png"); 36 | const imageBuffer = await imageResponse.arrayBuffer(); 37 | 38 | const texture1 = document.createTexture("color") 39 | .setImage(new Uint8Array(imageBuffer)) 40 | .setMimeType("image/png") 41 | .setURI("color.png"); 42 | 43 | 44 | const texture2 = document.createTexture("normal") 45 | .setImage(new Uint8Array(imageBuffer)) 46 | .setMimeType("image/png") 47 | .setURI("normal.png"); 48 | 49 | await document.transform( 50 | ktx2({ 51 | pattern: /^color/, 52 | isUASTC: true, 53 | wasmUrl: "/basis_encoder.wasm" 54 | }) 55 | ); 56 | 57 | expect(texture1.getMimeType()).toBe("image/ktx2"); 58 | expect(texture2.getMimeType()).toBe("image/png"); 59 | }); 60 | }); -------------------------------------------------------------------------------- /website/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vitepress"; 2 | 3 | const fileAndStyles: Record = {}; 4 | 5 | export default defineConfig({ 6 | title: "KTX2-Encoder", 7 | description: "A lightweight JavaScript library for converting images to KTX2 format", 8 | base: "/ktx2-encoder/", 9 | head: [["link", { rel: "icon", href: "/favicon.ico" }]], 10 | themeConfig: { 11 | // https://vitepress.dev/reference/default-theme-config 12 | nav: [ 13 | { text: "Home", link: "/" }, 14 | { text: "Guide", link: "/guide/" }, 15 | { text: "Tools", link: "/tools/" } 16 | ], 17 | 18 | sidebar: [ 19 | { 20 | text: "Guide", 21 | items: [ 22 | { text: "Getting Started", link: "/guide/" }, 23 | { text: "glTF Transform", link: "/guide/gltf-transform" }, 24 | { text: "API", link: "/guide/api" } 25 | ] 26 | }, 27 | { 28 | text: "Tools", 29 | items: [ 30 | { text: "KTX2 Encoder", link: "/tools/" }, 31 | { text: "KTX2 Viewer", link: "/tools/viewer" } 32 | ] 33 | } 34 | ], 35 | 36 | socialLinks: [{ icon: "github", link: "https://github.com/gz65555/ktx2-encoder" }] 37 | }, 38 | markdown: { 39 | toc: { level: [2, 3, 4] } 40 | }, 41 | // native ui 42 | vite: { 43 | ssr: { 44 | noExternal: ["naive-ui", "date-fns", "vueuc"] 45 | } 46 | }, 47 | postRender(context) { 48 | const styleRegex = /((.|\s)+)<\/css-render-style>/; 49 | const vitepressPathRegex = /(.+)<\/vitepress-path>/; 50 | const style = styleRegex.exec(context.content)?.[1]; 51 | const vitepressPath = vitepressPathRegex.exec(context.content)?.[1]; 52 | if (vitepressPath && style) { 53 | fileAndStyles[vitepressPath] = style; 54 | } 55 | context.content = context.content.replace(styleRegex, ""); 56 | context.content = context.content.replace(vitepressPathRegex, ""); 57 | }, 58 | transformHtml(code, id) { 59 | const html = id.split("/").pop(); 60 | if (!html) return; 61 | const style = fileAndStyles[`/${html}`]; 62 | if (style) { 63 | return code.replace(/<\/head>/, `${style}`); 64 | } 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /test/gltf-transform.node.test.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "@gltf-transform/core"; 2 | import { ktx2 } from "../src/gltf-transform"; 3 | import { expect, test, describe } from "vitest"; 4 | import { readFile } from "fs/promises"; 5 | import sharp from "sharp"; 6 | 7 | async function imageDecoder(buffer: Uint8Array) { 8 | const image = sharp(buffer); 9 | const metadata = await image.metadata(); 10 | const { width, height } = metadata; 11 | const rawBuffer = await image.ensureAlpha().raw().toBuffer(); 12 | const data = new Uint8Array(rawBuffer); 13 | 14 | return { width: width!, height: height!, data }; 15 | } 16 | 17 | describe("ktx2 transform node", () => { 18 | test("converts png to ktx2", async () => { 19 | const document = new Document(); 20 | const texture = document.createTexture("test") 21 | .setImage(await readFile("./public/tests/DuckCM.png")) 22 | .setMimeType("image/png"); 23 | 24 | await document.transform( 25 | ktx2({ 26 | isUASTC: true, 27 | enableDebug: false, 28 | qualityLevel: 230, 29 | generateMipmap: true, 30 | imageDecoder 31 | }) 32 | ); 33 | 34 | expect(texture.getMimeType()).toBe("image/ktx2"); 35 | 36 | const ktx2Data = Array.from(texture.getImage()!); 37 | const expectedData = Array.from(new Uint8Array(await readFile("./public/tests/DuckCM-uastc.ktx2"))); 38 | expect(ktx2Data).toEqual(expectedData); 39 | }); 40 | 41 | test("filters textures by pattern", async () => { 42 | const document = new Document(); 43 | const imageBuffer = await readFile("./public/tests/DuckCM.png"); 44 | 45 | const texture1 = document.createTexture("color") 46 | .setImage(imageBuffer) 47 | .setMimeType("image/png") 48 | .setURI("color.png"); 49 | 50 | const texture2 = document.createTexture("normal") 51 | .setImage(imageBuffer) 52 | .setMimeType("image/png") 53 | .setURI("normal.png"); 54 | 55 | await document.transform( 56 | ktx2({ 57 | pattern: /^color/, 58 | isUASTC: true, 59 | imageDecoder 60 | }) 61 | ); 62 | 63 | expect(texture1.getMimeType()).toBe("image/ktx2"); 64 | expect(texture2.getMimeType()).toBe("image/png"); 65 | }); 66 | }); -------------------------------------------------------------------------------- /.github/workflows/website: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy ktx2-encoder site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [website] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | - uses: pnpm/action-setup@v3 # Uncomment this block if you're using pnpm 36 | # with: 37 | # version: 9 # Not needed if you've set "packageManager" in package.json 38 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 39 | - name: Setup Node 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: 20 43 | cache: pnpm # or pnpm / yarn 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v4 46 | - name: Install dependencies 47 | run: pnpm install # or pnpm install / yarn install / bun install 48 | - name: Build with VitePress 49 | run: pnpm docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build 50 | - name: Upload artifact 51 | uses: actions/upload-pages-artifact@v3 52 | with: 53 | path: website/.vitepress/dist 54 | 55 | # Deployment job 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | needs: build 61 | runs-on: ubuntu-latest 62 | name: Deploy 63 | steps: 64 | - name: Deploy to GitHub Pages 65 | id: deployment 66 | uses: actions/deploy-pages@v4 67 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a VitePress site to GitHub Pages 2 | # 3 | name: Deploy ktx2-encoder site to Pages 4 | 5 | on: 6 | # Runs on pushes targeting the `main` branch. Change this to `master` if you're 7 | # using the `master` branch as the default branch. 8 | push: 9 | branches: [main] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 22 | concurrency: 23 | group: pages 24 | cancel-in-progress: false 25 | 26 | jobs: 27 | # Build job 28 | build: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 35 | - uses: pnpm/action-setup@v3 # Uncomment this block if you're using pnpm 36 | # with: 37 | # version: 9 # Not needed if you've set "packageManager" in package.json 38 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 39 | - name: Setup Node 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: 20 43 | cache: pnpm # or pnpm / yarn 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v4 46 | - name: Install dependencies 47 | run: pnpm install # or pnpm install / yarn install / bun install 48 | - name: Build with VitePress 49 | run: pnpm docs:build # or pnpm docs:build / yarn docs:build / bun run docs:build 50 | - name: List Basis 51 | run: ls website/.vitepress/dist/basis 52 | - name: Upload artifact 53 | uses: actions/upload-pages-artifact@v3 54 | with: 55 | path: website/.vitepress/dist 56 | 57 | # Deployment job 58 | deploy: 59 | environment: 60 | name: github-pages 61 | url: ${{ steps.deployment.outputs.page_url }} 62 | needs: build 63 | runs-on: ubuntu-latest 64 | name: Deploy 65 | steps: 66 | - name: Deploy to GitHub Pages 67 | id: deployment 68 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.5.1", 3 | "name": "ktx2-encoder", 4 | "description": "KTX2(.ktx2) encoder for browser applications", 5 | "type": "module", 6 | "packageManager": "pnpm@8.15.9", 7 | "exports": { 8 | ".": { 9 | "node": { 10 | "import": "./dist/node/index.js", 11 | "types": "./types/node/index.d.ts" 12 | }, 13 | "default": { 14 | "import": "./dist/web/index.js", 15 | "types": "./types/web/index.d.ts" 16 | } 17 | }, 18 | "./gltf-transform": { 19 | "import": "./dist/gltf-transform/index.js", 20 | "types": "./types/gltf-transform/index.d.ts" 21 | } 22 | }, 23 | "repository": "github:gz65555/ktx2-encoder", 24 | "author": { 25 | "name": "Hu Song", 26 | "email": "gz65555@gmail.com" 27 | }, 28 | "license": "MIT", 29 | "scripts": { 30 | "docs": "typedoc ./src/web/index.ts --plugin typedoc-plugin-markdown --out ./docs", 31 | "watch": "tsc -p tsconfig.json --watch", 32 | "build": "tsc -p tsconfig.json && cp -R src/basis dist", 33 | "test": "npm run test:node && npm run test:web", 34 | "test:node": "vitest run ./test/node.test.ts", 35 | "test:web": "vitest run --browser.name=chrome ./test/web.test.ts", 36 | "test:gltf": "vitest run ./test/gltf-transform.node.test.ts && vitest run --browser.name=chrome ./test/gltf-transform.web.test.ts", 37 | "release": "bumpp", 38 | "docs:dev": "vitepress dev website", 39 | "docs:build": "cp -r src/basis website/public && vitepress build website", 40 | "docs:preview": "vitepress preview website" 41 | }, 42 | "keywords": [ 43 | "ktx", 44 | "ktx2", 45 | "basis", 46 | "texture", 47 | "encoder", 48 | "webgl", 49 | "webgpu" 50 | ], 51 | "files": [ 52 | "types", 53 | "dist" 54 | ], 55 | "devDependencies": { 56 | "@css-render/vue3-ssr": "^0.15.14", 57 | "@galacean/engine": "^1.4.5", 58 | "@gltf-transform/core": "^4.1.1", 59 | "@gltf-transform/extensions": "^4.1.1", 60 | "@types/node": "^20.17.16", 61 | "@types/react": "^18.3.18", 62 | "@types/react-dom": "^18.3.5", 63 | "@types/three": "^0.172.0", 64 | "@vitejs/plugin-react": "^4.3.4", 65 | "@vitest/browser": "^2.1.8", 66 | "@webgpu/types": "^0.1.53", 67 | "bumpp": "^9.11.1", 68 | "naive-ui": "^2.41.0", 69 | "prettier": "^2.8.8", 70 | "sharp": "^0.33.5", 71 | "three": "^0.172.0", 72 | "typedoc": "^0.27.6", 73 | "typedoc-plugin-markdown": "^4.4.1", 74 | "typescript": "~5.6.3", 75 | "vite": "^5.4.14", 76 | "vitepress": "^1.6.3", 77 | "vitest": "^2.1.8", 78 | "vue": "^3.5.13", 79 | "webdriverio": "^9.7.1" 80 | }, 81 | "dependencies": { 82 | "ktx-parse": "^0.7.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/web/BrowserBasisEncoder.ts: -------------------------------------------------------------------------------- 1 | import { read, write } from "ktx-parse"; 2 | import { CubeBufferData, IBasisModule, IEncodeOptions } from "../type.js"; 3 | import { applyInputOptions } from "../applyInputOptions.js"; 4 | import { BasisTextureType, HDRSourceType, SourceType } from "../enum.js"; 5 | 6 | let promise: Promise | null = null; 7 | 8 | const DEFAULT_WASM_URL = 9 | "https://mdn.alipayobjects.com/rms/afts/file/A*r7D4SKbksYcAAAAAAAAAAAAAARQnAQ/basis_encoder.wasm"; 10 | 11 | class BrowserBasisEncoder { 12 | async init(options?: { jsUrl?: string; wasmUrl?: string }) { 13 | if (!promise) { 14 | function _init(): Promise { 15 | const wasmUrl = options?.wasmUrl ?? DEFAULT_WASM_URL; 16 | const jsUrl = options?.jsUrl ?? "../basis/basis_encoder.js"; 17 | return new Promise((resolve, reject) => { 18 | Promise.all([ 19 | import(/* @vite-ignore */ jsUrl), 20 | wasmUrl ? fetch(wasmUrl).then((res) => res.arrayBuffer()) : undefined 21 | ]) 22 | .then(([{ default: BASIS }, wasmBinary]) => { 23 | return BASIS({ wasmBinary }).then((Module: IBasisModule) => { 24 | Module.initializeBasis(); 25 | resolve(Module); 26 | }); 27 | }) 28 | .catch(reject); 29 | }); 30 | } 31 | promise = _init(); 32 | } 33 | return promise; 34 | } 35 | 36 | /** 37 | * encode image data to ktx2 file data 38 | * @param bufferOrBufferArray - image data, can be a single image or an array of images 39 | * if it's an array, the images will be encoded as a cube map, the order of the images is: 40 | * 0: Positive X face 41 | * 1: Negative X face 42 | * 2: Positive Y face 43 | * 3: Negative Y face 44 | * 4: Positive Z face 45 | * 5: Negative Z face 46 | * @param options - encode options, see {@link IEncodeOptions} 47 | * @returns ktx2 file data 48 | */ 49 | async encode( 50 | bufferOrBufferArray: Uint8Array | CubeBufferData, 51 | options: Partial = {} 52 | ): Promise { 53 | const basisModule = await this.init(options); 54 | const encoder = new basisModule.BasisEncoder(); 55 | applyInputOptions(options, encoder); 56 | const isCube = Array.isArray(bufferOrBufferArray) && bufferOrBufferArray.length === 6; 57 | encoder.setTexType( 58 | isCube ? BasisTextureType.cBASISTexTypeCubemapArray : BasisTextureType.cBASISTexType2D 59 | ); 60 | 61 | const bufferArray = Array.isArray(bufferOrBufferArray) ? bufferOrBufferArray : [bufferOrBufferArray]; 62 | 63 | for (let i = 0; i < bufferArray.length; i++) { 64 | const buffer = bufferArray[i]; 65 | if (options.isHDR) { 66 | encoder.setSliceSourceImageHDR( 67 | i, 68 | buffer, 69 | 0, 70 | 0, 71 | options.imageType === "hdr" ? HDRSourceType.HDR : HDRSourceType.EXR, 72 | true 73 | ); 74 | } else { 75 | const imageData = await options.imageDecoder!(buffer); 76 | encoder.setSliceSourceImage( 77 | i, 78 | new Uint8Array(imageData.data), 79 | imageData.width, 80 | imageData.height, 81 | SourceType.RAW 82 | ); 83 | } 84 | } 85 | 86 | const ktx2FileData = new Uint8Array(1024 * 1024 * (options.isHDR ? 24 : 10)); 87 | const byteLength = encoder.encode(ktx2FileData); 88 | if (byteLength === 0) { 89 | throw new Error("Encode failed"); 90 | } 91 | let actualKTX2FileData = new Uint8Array(ktx2FileData.buffer, 0, byteLength); 92 | if (options.kvData) { 93 | const container = read(ktx2FileData); 94 | for (let k in options.kvData) { 95 | container.keyValue[k] = options.kvData[k]; 96 | } 97 | actualKTX2FileData = write(container, { keepWriter: true }); 98 | } 99 | return actualKTX2FileData; 100 | } 101 | } 102 | 103 | export const browserEncoder = new BrowserBasisEncoder(); 104 | -------------------------------------------------------------------------------- /src/gltf-transform/ktx2.ts: -------------------------------------------------------------------------------- 1 | import { Document, Transform, Texture } from "@gltf-transform/core"; 2 | import { KHRTextureBasisu } from "@gltf-transform/extensions" 3 | import { IEncodeOptions } from "../type.js"; 4 | 5 | const NAME = "ktx2"; 6 | const SUPPORTED_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"]; 7 | 8 | function listTextureSlots(texture: Texture): string[] { 9 | const document = Document.fromGraph(texture.getGraph())!; 10 | const root = document.getRoot(); 11 | const slots = texture 12 | .getGraph() 13 | .listParentEdges(texture) 14 | .filter((edge) => edge.getParent() !== root) 15 | .map((edge) => edge.getName()); 16 | return Array.from(new Set(slots)); 17 | } 18 | 19 | function createTransform(name: string, fn: Transform): Transform { 20 | Object.defineProperty(fn, "name", { value: name }); 21 | return fn; 22 | } 23 | 24 | export type KTX2Options = IEncodeOptions & { 25 | /** Pattern identifying textures to compress, matched to name or URI */ 26 | pattern?: RegExp | null; 27 | /** Pattern matching the material texture slots to be compressed */ 28 | slots?: RegExp | null; 29 | } 30 | /** 31 | * Transforms compatible textures in a glTF asset to KTX2 format. 32 | * @param options KTX2 compression options 33 | * @returns Transform 34 | */ 35 | export function ktx2(options: Partial = {}): Transform { 36 | // Merge defaults with provided options 37 | const patternRe = options.pattern; 38 | const slotsRe = options.slots; 39 | 40 | return createTransform(NAME, async (document: Document): Promise => { 41 | // Dynamically import the appropriate encoder 42 | const { encodeToKTX2 } = typeof window !== "undefined" 43 | ? await import("../web/index.js") 44 | : await import("../node/index.js"); 45 | 46 | const logger = document.getLogger(); 47 | const textures = document.getRoot().listTextures(); 48 | 49 | let isKHRTextureBasisu = false; 50 | 51 | await Promise.all( 52 | textures.map(async (texture, textureIndex) => { 53 | const textureLabel = texture.getURI() || texture.getName() || `${textureIndex + 1}/${textures.length}`; 54 | const prefix = `${NAME}(${textureLabel})`; 55 | const slots = listTextureSlots(texture); 56 | 57 | // Skip textures that are already KTX2 58 | if (texture.getMimeType() === "image/ktx2") { 59 | logger.debug(`${prefix}: Skipping, already KTX2`); 60 | return; 61 | } 62 | 63 | // Skip unsupported mime types 64 | if (!SUPPORTED_MIME_TYPES.includes(texture.getMimeType())) { 65 | logger.debug(`${prefix}: Skipping, unsupported texture type "${texture.getMimeType()}"`); 66 | return; 67 | } 68 | 69 | // Skip textures that don't match pattern 70 | if (patternRe && !patternRe.test(texture.getName()) && !patternRe.test(texture.getURI())) { 71 | logger.debug(`${prefix}: Skipping, excluded by "pattern" parameter`); 72 | return; 73 | } 74 | 75 | // Skip textures that don't match slots 76 | if (slotsRe && slots.length && !slots.some((slot) => slotsRe.test(slot))) { 77 | logger.debug(`${prefix}: Skipping, [${slots.join(", ")}] excluded by "slots" parameter`); 78 | return; 79 | } 80 | 81 | try { 82 | const image = texture.getImage(); 83 | if (!image) { 84 | logger.warn(`${prefix}: Skipping, no image data`); 85 | return; 86 | } 87 | 88 | const srcByteLength = image.byteLength; 89 | 90 | // Encode to KTX2 91 | const ktx2Data = await encodeToKTX2(image, { 92 | ...options, 93 | }); 94 | // Update texture with new KTX2 data 95 | texture.setImage(ktx2Data); 96 | texture.setMimeType("image/ktx2"); 97 | 98 | isKHRTextureBasisu = true; 99 | 100 | const dstByteLength = ktx2Data.byteLength; 101 | logger.debug(`${prefix}: Size = ${srcByteLength} → ${dstByteLength} bytes`); 102 | } catch (error) { 103 | logger.warn(`${prefix}: Failed to convert texture: ${error.message}`); 104 | console.log(error); 105 | } 106 | }) 107 | ); 108 | 109 | logger.debug(`${NAME}: Complete.`); 110 | 111 | if (isKHRTextureBasisu) { 112 | document.createExtension(KHRTextureBasisu).setRequired(true); 113 | } 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /website/guide/api.md: -------------------------------------------------------------------------------- 1 | # API Reference 2 | 3 | ## Core Functions 4 | 5 | ### encodeToKTX2 6 | 7 | The main function for converting images to KTX2 format. 8 | 9 | ```typescript 10 | async function encodeToKTX2( 11 | imageBuffer: Uint8Array | Uint8Array[], 12 | options?: Partial 13 | ): Promise; 14 | ``` 15 | 16 | #### Parameters 17 | 18 | - `imageBuffer`: Raw image data as Uint8Array / Array of exactly 6 image buffers for each cubemap face in the order: `[posx, negx, posy, negy, posz, negz]` 19 | - `options`: Optional configuration object (see IEncodeOptions below) 20 | 21 | #### Example 22 | 23 | ```typescript 24 | import { encodeToKTX2 } from "ktx2-encoder"; 25 | 26 | const ktx2Data = await encodeToKTX2(imageBuffer, { 27 | isUASTC: true, 28 | generateMipmap: true, 29 | isNormalMap: false 30 | }); 31 | ``` 32 | 33 | ## Configuration Options 34 | 35 | ### IEncodeOptions Interface 36 | 37 | Complete configuration options for the encoder: 38 | 39 | | Option | Type | Default | Description | 40 | | --- | --- | --- | --- | 41 | | `isUASTC` | boolean | true | Use UASTC texture format instead of ETC1S | 42 | | `enableDebug` | boolean | false | Enable debug output | 43 | | `isYFlip` | boolean | false | If true, the source images will be Y flipped before compression | 44 | | `qualityLevel` | number | -1(unused) | Sets the ETC1S encoder's quality level (1-255). Controls file size vs. quality tradeoff | 45 | | `compressionLevel` | number | 2 | Controls encoder performance vs. file size tradeoff for ETC1S files (0-6) | 46 | | `needSupercompression` | boolean | false | Use UASTC Zstandard supercompression | 47 | | `isNormalMap` | boolean | false | Optimize compression parameters for normal maps | 48 | | `isSetKTX2SRGBTransferFunc` | boolean | false | Input source is sRGB. Should match the "perceptual" setting | 49 | | `generateMipmap` | boolean | false | Generate mipmaps from source images | 50 | | `isKTX2File` | boolean | false | Create .KTX2 files instead of .basis files | 51 | | `kvData` | Record | {} | Custom key-value metadata for the KTX2 file | 52 | | `type` | SourceType | - | Type of input source (RAW = 0, PNG = 1) | 53 | | `imageDecoder` | Function | undefined | Function to decode compressed image buffer to RGBA (Required for Node.js) | 54 | | `jsUrl` | string | undefined | URL to the JavaScript module | 55 | | `wasmUrl` | string | undefined | URL to the WebAssembly module | 56 | 57 | ### Image Decoder Function Type 58 | 59 | For Node.js usage, the `imageDecoder` function should match this signature: 60 | 61 | ```typescript 62 | type ImageDecoder = (buffer: Uint8Array) => Promise<{ 63 | width: number; 64 | height: number; 65 | data: Uint8Array; 66 | }>; 67 | ``` 68 | 69 | ### Source Type Enum 70 | 71 | ```typescript 72 | enum SourceType { 73 | RAW = 0, 74 | PNG = 1 75 | } 76 | ``` 77 | 78 | ## Examples 79 | 80 | ### Basic Usage 81 | 82 | ```typescript 83 | const ktx2Data = await encodeToKTX2(imageBuffer, { 84 | isUASTC: true, 85 | generateMipmap: true, 86 | isNormalMap: false 87 | }); 88 | ``` 89 | 90 | ### Normal Map Compression 91 | 92 | ```typescript 93 | const normalMapData = await encodeToKTX2(normalMapBuffer, { 94 | isUASTC: true, // Recommended for normal maps 95 | isNormalMap: true, 96 | isSetKTX2SRGBTransferFunc: false // Important for normal maps 97 | }); 98 | ``` 99 | 100 | ### ETC1S with Quality Settings 101 | 102 | ```typescript 103 | const etc1sData = await encodeToKTX2(imageBuffer, { 104 | isUASTC: false, 105 | qualityLevel: 128, 106 | compressionLevel: 2 107 | }); 108 | ``` 109 | 110 | ### Cubemap Encoding 111 | 112 | ```typescript 113 | import { encode } from "ktx2-encoder"; 114 | 115 | // Load your 6 cubemap face images 116 | Promise.all([ 117 | loadImage("posx.jpg"), 118 | loadImage("negx.jpg"), 119 | loadImage("posy.jpg"), 120 | loadImage("negy.jpg"), 121 | loadImage("posz.jpg"), 122 | loadImage("negz.jpg") 123 | ]).then((buffers) => { 124 | // Encode to KTX2 cubemap 125 | return encode(buffers, { 126 | isUASTC: true, 127 | generateMipmap: true, 128 | qualityLevel: 128 129 | }); 130 | }); 131 | ``` 132 | 133 | #### Face Order 134 | 135 | The cubemap faces must be provided in this specific order: 136 | 137 | - `posx`: Positive X face (+X) 138 | - `negx`: Negative X face (-X) 139 | - `posy`: Positive Y face (+Y) 140 | - `negy`: Negative Y face (-Y) 141 | - `posz`: Positive Z face (+Z) 142 | - `negz`: Negative Z face (-Z) 143 | 144 | ### With Custom Metadata 145 | 146 | ```typescript 147 | const ktx2Data = await encodeToKTX2(imageBuffer, { 148 | kvData: { 149 | myKey: "myValue", 150 | customData: new Uint8Array([1, 2, 3]) 151 | } 152 | }); 153 | ``` 154 | -------------------------------------------------------------------------------- /docs/_media/IEncodeOptions.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / IEncodeOptions 6 | 7 | # Interface: IEncodeOptions 8 | 9 | Defined in: [type.ts:129](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L129) 10 | 11 | ## Properties 12 | 13 | ### compressionLevel 14 | 15 | > **compressionLevel**: `number` 16 | 17 | Defined in: [type.ts:149](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L149) 18 | 19 | The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files. 20 | 21 | *** 22 | 23 | ### enableDebug 24 | 25 | > **enableDebug**: `boolean` 26 | 27 | Defined in: [type.ts:133](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L133) 28 | 29 | enable debug output, default is false 30 | 31 | *** 32 | 33 | ### generateMipmap 34 | 35 | > **generateMipmap**: `boolean` 36 | 37 | Defined in: [type.ts:165](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L165) 38 | 39 | If true mipmaps will be generated from the source images 40 | 41 | *** 42 | 43 | ### imageDecoder()? 44 | 45 | > `optional` **imageDecoder**: (`buffer`) => `Promise`\<\{ `data`: `Uint8Array`; `height`: `number`; `width`: `number`; \}\> 46 | 47 | Defined in: [type.ts:181](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L181) 48 | 49 | Decode compressed image buffer to RGBA imageData.(Required in Node.js) 50 | 51 | #### Parameters 52 | 53 | ##### buffer 54 | 55 | `Uint8Array` 56 | 57 | #### Returns 58 | 59 | `Promise`\<\{ `data`: `Uint8Array`; `height`: `number`; `width`: `number`; \}\> 60 | 61 | *** 62 | 63 | ### isKTX2File 64 | 65 | > **isKTX2File**: `boolean` 66 | 67 | Defined in: [type.ts:169](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L169) 68 | 69 | Create .KTX2 files instead of .basis files. By default this is FALSE. 70 | 71 | *** 72 | 73 | ### isNormalMap 74 | 75 | > **isNormalMap**: `boolean` 76 | 77 | Defined in: [type.ts:157](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L157) 78 | 79 | setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps. 80 | 81 | *** 82 | 83 | ### isSetKTX2SRGBTransferFunc 84 | 85 | > **isSetKTX2SRGBTransferFunc**: `boolean` 86 | 87 | Defined in: [type.ts:161](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L161) 88 | 89 | Input source is sRGB. This should very probably match the "perceptual" setting. 90 | 91 | *** 92 | 93 | ### isUASTC 94 | 95 | > **isUASTC**: `boolean` 96 | 97 | Defined in: [type.ts:137](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L137) 98 | 99 | is UASTC texture, default is true 100 | 101 | *** 102 | 103 | ### isYFlip 104 | 105 | > **isYFlip**: `boolean` 106 | 107 | Defined in: [type.ts:141](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L141) 108 | 109 | if true the source images will be Y flipped before compression, default is false 110 | 111 | *** 112 | 113 | ### jsUrl? 114 | 115 | > `optional` **jsUrl**: `string` 116 | 117 | Defined in: [type.ts:185](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L185) 118 | 119 | js url 120 | 121 | *** 122 | 123 | ### kvData 124 | 125 | > **kvData**: `Record`\<`string`, `string` \| `Uint8Array`\> 126 | 127 | Defined in: [type.ts:172](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L172) 128 | 129 | kv data 130 | 131 | *** 132 | 133 | ### needSupercompression 134 | 135 | > **needSupercompression**: `boolean` 136 | 137 | Defined in: [type.ts:153](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L153) 138 | 139 | Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE 140 | 141 | *** 142 | 143 | ### qualityLevel 144 | 145 | > **qualityLevel**: `number` 146 | 147 | Defined in: [type.ts:145](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L145) 148 | 149 | Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff. 150 | 151 | *** 152 | 153 | ### type 154 | 155 | > **type**: [`SourceType`](../enumerations/SourceType.md) 156 | 157 | Defined in: [type.ts:175](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L175) 158 | 159 | type 160 | 161 | *** 162 | 163 | ### wasmUrl? 164 | 165 | > `optional` **wasmUrl**: `string` 166 | 167 | Defined in: [type.ts:189](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L189) 168 | 169 | wasm url 170 | -------------------------------------------------------------------------------- /docs/interfaces/IEncodeOptions.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / IEncodeOptions 6 | 7 | # Interface: IEncodeOptions 8 | 9 | Defined in: [type.ts:129](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L129) 10 | 11 | ## Properties 12 | 13 | ### compressionLevel 14 | 15 | > **compressionLevel**: `number` 16 | 17 | Defined in: [type.ts:149](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L149) 18 | 19 | The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files. 20 | 21 | *** 22 | 23 | ### enableDebug 24 | 25 | > **enableDebug**: `boolean` 26 | 27 | Defined in: [type.ts:133](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L133) 28 | 29 | enable debug output, default is false 30 | 31 | *** 32 | 33 | ### generateMipmap 34 | 35 | > **generateMipmap**: `boolean` 36 | 37 | Defined in: [type.ts:165](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L165) 38 | 39 | If true mipmaps will be generated from the source images 40 | 41 | *** 42 | 43 | ### imageDecoder()? 44 | 45 | > `optional` **imageDecoder**: (`buffer`) => `Promise`\<\{ `data`: `Uint8Array`; `height`: `number`; `width`: `number`; \}\> 46 | 47 | Defined in: [type.ts:181](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L181) 48 | 49 | Decode compressed image buffer to RGBA imageData.(Required in Node.js) 50 | 51 | #### Parameters 52 | 53 | ##### buffer 54 | 55 | `Uint8Array` 56 | 57 | #### Returns 58 | 59 | `Promise`\<\{ `data`: `Uint8Array`; `height`: `number`; `width`: `number`; \}\> 60 | 61 | *** 62 | 63 | ### isKTX2File 64 | 65 | > **isKTX2File**: `boolean` 66 | 67 | Defined in: [type.ts:169](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L169) 68 | 69 | Create .KTX2 files instead of .basis files. By default this is FALSE. 70 | 71 | *** 72 | 73 | ### isNormalMap 74 | 75 | > **isNormalMap**: `boolean` 76 | 77 | Defined in: [type.ts:157](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L157) 78 | 79 | setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps. 80 | 81 | *** 82 | 83 | ### isSetKTX2SRGBTransferFunc 84 | 85 | > **isSetKTX2SRGBTransferFunc**: `boolean` 86 | 87 | Defined in: [type.ts:161](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L161) 88 | 89 | Input source is sRGB. This should very probably match the "perceptual" setting. 90 | 91 | *** 92 | 93 | ### isUASTC 94 | 95 | > **isUASTC**: `boolean` 96 | 97 | Defined in: [type.ts:137](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L137) 98 | 99 | is UASTC texture, default is true 100 | 101 | *** 102 | 103 | ### isYFlip 104 | 105 | > **isYFlip**: `boolean` 106 | 107 | Defined in: [type.ts:141](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L141) 108 | 109 | if true the source images will be Y flipped before compression, default is false 110 | 111 | *** 112 | 113 | ### jsUrl? 114 | 115 | > `optional` **jsUrl**: `string` 116 | 117 | Defined in: [type.ts:185](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L185) 118 | 119 | js url 120 | 121 | *** 122 | 123 | ### kvData 124 | 125 | > **kvData**: `Record`\<`string`, `string` \| `Uint8Array`\> 126 | 127 | Defined in: [type.ts:172](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L172) 128 | 129 | kv data 130 | 131 | *** 132 | 133 | ### needSupercompression 134 | 135 | > **needSupercompression**: `boolean` 136 | 137 | Defined in: [type.ts:153](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L153) 138 | 139 | Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE 140 | 141 | *** 142 | 143 | ### qualityLevel 144 | 145 | > **qualityLevel**: `number` 146 | 147 | Defined in: [type.ts:145](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L145) 148 | 149 | Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff. 150 | 151 | *** 152 | 153 | ### type 154 | 155 | > **type**: [`SourceType`](../enumerations/SourceType.md) 156 | 157 | Defined in: [type.ts:175](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L175) 158 | 159 | type 160 | 161 | *** 162 | 163 | ### wasmUrl? 164 | 165 | > `optional` **wasmUrl**: `string` 166 | 167 | Defined in: [type.ts:189](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L189) 168 | 169 | wasm url 170 | -------------------------------------------------------------------------------- /website/.vitepress/components/ktx2-viewer.vue: -------------------------------------------------------------------------------- 1 | 127 | 128 | 153 | 154 | 181 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | import type { BasisTextureType, HDRSourceType, SourceType } from "./enum.js"; 2 | 3 | export type CubeBufferData = [Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array, Uint8Array]; 4 | /** 5 | * which is defined in [basis_wrappers.cpp](https://github.com/BinomialLLC/basis_universal/blob/df079eca71cf83481c45059dce2165348dc1a5ea/webgl/transcoder/basis_wrappers.cpp#L1830) 6 | */ 7 | export interface IBasisEncoder { 8 | /** 9 | * Sets the slice's source image, either from a PNG file or from a raw 32-bit RGBA raster image. 10 | * The first texel is the top-left texel. The texel byte order in memory is R,G,B,A (R first at offset 0, A last at offset 3). 11 | * @param sliceIndex sliceIndex is the slice to change. Valid range is [0,BASISU_MAX_SLICES-1]. 12 | * @param imageBuffer png image buffer or 32bit RGBA raster image buffer 13 | * @param width if isPNG is true, width set 0. 14 | * @param height if isPNG is true, height set 0. 15 | * @param type type of the input source. 16 | */ 17 | setSliceSourceImage( 18 | sliceIndex: number, 19 | imageBuffer: Uint8Array, 20 | width: number, 21 | height: number, 22 | type: SourceType 23 | ): void; 24 | /** 25 | * Compresses the provided source slice(s) to an output .basis file. 26 | * At the minimum, you must provided at least 1 source slice by calling setSliceSourceImage() before calling this method. 27 | * @param pngBuffer 28 | * @returns byte length of encoded data 29 | */ 30 | encode(pngBuffer: Uint8Array): number; 31 | /** 32 | * If true, the encoder will output a UASTC texture, otherwise a ETC1S texture. 33 | * @param isUASTC 34 | */ 35 | setUASTC(isUASTC: boolean): void; 36 | /** 37 | * Sets the basis texture type, default is cBASISTexType2D 38 | * @param texType texType is enum BasisTextureType 39 | */ 40 | setTexType(texType: BasisTextureType): void; 41 | /** 42 | * Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE 43 | */ 44 | setKTX2UASTCSupercompression(needSupercompression: boolean): void; 45 | /** 46 | * If true the source images will be Y flipped before compression. 47 | */ 48 | setYFlip(isYFlip: boolean): void; 49 | /** 50 | * Enables debug output to stdout 51 | * @param isDebug 52 | */ 53 | setDebug(isDebug: boolean): void; 54 | /** 55 | * Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff. 56 | * Default is -1 (meaning unused - the compressor will use m_max_endpoint_clusters/m_max_selector_clusters instead to control the codebook sizes). 57 | * @param level Range is [1, 255] 58 | */ 59 | setQualityLevel(level: number): void; 60 | /** 61 | * The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files. 62 | * @param level Default is 2, range is [0, 6] 63 | */ 64 | setCompressionLevel(level: number): void; 65 | /** 66 | * setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps. 67 | */ 68 | setNormalMap(): void; 69 | /** 70 | * Create .KTX2 files instead of .basis files. By default this is FALSE. 71 | * @param isKTX2 72 | */ 73 | setCreateKTX2File(isKTX2: boolean): void; 74 | /** 75 | * If true mipmaps will be generated from the source images 76 | * @param needGenMipmap 77 | */ 78 | setMipGen(needGenMipmap: boolean): void; 79 | /** 80 | * Use sRGB transfer func in the file's DFD. Default is FALSE. This should very probably match the "perceptual" setting. 81 | * @param srgbTransferFunc need sRGB transfer func 82 | */ 83 | setKTX2SRGBTransferFunc(srgbTransferFunc: boolean): void; 84 | /** 85 | * If true, the input is assumed to be in sRGB space. Be sure to set this correctly! (Examples: True on photos, albedo/spec maps, and false on normal maps.) 86 | */ 87 | setPerceptual(perceptual: boolean): void; 88 | 89 | /** release memory */ 90 | delete(): void; 91 | 92 | /** If true, the RDO post-processor will be applied to the encoded UASTC texture data. */ 93 | setRDOUASTC(rdoUASTC: boolean): void; 94 | 95 | // Default is 1.0 range is [0.001, 10.0] 96 | setRDOUASTCQualityScalar(rdo_quality: number): void; 97 | 98 | /** Default is 4096, range is [64, 65535] */ 99 | setRDOUASTCDictSize(dictSize: number): void; 100 | 101 | /** Default is 10.0f, range is [01, 100.0] */ 102 | setRDOUASTCMaxAllowedRMSIncreaseRatio(rdo_uastc_max_allowed_rms_increase_ratio: number): void; 103 | 104 | /** Default is 8.0f, range is [.01f, 100.0f] */ 105 | setRDOUASTCSkipBlockRMSThresh(rdo_uastc_skip_block_rms_thresh: number): void; 106 | 107 | /** 108 | * Sets the max # of endpoint clusters for ETC1S mode. Use instead of setQualityLevel. 109 | * Default is 512, range is [1, 16128] 110 | */ 111 | setMaxEndpointClusters(max_endpoint_clusters: number): void; 112 | 113 | /** 114 | * Sets the max # of selectors clusters for ETC1S mode. Use instead of setQualityLevel. 115 | * Default is 512, range is [1, 16128] 116 | */ 117 | setMaxSelectorClusters(max_selector_clusters: number): void; 118 | 119 | /** Default is BASISU_DEFAULT_SELECTOR_RDO_THRESH, range is [0,1e+10] */ 120 | setSelectorRDOThresh(selector_rdo_thresh: number): void; 121 | 122 | /** Default is BASISU_DEFAULT_ENDPOINT_RDO_THRESH, range is [0,1e+10] */ 123 | setEndpointRDOThresh(endpoint_rdo_thresh: number): void; 124 | 125 | /** 126 | * Sets the UASTC(LDR) encoding performance vs. quality tradeoff, and other lesser used UASTC encoder flags. 127 | * @param - packUastcFlags 0-4, default is 1, the value is higher, the UASTC's quality is higher 128 | */ 129 | setPackUASTCFlags(packUastcFlags: number): void; 130 | 131 | /** 132 | * Enable HDR mode. Only supported for UASTC encoding. 133 | * @param enableHDR Whether to enable HDR encoding mode 134 | */ 135 | setHDR(enableHDR: boolean): void; 136 | 137 | /** 138 | * Sets the quality vs. encoder performance tradeoff (0-4, default is 1). Higher=slower but better quality. 139 | * @param level - Quality level(0-4), default is 1 140 | */ 141 | setUASTCHDRQualityLevel(level: number): void; 142 | 143 | /** 144 | * setSliceSourceImageHDR is the same as setSliceSourceImage, but for HDR images. 145 | * If the input is a raster image, the buffer must be width*height*4 bytes in size. The raster image is stored in top down scanline order. 146 | * @param sliceIndex - sliceIndex is the slice to change. Valid range is [0,BASISU_MAX_SLICES-1]. 147 | * @param imageBuffer - png image buffer or 32bit RGBA raster image buffer 148 | * @param width - if it is not raster image, width set 0. 149 | * @param height - if it is not raster image, height set 0. 150 | * @param type - type of the input source. 151 | * @param ldrSrgbToLinear - If true, the input is assumed to be in sRGB space. 152 | */ 153 | setSliceSourceImageHDR( 154 | sliceIndex: number, 155 | imageBuffer: Uint8Array, 156 | width: number, 157 | height: number, 158 | type: HDRSourceType, 159 | ldrSrgbToLinear: boolean 160 | ): void; 161 | 162 | new (): IBasisEncoder; 163 | } 164 | 165 | export interface IBasisModule { 166 | BasisEncoder: IBasisEncoder; 167 | initializeBasis: () => void; 168 | } 169 | 170 | interface HDROptions extends BasisOptions { 171 | /** 172 | * Enable HDR mode. Only supported for UASTC encoding. 173 | */ 174 | isHDR: true; 175 | /** 176 | * Image type 177 | */ 178 | imageType: "hdr" | "exr" | "raster"; 179 | /** 180 | * HDR quality level 181 | */ 182 | hdrQualityLevel?: number; 183 | } 184 | interface LDROptions extends BasisOptions { 185 | /** 186 | * Enable HDR mode. Only supported for UASTC encoding. 187 | * Default is false. 188 | */ 189 | isHDR?: false; 190 | } 191 | 192 | interface BasisOptions { 193 | /** 194 | * enable debug output, default is false 195 | */ 196 | enableDebug?: boolean; 197 | /** 198 | * is UASTC texture, default is true 199 | */ 200 | isUASTC?: boolean; 201 | /** 202 | * if true the source images will be Y flipped before compression, default is false 203 | */ 204 | isYFlip?: boolean; 205 | /** 206 | * Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff. 207 | */ 208 | qualityLevel?: number; 209 | /** 210 | * The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files. 211 | */ 212 | compressionLevel?: number; 213 | /** 214 | * Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE. 215 | */ 216 | needSupercompression?: boolean; 217 | /** 218 | * uastcLDRQualityLevel 0-3, default is 1, the value is higher, the UASTC's quality is higher. 219 | */ 220 | uastcLDRQualityLevel?: number; 221 | /** 222 | * enableRDO(UASTC LDR), default is false. 223 | */ 224 | enableRDO?: boolean; 225 | /** 226 | * RDO quality level, 0 - 10 float number, default is 1.0. 227 | */ 228 | rdoQualityLevel?: number; 229 | /** 230 | * setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps. 231 | */ 232 | isNormalMap?: boolean; 233 | 234 | /** 235 | * If true, the input is assumed to be in sRGB space. Be sure to set this correctly! (Examples: True on photos, albedo/spec maps, and false on normal maps.) 236 | */ 237 | isPerceptual?: boolean; 238 | /** 239 | * Input source is sRGB. This should very probably match the "perceptual" setting. 240 | */ 241 | isSetKTX2SRGBTransferFunc?: boolean; 242 | /** 243 | * If true mipmaps will be generated from the source images 244 | */ 245 | generateMipmap?: boolean; 246 | /** 247 | * Create .KTX2 files instead of .basis files. By default this is FALSE. 248 | */ 249 | isKTX2File?: boolean; 250 | 251 | /** kv data */ 252 | kvData?: Record; 253 | 254 | /** type */ 255 | type?: SourceType; 256 | /** 257 | * Decode compressed image buffer to RGBA imageData.(Required in Node.js) 258 | * @param buffer 259 | * @returns 260 | */ 261 | imageDecoder?: (buffer: Uint8Array) => Promise<{ width: number; height: number; data: Uint8Array }>; 262 | /** 263 | * js url 264 | */ 265 | jsUrl?: string; 266 | /** 267 | * wasm url 268 | */ 269 | wasmUrl?: string; 270 | } 271 | 272 | declare global { 273 | var __KTX2_DEBUG__: boolean; 274 | } 275 | 276 | export type IEncodeOptions = HDROptions | LDROptions; 277 | -------------------------------------------------------------------------------- /website/.vitepress/components/ktx-encoder.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 102 | 103 | 297 | -------------------------------------------------------------------------------- /website/.vitepress/components/ktx-cube-encoder.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 122 | 123 | 330 | -------------------------------------------------------------------------------- /docs/interfaces/IBasisEncoder.md: -------------------------------------------------------------------------------- 1 | [**ktx2-encoder**](../README.md) 2 | 3 | *** 4 | 5 | [ktx2-encoder](../globals.md) / IBasisEncoder 6 | 7 | # Interface: IBasisEncoder 8 | 9 | Defined in: [type.ts:3](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L3) 10 | 11 | ## Constructors 12 | 13 | ### new IBasisEncoder() 14 | 15 | > **new IBasisEncoder**(): [`IBasisEncoder`](IBasisEncoder.md) 16 | 17 | Defined in: [type.ts:121](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L121) 18 | 19 | #### Returns 20 | 21 | [`IBasisEncoder`](IBasisEncoder.md) 22 | 23 | ## Methods 24 | 25 | ### delete() 26 | 27 | > **delete**(): `void` 28 | 29 | Defined in: [type.ts:86](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L86) 30 | 31 | release memory 32 | 33 | #### Returns 34 | 35 | `void` 36 | 37 | *** 38 | 39 | ### encode() 40 | 41 | > **encode**(`pngBuffer`): `number` 42 | 43 | Defined in: [type.ts:26](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L26) 44 | 45 | Compresses the provided source slice(s) to an output .basis file. 46 | At the minimum, you must provided at least 1 source slice by calling setSliceSourceImage() before calling this method. 47 | 48 | #### Parameters 49 | 50 | ##### pngBuffer 51 | 52 | `Uint8Array` 53 | 54 | #### Returns 55 | 56 | `number` 57 | 58 | byte length of encoded data 59 | 60 | *** 61 | 62 | ### setCompressionLevel() 63 | 64 | > **setCompressionLevel**(`level`): `void` 65 | 66 | Defined in: [type.ts:60](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L60) 67 | 68 | The compression_level parameter controls the encoder perf vs. file size tradeoff for ETC1S files. 69 | 70 | #### Parameters 71 | 72 | ##### level 73 | 74 | `number` 75 | 76 | Default is 2, range is [0, 6] 77 | 78 | #### Returns 79 | 80 | `void` 81 | 82 | *** 83 | 84 | ### setCreateKTX2File() 85 | 86 | > **setCreateKTX2File**(`isKTX2`): `void` 87 | 88 | Defined in: [type.ts:69](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L69) 89 | 90 | Create .KTX2 files instead of .basis files. By default this is FALSE. 91 | 92 | #### Parameters 93 | 94 | ##### isKTX2 95 | 96 | `boolean` 97 | 98 | #### Returns 99 | 100 | `void` 101 | 102 | *** 103 | 104 | ### setDebug() 105 | 106 | > **setDebug**(`isDebug`): `void` 107 | 108 | Defined in: [type.ts:49](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L49) 109 | 110 | Enables debug output to stdout 111 | 112 | #### Parameters 113 | 114 | ##### isDebug 115 | 116 | `boolean` 117 | 118 | #### Returns 119 | 120 | `void` 121 | 122 | *** 123 | 124 | ### setEndpointRDOThresh() 125 | 126 | > **setEndpointRDOThresh**(`endpoint_rdo_thresh`): `void` 127 | 128 | Defined in: [type.ts:119](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L119) 129 | 130 | Default is BASISU_DEFAULT_ENDPOINT_RDO_THRESH, range is [0,1e+10] 131 | 132 | #### Parameters 133 | 134 | ##### endpoint\_rdo\_thresh 135 | 136 | `number` 137 | 138 | #### Returns 139 | 140 | `void` 141 | 142 | *** 143 | 144 | ### setKTX2SRGBTransferFunc() 145 | 146 | > **setKTX2SRGBTransferFunc**(`srgbTransferFunc`): `void` 147 | 148 | Defined in: [type.ts:79](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L79) 149 | 150 | Use sRGB transfer func in the file's DFD. Default is FALSE. This should very probably match the "perceptual" setting. 151 | 152 | #### Parameters 153 | 154 | ##### srgbTransferFunc 155 | 156 | `boolean` 157 | 158 | need sRGB transfer func 159 | 160 | #### Returns 161 | 162 | `void` 163 | 164 | *** 165 | 166 | ### setKTX2UASTCSupercompression() 167 | 168 | > **setKTX2UASTCSupercompression**(`needSupercompression`): `void` 169 | 170 | Defined in: [type.ts:40](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L40) 171 | 172 | Use UASTC Zstandard supercompression. Defaults to disabled or KTX2_SS_NONE 173 | 174 | #### Parameters 175 | 176 | ##### needSupercompression 177 | 178 | `boolean` 179 | 180 | #### Returns 181 | 182 | `void` 183 | 184 | *** 185 | 186 | ### setMaxEndpointClusters() 187 | 188 | > **setMaxEndpointClusters**(`max_endpoint_clusters`): `void` 189 | 190 | Defined in: [type.ts:107](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L107) 191 | 192 | Sets the max # of endpoint clusters for ETC1S mode. Use instead of setQualityLevel. 193 | Default is 512, range is [1, 16128] 194 | 195 | #### Parameters 196 | 197 | ##### max\_endpoint\_clusters 198 | 199 | `number` 200 | 201 | #### Returns 202 | 203 | `void` 204 | 205 | *** 206 | 207 | ### setMaxSelectorClusters() 208 | 209 | > **setMaxSelectorClusters**(`max_selector_clusters`): `void` 210 | 211 | Defined in: [type.ts:113](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L113) 212 | 213 | Sets the max # of selectors clusters for ETC1S mode. Use instead of setQualityLevel. 214 | Default is 512, range is [1, 16128] 215 | 216 | #### Parameters 217 | 218 | ##### max\_selector\_clusters 219 | 220 | `number` 221 | 222 | #### Returns 223 | 224 | `void` 225 | 226 | *** 227 | 228 | ### setMipGen() 229 | 230 | > **setMipGen**(`needGenMipmap`): `void` 231 | 232 | Defined in: [type.ts:74](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L74) 233 | 234 | If true mipmaps will be generated from the source images 235 | 236 | #### Parameters 237 | 238 | ##### needGenMipmap 239 | 240 | `boolean` 241 | 242 | #### Returns 243 | 244 | `void` 245 | 246 | *** 247 | 248 | ### setNormalMap() 249 | 250 | > **setNormalMap**(): `void` 251 | 252 | Defined in: [type.ts:64](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L64) 253 | 254 | setNormalMapMode is the same as the basisu.exe "-normal_map" option. It tunes several codec parameters so compression works better on normal maps. 255 | 256 | #### Returns 257 | 258 | `void` 259 | 260 | *** 261 | 262 | ### setPerceptual() 263 | 264 | > **setPerceptual**(`perceptual`): `void` 265 | 266 | Defined in: [type.ts:83](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L83) 267 | 268 | If true, the input is assumed to be in sRGB space. Be sure to set this correctly! (Examples: True on photos, albedo/spec maps, and false on normal maps.) 269 | 270 | #### Parameters 271 | 272 | ##### perceptual 273 | 274 | `boolean` 275 | 276 | #### Returns 277 | 278 | `void` 279 | 280 | *** 281 | 282 | ### setQualityLevel() 283 | 284 | > **setQualityLevel**(`level`): `void` 285 | 286 | Defined in: [type.ts:55](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L55) 287 | 288 | Sets the ETC1S encoder's quality level, which controls the file size vs. quality tradeoff. 289 | Default is -1 (meaning unused - the compressor will use m_max_endpoint_clusters/m_max_selector_clusters instead to control the codebook sizes). 290 | 291 | #### Parameters 292 | 293 | ##### level 294 | 295 | `number` 296 | 297 | Range is [1, 255] 298 | 299 | #### Returns 300 | 301 | `void` 302 | 303 | *** 304 | 305 | ### setRDOUASTC() 306 | 307 | > **setRDOUASTC**(`rdoUASTC`): `void` 308 | 309 | Defined in: [type.ts:89](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L89) 310 | 311 | If true, the RDO post-processor will be applied to the encoded UASTC texture data. 312 | 313 | #### Parameters 314 | 315 | ##### rdoUASTC 316 | 317 | `boolean` 318 | 319 | #### Returns 320 | 321 | `void` 322 | 323 | *** 324 | 325 | ### setRDOUASTCDictSize() 326 | 327 | > **setRDOUASTCDictSize**(`dictSize`): `void` 328 | 329 | Defined in: [type.ts:95](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L95) 330 | 331 | Default is 4096, range is [64, 65535] 332 | 333 | #### Parameters 334 | 335 | ##### dictSize 336 | 337 | `number` 338 | 339 | #### Returns 340 | 341 | `void` 342 | 343 | *** 344 | 345 | ### setRDOUASTCMaxAllowedRMSIncreaseRatio() 346 | 347 | > **setRDOUASTCMaxAllowedRMSIncreaseRatio**(`rdo_uastc_max_allowed_rms_increase_ratio`): `any` 348 | 349 | Defined in: [type.ts:98](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L98) 350 | 351 | Default is 10.0f, range is [01, 100.0] 352 | 353 | #### Parameters 354 | 355 | ##### rdo\_uastc\_max\_allowed\_rms\_increase\_ratio 356 | 357 | `number` 358 | 359 | #### Returns 360 | 361 | `any` 362 | 363 | *** 364 | 365 | ### setRDOUASTCQualityScalar() 366 | 367 | > **setRDOUASTCQualityScalar**(`rdo_quality`): `void` 368 | 369 | Defined in: [type.ts:92](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L92) 370 | 371 | #### Parameters 372 | 373 | ##### rdo\_quality 374 | 375 | `number` 376 | 377 | #### Returns 378 | 379 | `void` 380 | 381 | *** 382 | 383 | ### setRDOUASTCSkipBlockRMSThresh() 384 | 385 | > **setRDOUASTCSkipBlockRMSThresh**(`rdo_uastc_skip_block_rms_thresh`): `any` 386 | 387 | Defined in: [type.ts:101](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L101) 388 | 389 | Default is 8.0f, range is [.01f, 100.0f] 390 | 391 | #### Parameters 392 | 393 | ##### rdo\_uastc\_skip\_block\_rms\_thresh 394 | 395 | `number` 396 | 397 | #### Returns 398 | 399 | `any` 400 | 401 | *** 402 | 403 | ### setSelectorRDOThresh() 404 | 405 | > **setSelectorRDOThresh**(`selector_rdo_thresh`): `void` 406 | 407 | Defined in: [type.ts:116](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L116) 408 | 409 | Default is BASISU_DEFAULT_SELECTOR_RDO_THRESH, range is [0,1e+10] 410 | 411 | #### Parameters 412 | 413 | ##### selector\_rdo\_thresh 414 | 415 | `number` 416 | 417 | #### Returns 418 | 419 | `void` 420 | 421 | *** 422 | 423 | ### setSliceSourceImage() 424 | 425 | > **setSliceSourceImage**(`sliceIndex`, `imageBuffer`, `width`, `height`, `type`): `void` 426 | 427 | Defined in: [type.ts:13](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L13) 428 | 429 | Sets the slice's source image, either from a PNG file or from a raw 32-bit RGBA raster image. 430 | The first texel is the top-left texel. The texel byte order in memory is R,G,B,A (R first at offset 0, A last at offset 3). 431 | 432 | #### Parameters 433 | 434 | ##### sliceIndex 435 | 436 | `number` 437 | 438 | sliceIndex is the slice to change. Valid range is [0,BASISU_MAX_SLICES-1]. 439 | 440 | ##### imageBuffer 441 | 442 | `Uint8Array` 443 | 444 | png image buffer or 32bit RGBA raster image buffer 445 | 446 | ##### width 447 | 448 | `number` 449 | 450 | if isPNG is true, width set 0. 451 | 452 | ##### height 453 | 454 | `number` 455 | 456 | if isPNG is true, height set 0. 457 | 458 | ##### type 459 | 460 | [`SourceType`](../enumerations/SourceType.md) 461 | 462 | type of the input source. 463 | 464 | #### Returns 465 | 466 | `void` 467 | 468 | *** 469 | 470 | ### setTexType() 471 | 472 | > **setTexType**(`texType`): `void` 473 | 474 | Defined in: [type.ts:36](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L36) 475 | 476 | Sets the basis texture type, default is cBASISTexType2D 477 | 478 | #### Parameters 479 | 480 | ##### texType 481 | 482 | [`BasisTextureType`](../enumerations/BasisTextureType.md) 483 | 484 | texType is enum BasisTextureType 485 | 486 | #### Returns 487 | 488 | `void` 489 | 490 | *** 491 | 492 | ### setUASTC() 493 | 494 | > **setUASTC**(`isUASTC`): `void` 495 | 496 | Defined in: [type.ts:31](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L31) 497 | 498 | If true, the encoder will output a UASTC texture, otherwise a ETC1S texture. 499 | 500 | #### Parameters 501 | 502 | ##### isUASTC 503 | 504 | `boolean` 505 | 506 | #### Returns 507 | 508 | `void` 509 | 510 | *** 511 | 512 | ### setYFlip() 513 | 514 | > **setYFlip**(`isYFlip`): `void` 515 | 516 | Defined in: [type.ts:44](https://github.com/gz65555/ktx2-encoder/blob/7c4de41129ab790944f9dc093b94cea7ef1d2328/src/type.ts#L44) 517 | 518 | If true the source images will be Y flipped before compression. 519 | 520 | #### Parameters 521 | 522 | ##### isYFlip 523 | 524 | `boolean` 525 | 526 | #### Returns 527 | 528 | `void` 529 | -------------------------------------------------------------------------------- /scene.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "eeda8863-cf98-4d33-9a59-2e9b69f3f2d6", 4 | "type": "prefab", 5 | "virtualPath": "/Assets/FBX_Track/2.fbx", 6 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*qvqKQ6sXbkMAAAAAAAAAAAAADkp5AQ/2.glb" 7 | }, 8 | { 9 | "id": "594ce082-0e19-4c57-a35b-bc648b6cb60f", 10 | "type": "prefab", 11 | "virtualPath": "/Assets/FBX_Track/3.fbx", 12 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*n-i9R4r7rMwAAAAAAAAAAAAADkp5AQ/3.glb" 13 | }, 14 | { 15 | "id": "a3f7e2ed-7131-440d-9fa0-d360b7bf259e", 16 | "type": "prefab", 17 | "virtualPath": "/Assets/FBX_Track/4.fbx", 18 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*az5dT4LWhbQAAAAAAAAAAAAADkp5AQ/4.glb" 19 | }, 20 | { 21 | "id": "5fb652d2-cefa-48ce-846f-622f1f40cadd", 22 | "type": "prefab", 23 | "virtualPath": "/Assets/FBX_Track/5.fbx", 24 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*y1WlSLYlgOgAAAAAAAAAAAAADkp5AQ/5.glb" 25 | }, 26 | { 27 | "id": "a04e7c20-7656-4078-88dc-cede4cd77613", 28 | "type": "prefab", 29 | "virtualPath": "/Assets/FBX_Track/6.fbx", 30 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*QQ9OSpL31n0AAAAAAAAAAAAADkp5AQ/6.glb" 31 | }, 32 | { 33 | "id": "617812b0-c054-4dc4-8694-3322cc3583c1", 34 | "type": "prefab", 35 | "virtualPath": "/Assets/FBX_Track/7.fbx", 36 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*gLTdQKtmhZoAAAAAAAAAAAAADkp5AQ/7.glb" 37 | }, 38 | { 39 | "id": "bd3c15ff-98b0-4afe-b3b0-6d6838fd8914", 40 | "type": "prefab", 41 | "virtualPath": "/Assets/FBX_Track/8.fbx", 42 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*mrhgRKpJ-98AAAAAAAAAAAAADkp5AQ/8.glb" 43 | }, 44 | { 45 | "id": "a1434e80-6fd8-4dc1-b5a7-cb5a40346ea3", 46 | "type": "prefab", 47 | "virtualPath": "/Assets/FBX_Track/9.fbx", 48 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*hEM8RKquRzwAAAAAAAAAAAAADkp5AQ/9.glb" 49 | }, 50 | { 51 | "id": "7ea006b5-f262-4305-a8cb-7341249cbd79", 52 | "type": "prefab", 53 | "virtualPath": "/Assets/FBX_Track/10.fbx", 54 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*vdXES6HannoAAAAAAAAAAAAADkp5AQ/10.glb" 55 | }, 56 | { 57 | "id": "471f758c-d559-4ec9-86c6-607d1b88555c", 58 | "type": "prefab", 59 | "virtualPath": "/Assets/FBX_Track/11.fbx", 60 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*uJ3oQ4zLy8IAAAAAAAAAAAAADkp5AQ/11.glb" 61 | }, 62 | { 63 | "id": "2bc35f78-0644-47ce-885f-97a52df9ba96", 64 | "type": "prefab", 65 | "virtualPath": "/Assets/FBX_Track/12.fbx", 66 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*cJhFSoCER0IAAAAAAAAAAAAADkp5AQ/12.glb" 67 | }, 68 | { 69 | "id": "c2e84a55-bac8-4ff0-89ea-13cfcb679f10", 70 | "type": "prefab", 71 | "virtualPath": "/Assets/FBX_Track/1.fbx", 72 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*9a6ISbiDmJcAAAAAAAAAAAAADkp5AQ/1.glb" 73 | }, 74 | { 75 | "id": "febafede-5139-447c-81a0-1c9870adcc7e", 76 | "type": "EditorTexture2D", 77 | "virtualPath": "/Assets/huopen/huopen_BaseColor.png", 78 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*f--9QI3wPZEAAAAAAAAAAAAADkp5AQ/huopen_BaseColor.png" 79 | }, 80 | { 81 | "id": "c801dbe0-29aa-4ad9-b462-1a7ee8439736", 82 | "type": "EditorTexture2D", 83 | "virtualPath": "/Assets/huopen/huopen_Normal.png", 84 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*io52QrHGbNIAAAAAAAAAAAAADkp5AQ/huopen_Normal.png" 85 | }, 86 | { 87 | "id": "d21630bd-39a4-4a80-a274-15920d605328", 88 | "type": "EditorTexture2D", 89 | "virtualPath": "/Assets/huopen/huopen_Roughness.png", 90 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*FeAcQIO6Io0AAAAAAAAAAAAADkp5AQ/huopen_Roughness.png" 91 | }, 92 | { 93 | "id": "10089815-cfe5-41e8-b18a-e7410b4b42ce", 94 | "type": "prefab", 95 | "virtualPath": "/Assets/huopen/huopen.fbx", 96 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*JKvMQpvjBQIAAAAAAAAAAAAADkp5AQ/huopen.glb" 97 | }, 98 | { 99 | "id": "f3874eba-0882-4c35-a67a-ef2b35fda130", 100 | "type": "material", 101 | "virtualPath": "/Assets/huopen/huopen-copied", 102 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*fFGnTrE8a58AAAAAAAAAAAAADkp5AQ/huopen-copied.json" 103 | }, 104 | { 105 | "id": "f4d1cd84-c6fd-441a-ba0c-23320ec24e2b", 106 | "type": "EditorTexture2D", 107 | "virtualPath": "/Assets/LV1/XDDN_LV1_02_albedo.png", 108 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*lHzMTqawLUMAAAAAAAAAAAAADkp5AQ/XDDN_LV1_02_albedo.png" 109 | }, 110 | { 111 | "id": "02a6c96d-3478-4510-8d37-5849f89ba049", 112 | "type": "EditorTexture2D", 113 | "virtualPath": "/Assets/LV1/XDDN_LV1_06_albedo.png", 114 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*m-dKQoqdIhUAAAAAAAAAAAAADkp5AQ/XDDN_LV1_06_albedo.png" 115 | }, 116 | { 117 | "id": "a66d70fa-5637-4370-b83b-1c17d94cf1ab", 118 | "type": "EditorTexture2D", 119 | "virtualPath": "/Assets/LV1/XDDN_LV1_03_albedo.png", 120 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*dskbT67SdEMAAAAAAAAAAAAADkp5AQ/XDDN_LV1_03_albedo.png" 121 | }, 122 | { 123 | "id": "5af4711d-02eb-477e-b363-74033229cfbb", 124 | "type": "EditorTexture2D", 125 | "virtualPath": "/Assets/LV1/XDDN_LV1_05_albedo.png", 126 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*_XdBSIDd47kAAAAAAAAAAAAADkp5AQ/XDDN_LV1_05_albedo.png" 127 | }, 128 | { 129 | "id": "ee7cffda-7137-49fa-a676-9d3a355e0b72", 130 | "type": "EditorTexture2D", 131 | "virtualPath": "/Assets/LV1/XDDN_LV1_01_albedo.png", 132 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*Icz6R61vcBQAAAAAAAAAAAAADkp5AQ/XDDN_LV1_01_albedo.png" 133 | }, 134 | { 135 | "id": "09199bde-eada-4142-936e-2c094734f470", 136 | "type": "EditorTexture2D", 137 | "virtualPath": "/Assets/LV1/XDDN_LV1_07_albedo.png", 138 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*S-sKT4MM3LEAAAAAAAAAAAAADkp5AQ/XDDN_LV1_07_albedo.png" 139 | }, 140 | { 141 | "id": "5a74fb55-879d-4abc-839a-4fe14f410b19", 142 | "type": "EditorTexture2D", 143 | "virtualPath": "/Assets/LV1/XDDN_LV1_04_albedo.png", 144 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*079vR6dOVdcAAAAAAAAAAAAADkp5AQ/XDDN_LV1_04_albedo.png" 145 | }, 146 | { 147 | "id": "3ef227f9-0d96-418f-bc22-1856a5da87f6", 148 | "type": "prefab", 149 | "virtualPath": "/Assets/LV1/yu_APL.glb", 150 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*LfFeR5aZ8e8AAAAAAAAAAAAADkp5AQ/yu_APL.glb" 151 | }, 152 | { 153 | "id": "92985dbe-bd27-4252-bc10-066e34488da1", 154 | "type": "prefab", 155 | "virtualPath": "/Assets/LV1/yu_anim_0628.fbx", 156 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*OAh-QrHsU6AAAAAAAAAAAAAADkp5AQ/yu_anim_0628.glb" 157 | }, 158 | { 159 | "id": "6f649176-daf3-4397-8b9b-574ebe297459", 160 | "type": "AnimatorController", 161 | "virtualPath": "/Assets/LV1/yu_anim", 162 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*4aOfSKpgf78AAAAAAAAAAAAADkp5AQ/yu_anim.json" 163 | }, 164 | { 165 | "id": "6be6cb56-7b46-49ee-83d2-c7635a41e1c4", 166 | "type": "AnimatorController", 167 | "virtualPath": "/Assets/LV1/qiu_anim", 168 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*ZaCXQLChCjsAAAAAAAAAAAAADkp5AQ/qiu_anim.json" 169 | }, 170 | { 171 | "id": "161552d8-e763-48e2-b692-bb66f189df51", 172 | "type": "prefab", 173 | "virtualPath": "/Assets/LV1/qiu_anim.fbx", 174 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*RVj0QI3VJf4AAAAAAAAAAAAADkp5AQ/qiu_anim.glb" 175 | }, 176 | { 177 | "id": "aa9590e3-c865-4ae9-bd8b-2f6b33112c5f", 178 | "type": "AnimatorController", 179 | "virtualPath": "/Assets/LV1/hua_anim", 180 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*bW0DSY5friAAAAAAAAAAAAAADkp5AQ/hua_anim.json" 181 | }, 182 | { 183 | "id": "628326f2-25a8-4737-b1f5-46208030363f", 184 | "type": "prefab", 185 | "virtualPath": "/Assets/LV1/LV1.fbx", 186 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*cuRURYSQTJ8AAAAAAAAAAAAADkp5AQ/LV1.glb" 187 | }, 188 | { 189 | "id": "69b12d34-ff27-499a-8d04-22b477dde8c0", 190 | "type": "EditorTexture2D", 191 | "virtualPath": "/Assets/LV1/XDDN_LV1_08_albedo.png", 192 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*yDKnT5JwNQEAAAAAAAAAAAAADkp5AQ/XDDN_LV1_08_albedo.png" 193 | }, 194 | { 195 | "id": "76faa0c0-5de9-4633-88c2-4f126ba8ba72", 196 | "type": "EditorTexture2D", 197 | "virtualPath": "/Assets/LV1/XDDN_LV1_09_albedo.png", 198 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*DhAzR7gSkfsAAAAAAAAAAAAADkp5AQ/XDDN_LV1_09_albedo.png" 199 | }, 200 | { 201 | "id": "4663d177-44d1-4ea3-a2a6-f95f93eefba1", 202 | "type": "prefab", 203 | "virtualPath": "/Assets/LV1/hua.glb", 204 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*AhCEQqyUaWEAAAAAAAAAAAAADkp5AQ/hua.glb" 205 | }, 206 | { 207 | "id": "61ca8cc0-6baf-46bc-bc42-d8d97bae9eca", 208 | "type": "EditorTexture2D", 209 | "virtualPath": "/Assets/shan_di/shui.png", 210 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*rDQnR7s1x_cAAAAAAAAAAAAADkp5AQ/shui.png" 211 | }, 212 | { 213 | "id": "fb3c6585-851d-4e88-8c96-d5d537591a3e", 214 | "type": "EditorTexture2D", 215 | "virtualPath": "/Assets/shan_di/di4.png", 216 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*1KQsSbtqarAAAAAAAAAAAAAADkp5AQ/di4.png" 217 | }, 218 | { 219 | "id": "3653edc5-7f4b-43c3-9464-bd863d0caead", 220 | "type": "EditorTexture2D", 221 | "virtualPath": "/Assets/shan_di/di3__albedo.png", 222 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*pLuKRbY-ntoAAAAAAAAAAAAADkp5AQ/di3__albedo.png" 223 | }, 224 | { 225 | "id": "a69d02d8-f162-420c-a6e9-1c78e9f73edc", 226 | "type": "EditorTexture2D", 227 | "virtualPath": "/Assets/shan_di/shan_albedo.png", 228 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*exObQLRxUW0AAAAAAAAAAAAADkp5AQ/shan_albedo.png" 229 | }, 230 | { 231 | "id": "94c8908b-407f-4393-9973-4db8274d2e01", 232 | "type": "EditorTexture2D", 233 | "virtualPath": "/Assets/shan_di/di2_1.png", 234 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*PaV4Tq6sG9YAAAAAAAAAAAAADkp5AQ/di2_1.png" 235 | }, 236 | { 237 | "id": "57f9f76f-0040-4286-9cbf-e9eba796f04e", 238 | "type": "EditorTexture2D", 239 | "virtualPath": "/Assets/shan_di/di1_2.png", 240 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*OcGISoDi6DsAAAAAAAAAAAAADkp5AQ/di1_2.png" 241 | }, 242 | { 243 | "id": "424810a7-f108-40ef-bff9-7b4885f8c3d1", 244 | "type": "EditorTexture2D", 245 | "virtualPath": "/Assets/shan_di/di1_1.png", 246 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*jaQPT6eD78oAAAAAAAAAAAAADkp5AQ/di1_1.png" 247 | }, 248 | { 249 | "id": "6d06171f-c567-4c1c-baea-42fa9a7a3fc5", 250 | "type": "EditorTexture2D", 251 | "virtualPath": "/Assets/shan_di/di2_2.png", 252 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*w9BFS7H3ZwsAAAAAAAAAAAAADkp5AQ/di2_2.png" 253 | }, 254 | { 255 | "id": "2c6c1317-052e-406f-9661-307ba5654ca6", 256 | "type": "prefab", 257 | "virtualPath": "/Assets/shan_di/shan.fbx", 258 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*X_U_To6C5igAAAAAAAAAAAAADkp5AQ/shan.glb" 259 | }, 260 | { 261 | "id": "cf30858a-dd92-419b-a0b8-5df06b542ae6", 262 | "type": "prefab", 263 | "virtualPath": "/Assets/shan_di/di.fbx", 264 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*lS9YSqr2IRoAAAAAAAAAAAAADkp5AQ/di.glb" 265 | }, 266 | { 267 | "id": "4d3b98ff-0433-428d-8d17-06f1ef583ce5", 268 | "type": "EditorTexture2D", 269 | "virtualPath": "/Assets/LV23/XDDN_LV23_5_8.png", 270 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*qPqNQKcGaPcAAAAAAAAAAAAADkp5AQ/XDDN_LV23_5_8.png" 271 | }, 272 | { 273 | "id": "ab681d21-01c5-4b4e-9edb-6645da95e1a6", 274 | "type": "EditorTexture2D", 275 | "virtualPath": "/Assets/LV23/XDDN_LV23_1_4.png", 276 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*s2vgRbXs2doAAAAAAAAAAAAADkp5AQ/XDDN_LV23_1_4.png" 277 | }, 278 | { 279 | "id": "9aa59019-0bcc-4369-9058-6762b57e3649", 280 | "type": "AnimatorController", 281 | "virtualPath": "/Assets/LV23/LV23", 282 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*X5H_SpXuBKUAAAAAAAAAAAAADkp5AQ/LV23.json" 283 | }, 284 | { 285 | "id": "dc9a1902-f8ac-4d94-b984-d0d9b568d6f1", 286 | "type": "prefab", 287 | "virtualPath": "/Assets/LV23/LV23.fbx", 288 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*ZO16QYMs0XkAAAAAAAAAAAAADkp5AQ/LV23.glb" 289 | }, 290 | { 291 | "id": "45216dc3-67e8-47f5-add0-baccd9c8da47", 292 | "type": "EditorTexture2D", 293 | "virtualPath": "/Assets/plant/yumao_bjs_r_ship_fruit.png", 294 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*umtPR6UfNloAAAAAAAAAAAAADkp5AQ/yumao_bjs_r_ship_fruit.png" 295 | }, 296 | { 297 | "id": "adf08b41-63c1-4c63-85f6-af849c9c47cc", 298 | "type": "EditorTexture2D", 299 | "virtualPath": "/Assets/plant/plant.png", 300 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*Hh9MRb1WJ0oAAAAAAAAAAAAADkp5AQ/plant.png" 301 | }, 302 | { 303 | "id": "d0548e9b-f8bd-44c4-a1a5-b9bbcac3d60a", 304 | "type": "EditorTexture2D", 305 | "virtualPath": "/Assets/plant/plant_2.png", 306 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*o-KNSatTckMAAAAAAAAAAAAADkp5AQ/plant_2.png" 307 | }, 308 | { 309 | "id": "1f0be934-c5ab-4bcd-8a53-836e420783dd", 310 | "type": "EditorTexture2D", 311 | "virtualPath": "/Assets/plant/plant_3.png", 312 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*d6ntR76P9l8AAAAAAAAAAAAADkp5AQ/plant_3.png" 313 | }, 314 | { 315 | "id": "b98a4c9f-aad2-4377-a952-5074097f3f64", 316 | "type": "prefab", 317 | "virtualPath": "/Assets/plant/plant.fbx", 318 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*BbZKRZsf-W4AAAAAAAAAAAAADkp5AQ/plant.glb" 319 | }, 320 | { 321 | "id": "44180257-167c-4a50-9625-45ad2bebc861", 322 | "type": "prefab", 323 | "virtualPath": "/Assets/plant/ship_fruit.fbx", 324 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*xJPqSZiFv_cAAAAAAAAAAAAADkp5AQ/ship_fruit.glb" 325 | }, 326 | { 327 | "id": "19a6d69f-000d-48f7-a38b-8c7257a02637", 328 | "type": "prefab", 329 | "virtualPath": "/Assets/r/r2.fbx", 330 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*YnlsRZjvV8kAAAAAAAAAAAAADkp5AQ/r2.glb" 331 | }, 332 | { 333 | "id": "aac0c324-9062-4a01-a22b-123c05783b23", 334 | "type": "EditorTexture2D", 335 | "virtualPath": "/Assets/r/new_r.png", 336 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*i747RoBOUE0AAAAAAAAAAAAADkp5AQ/new_r.png" 337 | }, 338 | { 339 | "id": "ec85f987-1dc7-4bed-bf67-ab7b9ab0920d", 340 | "type": "EditorTexture2D", 341 | "virtualPath": "/Assets/HUD/HUD_zise.png", 342 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*yft9RIlkDBEAAAAAAAAAAAAADkp5AQ/HUD_zise.png" 343 | }, 344 | { 345 | "id": "10d172ae-8bfd-4bf4-b851-bc4bf7f2a0e1", 346 | "type": "EditorTexture2D", 347 | "virtualPath": "/Assets/HUD/logo.png", 348 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*C5mvRKCUXbwAAAAAAAAAAAAADkp5AQ/logo.png" 349 | }, 350 | { 351 | "id": "80b232fa-bb88-42cc-87f8-81156366b14c", 352 | "type": "prefab", 353 | "virtualPath": "/Assets/HUD/HUD.glb", 354 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*oBHlT7kTteUAAAAAAAAAAAAADkp5AQ/HUD.glb" 355 | }, 356 | { 357 | "id": "bec37b8b-1121-48c4-9ecf-fb11451f1e6d", 358 | "type": "EditorTexture2D", 359 | "virtualPath": "/Assets/HUD/HUD2.png", 360 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*9AniT64CnVwAAAAAAAAAAAAADkp5AQ/HUD2.png" 361 | }, 362 | { 363 | "id": "89fdff2a-aa46-4e0a-8e31-20627e82513d", 364 | "type": "prefab", 365 | "virtualPath": "/Assets/HUD/fence.fbx", 366 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*402RR40ILocAAAAAAAAAAAAADkp5AQ/fence.glb" 367 | }, 368 | { 369 | "id": "21478942-a75d-47fe-9c11-f011acf43edb", 370 | "type": "environment", 371 | "virtualPath": "/Assets/sky/SKY_256.hdr", 372 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*z8n7T4Ghhr8AAAAAAAAAAAAADkp5AQ/SKY_256.hdr" 373 | }, 374 | { 375 | "id": "aaca6939-3487-456a-823d-d527b3041db3", 376 | "type": "EditorTexture2D", 377 | "virtualPath": "/Assets/sky/sky_img_albedo.png", 378 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*O1D1S5T9eUoAAAAAAAAAAAAADkp5AQ/sky_img_albedo.png" 379 | }, 380 | { 381 | "id": "353a428b-1d51-4563-9fc5-60b27417f2ba", 382 | "type": "prefab", 383 | "virtualPath": "/Assets/sky/sky.fbx", 384 | "path": "https://mdn.alipayobjects.com/oasis_be/afts/file/A*JCn3QKZnGXkAAAAAAAAAAAAADkp5AQ/sky.glb" 385 | } 386 | ] 387 | --------------------------------------------------------------------------------