├── 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 | [](https://www.npmjs.com/package/ktx2-encoder)
4 | [](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 | [](https://www.npmjs.com/package/ktx2-encoder)
8 | [](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
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 |
129 |
130 |
131 |
132 |
133 |
134 |
144 |
145 |
146 | Click or drag a KTX2 file to upload
147 | Only .ktx2 files are supported
148 |
149 |
150 |
151 |
152 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Select Image
10 |
11 |
12 |
![Preview]()
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {{ loading ? "Encoding..." : "Encode to KTX2" }}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
102 |
103 |
297 |
--------------------------------------------------------------------------------
/website/.vitepress/components/ktx-cube-encoder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Select 6 Faces (posx, negx, posy, negy, posz, negz)
10 |
11 |
12 |
13 |
![]()
14 |
{{ faceOrder[i] }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | {{ loading ? "Encoding..." : "Encode to KTX2 (Cubemap)" }}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
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 |
--------------------------------------------------------------------------------