UNPKG

ktx2-encoder

Version:

KTX2(.ktx2) encoder for browser applications

89 lines (88 loc) 3.83 kB
import { Document } from "@gltf-transform/core"; import { KHRTextureBasisu } from "@gltf-transform/extensions"; const NAME = "ktx2"; const SUPPORTED_MIME_TYPES = ["image/jpeg", "image/png", "image/webp"]; function listTextureSlots(texture) { const document = Document.fromGraph(texture.getGraph()); const root = document.getRoot(); const slots = texture .getGraph() .listParentEdges(texture) .filter((edge) => edge.getParent() !== root) .map((edge) => edge.getName()); return Array.from(new Set(slots)); } function createTransform(name, fn) { Object.defineProperty(fn, "name", { value: name }); return fn; } /** * Transforms compatible textures in a glTF asset to KTX2 format. * @param options KTX2 compression options * @returns Transform */ export function ktx2(options = {}) { // Merge defaults with provided options const patternRe = options.pattern; const slotsRe = options.slots; return createTransform(NAME, async (document) => { // Dynamically import the appropriate encoder const { encodeToKTX2 } = typeof window !== "undefined" ? await import("../web/index.js") : await import("../node/index.js"); const logger = document.getLogger(); const textures = document.getRoot().listTextures(); let isKHRTextureBasisu = false; await Promise.all(textures.map(async (texture, textureIndex) => { const textureLabel = texture.getURI() || texture.getName() || `${textureIndex + 1}/${textures.length}`; const prefix = `${NAME}(${textureLabel})`; const slots = listTextureSlots(texture); // Skip textures that are already KTX2 if (texture.getMimeType() === "image/ktx2") { logger.debug(`${prefix}: Skipping, already KTX2`); return; } // Skip unsupported mime types if (!SUPPORTED_MIME_TYPES.includes(texture.getMimeType())) { logger.debug(`${prefix}: Skipping, unsupported texture type "${texture.getMimeType()}"`); return; } // Skip textures that don't match pattern if (patternRe && !patternRe.test(texture.getName()) && !patternRe.test(texture.getURI())) { logger.debug(`${prefix}: Skipping, excluded by "pattern" parameter`); return; } // Skip textures that don't match slots if (slotsRe && slots.length && !slots.some((slot) => slotsRe.test(slot))) { logger.debug(`${prefix}: Skipping, [${slots.join(", ")}] excluded by "slots" parameter`); return; } try { const image = texture.getImage(); if (!image) { logger.warn(`${prefix}: Skipping, no image data`); return; } const srcByteLength = image.byteLength; // Encode to KTX2 const ktx2Data = await encodeToKTX2(image, { ...options, }); // Update texture with new KTX2 data texture.setImage(ktx2Data); texture.setMimeType("image/ktx2"); isKHRTextureBasisu = true; const dstByteLength = ktx2Data.byteLength; logger.debug(`${prefix}: Size = ${srcByteLength}${dstByteLength} bytes`); } catch (error) { logger.warn(`${prefix}: Failed to convert texture: ${error.message}`); console.log(error); } })); logger.debug(`${NAME}: Complete.`); if (isKHRTextureBasisu) { document.createExtension(KHRTextureBasisu).setRequired(true); } }); }