diff --git a/package-lock.json b/package-lock.json index 480db27..66fa64f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3776,6 +3776,73 @@ "node": ">=8" } }, + "node_modules/@gpu-tex-enc/astc": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@gpu-tex-enc/astc/-/astc-4.7.0.tgz", + "integrity": "sha512-YBeXSYM/WP71hAxLgFxpaF6VEe2dwkS9rjTtKMPRDJEJoja/NIHWrqScwJC600UyxY7kLsjFTek7W6WtYzyi8A==", + "dependencies": { + "cpu-features": "^0.0.9" + }, + "bin": { + "astcenc-darwin-arm64-avx2": "bin/darwin/astcenc", + "astcenc-darwin-arm64-sse2": "bin/darwin/astcenc", + "astcenc-darwin-arm64-sse4.1": "bin/darwin/astcenc", + "astcenc-darwin-x64-avx2": "bin/darwin/astcenc", + "astcenc-darwin-x64-sse2": "bin/darwin/astcenc", + "astcenc-darwin-x64-sse4.1": "bin/darwin/astcenc", + "astcenc-linux-x64-avx2": "bin/linux-x64/astcenc-avx2", + "astcenc-linux-x64-sse2": "bin/linux-x64/astcenc-sse2", + "astcenc-linux-x64-sse4.1": "bin/linux-x64/astcenc-sse4.1", + "astcenc-win32-arm64-avx2": "bin/win32-arm64/astcenc-neon.exe", + "astcenc-win32-arm64-sse2": "bin/win32-arm64/astcenc-neon.exe", + "astcenc-win32-arm64-sse4.1": "bin/win32-arm64/astcenc-neon.exe", + "astcenc-win32-x64-avx2": "bin/win32-x64/astcenc-avx2.exe", + "astcenc-win32-x64-sse2": "bin/win32-x64/astcenc-sse2.exe", + "astcenc-win32-x64-sse4.1": "bin/win32-x64/astcenc-sse4.1.exe" + } + }, + "node_modules/@gpu-tex-enc/basis": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/@gpu-tex-enc/basis/-/basis-1.16.3.tgz", + "integrity": "sha512-u5ooQXTb5UasL8rI2kWz1CMuSARKgvTK2TjQqnOTVWg5gW/HZu0hn+2WkgjkqHKgkxtVIHHJuN2esCu+EmnShQ==", + "dependencies": { + "cpu-features": "^0.0.9" + }, + "bin": { + "basisu-darwin-arm64": "bin/darwin-arm64/basisu", + "basisu-darwin-arm64-sse": "bin/darwin-arm64/basisu", + "basisu-darwin-x64": "bin/darwin-x64/basisu", + "basisu-darwin-x64-sse": "bin/darwin-x64/basisu-sse", + "basisu-linux-arm64": "bin/linux-arm64/basisu", + "basisu-linux-arm64-sse": "bin/linux-arm64/basisu", + "basisu-linux-x64": "bin/linux-x64/basisu", + "basisu-linux-x64-sse": "bin/linux-x64/basisu-sse", + "basisu-win32-x64": "bin/win32-x64/basisu.exe", + "basisu-win32-x64-sse": "bin/win32-x64/basisu-sse.exe" + } + }, + "node_modules/@gpu-tex-enc/bc": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@gpu-tex-enc/bc/-/bc-1.0.11.tgz", + "integrity": "sha512-Nxp3uUS3MG02XMA28uwey9aLNltW0lxRV4gKqrckYGEC4V5R8MG29LXQcW7hJ3xIqhUG2kvt0E80tbq6WzO9Cg==", + "bin": { + "bc7enc-darwin-arm64": "bin/darwin-arm64/bc7enc", + "bc7enc-darwin-x64": "bin/darwin-x64/bc7enc", + "bc7enc-linux": "bin/linux/bc7enc", + "bc7enc-win32": "bin/win32/bc7enc.exe" + } + }, + "node_modules/@gpu-tex-enc/etc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gpu-tex-enc/etc/-/etc-1.0.3.tgz", + "integrity": "sha512-KRGP0qua3bzj1wdNlkFxg/TV3beGCW5iOlFjVJ90+osDVqYBLd1BsEqRSlqxd3b/gG1S7dL/gX4igqZtUDobKQ==", + "bin": { + "etc2comp-darwin-arm64": "bin/darwin-arm64/EtcTool", + "etc2comp-darwin-x64": "bin/darwin-x64/EtcTool", + "etc2comp-linux": "bin/linux/EtcTool", + "etc2comp-win32": "bin/win32/EtcTool.exe" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -10816,6 +10883,14 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buildcheck": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", + "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -12093,6 +12168,19 @@ } } }, + "node_modules/cpu-features": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", + "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", + "hasInstallScript": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.17.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -15571,7 +15659,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -16137,6 +16224,132 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gpu-tex-enc": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/gpu-tex-enc/-/gpu-tex-enc-1.2.4.tgz", + "integrity": "sha512-1dRld8Lovtw1xaHH5PEz/SUTiC+1SFS+C71psupWOfgoRknTBdSIdARCP5IwPUlvVdKeRdMK8HKD+u/asOjt5g==", + "dependencies": { + "@gpu-tex-enc/astc": "^4.7.0", + "@gpu-tex-enc/basis": "^1.16.3", + "@gpu-tex-enc/bc": "^1.0.11", + "@gpu-tex-enc/etc": "^1.0.3", + "yargs": "^17.7.2" + }, + "bin": { + "gputexenc": "src/cli.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "sharp": "^0.33.0" + } + }, + "node_modules/gpu-tex-enc/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gpu-tex-enc/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/gpu-tex-enc/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gpu-tex-enc/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gpu-tex-enc/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gpu-tex-enc/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gpu-tex-enc/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gpu-tex-enc/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/gpu-tex-enc/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -23992,6 +24205,11 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/nan": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -27255,7 +27473,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -31782,7 +31999,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -31826,7 +32042,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -31944,6 +32159,7 @@ "fluent-ffmpeg": "^2.1.3", "fs-extra": "^11.2.0", "glob": "^10.4.1", + "gpu-tex-enc": "^1.2.4", "maxrects-packer": "^2.7.3", "merge": "^2.1.1", "minimatch": "9.0.4", diff --git a/packages/assetpack/package.json b/packages/assetpack/package.json index d9846ea..5772199 100644 --- a/packages/assetpack/package.json +++ b/packages/assetpack/package.json @@ -69,6 +69,7 @@ "fluent-ffmpeg": "^2.1.3", "fs-extra": "^11.2.0", "glob": "^10.4.1", + "gpu-tex-enc": "^1.2.4", "maxrects-packer": "^2.7.3", "merge": "^2.1.1", "minimatch": "9.0.4", diff --git a/packages/assetpack/src/image/compress.ts b/packages/assetpack/src/image/compress.ts index 277ed0b..c94d361 100644 --- a/packages/assetpack/src/image/compress.ts +++ b/packages/assetpack/src/image/compress.ts @@ -1,8 +1,10 @@ import sharp from 'sharp'; import { checkExt, createNewAssetAt } from '../core/index.js'; +import { compressGpuTextures } from './utils/compressGpuTextures.js'; import { compressSharp } from './utils/compressSharp.js'; import { resolveOptions } from './utils/resolveOptions.js'; +import type { AstcOptions, BasisOptions, BcOptions } from 'gpu-tex-enc'; import type { AvifOptions, JpegOptions, PngOptions, WebpOptions } from 'sharp'; import type { Asset, AssetPipe, PluginOptions } from '../core/index.js'; @@ -10,6 +12,9 @@ type CompressJpgOptions = Omit; type CompressWebpOptions = Omit; type CompressAvifOptions = Omit; type CompressPngOptions = Omit; +type CompressBc7Options = BcOptions; +type CompressAstcOptions = AstcOptions; +type CompressBasisOptions = BasisOptions; export interface CompressOptions extends PluginOptions { @@ -17,6 +22,9 @@ export interface CompressOptions extends PluginOptions webp?: CompressWebpOptions | boolean; avif?: CompressAvifOptions | boolean; jpg?: CompressJpgOptions | boolean; + bc7?: CompressBc7Options | boolean; + astc?: CompressAstcOptions | boolean; + basis?: CompressBasisOptions | boolean; } export interface CompressImageData @@ -26,6 +34,13 @@ export interface CompressImageData sharpImage: sharp.Sharp; } +export interface CompressImageDataResult +{ + format: CompressImageData['format'] | '.bc7.dds' | '.astc.ktx' | '.basis.ktx2'; + resolution: number; + buffer: Buffer; +} + export function compress(options: CompressOptions = {}): AssetPipe { const compress = resolveOptions(options, { @@ -33,6 +48,9 @@ export function compress(options: CompressOptions = {}): AssetPipe(compress.avif, { }); + + compress.bc7 = resolveOptions(compress.bc7, { + + }); + + compress.astc = resolveOptions(compress.astc, { + + }); + + compress.basis = resolveOptions(compress.basis, { + + }); } return { @@ -82,7 +112,10 @@ export function compress(options: CompressOptions = {}): AssetPipe { @@ -95,6 +128,8 @@ export function compress(options: CompressOptions = {}): AssetPipe image.sharpImage.toBuffer().then((buffer) => - { - newAssets[i].buffer = buffer; - })); - - await Promise.all(promises); + } return newAssets; } catch (error) diff --git a/packages/assetpack/src/image/utils/compressGpuTextures.ts b/packages/assetpack/src/image/utils/compressGpuTextures.ts new file mode 100644 index 0000000..7255907 --- /dev/null +++ b/packages/assetpack/src/image/utils/compressGpuTextures.ts @@ -0,0 +1,117 @@ +import { + generateASTC as astc, + generateBasis as basis, + generateBC as bc, +} from 'gpu-tex-enc'; +import crypto from 'node:crypto'; +import { promises as fs } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { extname, join } from 'node:path'; + +import type { + AstcOptions, + BasisOptions, + BcOptions +} from 'gpu-tex-enc'; +import type sharp from 'sharp'; +import type { CompressImageData, CompressImageDataResult, CompressOptions } from '../compress.js'; + +export async function compressGpuTextures( + image: CompressImageData, + options: CompressOptions, +): Promise +{ + let compressed: CompressImageDataResult[] = []; + + if (!options.astc && !options.bc7 && !options.basis) + { + return compressed; + } + + const tmpDir = await fs.mkdtemp(join(tmpdir(), 'assetpack-tex-')); + + try + { + const imagePath = join(tmpDir, `${crypto.randomUUID()}.png`); + + const sharpImage = image.sharpImage; + const pngImage = image.format !== '.png' + ? sharpImage.clone().png({ quality: 100, force: true }) // most texture generators only support PNG. + : sharpImage.clone(); + + await pngImage.toFile(imagePath); + + if (options.astc) + { + const astcOpts = typeof options.astc === 'boolean' ? {} : options.astc as AstcOptions; + + compressed.push({ + format: '.astc.ktx', + resolution: 1, + image: astc(imagePath, astcOpts.blocksize, astcOpts.quality, astcOpts.colorProfile, astcOpts.options), + }); + } + + if (options.bc7) + { + const bc7Opts = typeof options.bc7 === 'boolean' ? {} : options.bc7 as BcOptions; + + compressed.push({ + format: '.bc7.dds', + resolution: 1, + image: bc(imagePath, 'BC7', true, bc7Opts.options), + }); + } + + if (options.basis) + { + const basisOpts = typeof options.basis === 'boolean' ? {} : options.basis as BasisOptions; + const adjustedImagePath = await adjustImageSize(pngImage, imagePath); + + compressed.push({ + format: '.basis.ktx2', + resolution: 1, + image: basis(adjustedImagePath, 'UASTC', true, basisOpts.options), + }); + } + + const results = await Promise.all(compressed.map(async (result) => ({ + ...result, + buffer: await fs.readFile(await result.image) + }))); + + compressed = results; + } + finally + { + await fs.rm(tmpDir, { recursive: true, force: true }); + } + + return compressed; +} + +async function adjustImageSize(sharpImage: sharp.Sharp, imagePath: string): Promise +{ + const metadata = await sharpImage.metadata(); + const { width = 0, height = 0 } = metadata; + const right = width % 4 !== 0 ? 4 - (width % 4) : 0; + const bottom = height % 4 !== 0 ? 4 - (height % 4) : 0; + + if (right > 0 || bottom > 0) + { + const adjustedImagePath = `${imagePath}.1${extname(imagePath)}`; + + await sharpImage + .clone() + .extend({ + bottom, + right, + background: { r: 0, g: 0, b: 0, alpha: 0 }, + }) + .toFile(adjustedImagePath); + + return adjustedImagePath; + } + + return imagePath; +} diff --git a/packages/assetpack/src/image/utils/compressSharp.ts b/packages/assetpack/src/image/utils/compressSharp.ts index 2d8ea98..1d3043e 100644 --- a/packages/assetpack/src/image/utils/compressSharp.ts +++ b/packages/assetpack/src/image/utils/compressSharp.ts @@ -1,13 +1,12 @@ import type { AvifOptions, JpegOptions, PngOptions, WebpOptions } from 'sharp'; -import type { CompressImageData, CompressOptions } from '../compress.js'; +import type { CompressImageData, CompressImageDataResult, CompressOptions } from '../compress.js'; export async function compressSharp( image: CompressImageData, options: CompressOptions -): Promise +): Promise { const compressed: CompressImageData[] = []; - const sharpImage = image.sharpImage; if (image.format === '.png' && options.png) @@ -46,5 +45,10 @@ export async function compressSharp( }); } - return compressed; + const results = await Promise.all(compressed.map(async (result) => ({ + ...result, + buffer: await result.sharpImage.toBuffer() + }))); + + return results; } diff --git a/packages/assetpack/src/spine/AtlasView.ts b/packages/assetpack/src/spine/AtlasView.ts index 67d0876..f62ee0f 100644 --- a/packages/assetpack/src/spine/AtlasView.ts +++ b/packages/assetpack/src/spine/AtlasView.ts @@ -10,7 +10,7 @@ export class AtlasView getTextures(): string[] { - const regex = /^.+?(?:\.png|\.jpg|\.jpeg|\.webp|\.avif)$/gm; + const regex = /^.+?(?:\.png|\.jpg|\.jpeg|\.webp|\.avif|\.dds|\.ktx)$/gm; const matches = this.rawAtlas.match(regex); diff --git a/packages/assetpack/src/spine/spineAtlasCompress.ts b/packages/assetpack/src/spine/spineAtlasCompress.ts index 0781256..85b2863 100644 --- a/packages/assetpack/src/spine/spineAtlasCompress.ts +++ b/packages/assetpack/src/spine/spineAtlasCompress.ts @@ -15,6 +15,9 @@ export function spineAtlasCompress(_options?: SpineAtlasCompressOptions): AssetP png: true, webp: true, avif: false, + astc: false, + bc7: false, + basis: false }, ..._options, }, @@ -28,23 +31,24 @@ export function spineAtlasCompress(_options?: SpineAtlasCompressOptions): AssetP }, async transform(asset: Asset, options) { - const formats = []; + const formats: Array<[format: string, extension: string]> = []; - if (options.avif)formats.push('avif'); - if (options.png)formats.push('png'); - if (options.webp)formats.push('webp'); + if (options.avif) formats.push(['avif', '.avif']); + if (options.png) formats.push(['png', '.png']); + if (options.webp) formats.push(['webp', '.webp']); + if (options.astc) formats.push(['astc', '.astc.ktx']); + if (options.bc7) formats.push(['bc7', '.bc7.dds']); + if (options.basis) formats.push(['basis', '.basis.ktx2']); const atlas = new AtlasView(asset.buffer); const textures = atlas.getTextures(); - const assets = formats.map((format) => + const assets = formats.map(([format, extension]) => { - const extension = `.${format}`; - const newAtlas = new AtlasView(asset.buffer); - const newFileName = swapExt(asset.filename, `${extension}.atlas`); + const newFileName = swapExt(asset.filename, `.${format}.atlas`); textures.forEach((texture) => { diff --git a/packages/assetpack/src/texture-packer/texturePackerCompress.ts b/packages/assetpack/src/texture-packer/texturePackerCompress.ts index c63cb36..3d666f5 100644 --- a/packages/assetpack/src/texture-packer/texturePackerCompress.ts +++ b/packages/assetpack/src/texture-packer/texturePackerCompress.ts @@ -16,6 +16,9 @@ export function texturePackerCompress( png: true, webp: true, avif: false, + astc: false, + bc7: false, + basis: false }, ..._options, }, @@ -33,29 +36,30 @@ export function texturePackerCompress( }, async transform(asset: Asset, options) { - const formats = []; + const formats: Array<[format: string, extension: string]> = []; - if (options.avif) formats.push('avif'); - if (options.png) formats.push('png'); - if (options.webp) formats.push('webp'); + if (options.avif) formats.push(['avif', '.avif']); + if (options.png) formats.push(['png', '.png']); + if (options.webp) formats.push(['webp', '.webp']); + if (options.astc) formats.push(['astc', '.astc.ktx']); + if (options.bc7) formats.push(['bc7', '.bc7.dds']); + if (options.basis) formats.push(['basis', '.basis.ktx2']); const json = JSON.parse(asset.buffer.toString()); - const assets = formats.map((format) => + const assets = formats.map(([format, extension]) => { - const extension = `.${format}`; - - const newFileName = swapExt(asset.filename, `${extension}.json`); - - json.meta.image = swapExt(json.meta.image, extension); + const newFileName = swapExt(asset.filename, `.${format}.json`); const newAsset = createNewAssetAt(asset, newFileName); const newJson = JSON.parse(JSON.stringify(json)); + newJson.meta.image = swapExt(newJson.meta.image, extension); + if (newJson.meta.related_multi_packs) { newJson.meta.related_multi_packs = (newJson.meta.related_multi_packs as string[]).map((pack) => - swapExt(pack, `${extension}.json`), + swapExt(pack, `.${format}.json`), ); } diff --git a/packages/assetpack/test/image/Compress.test.ts b/packages/assetpack/test/image/Compress.test.ts index f68a954..d75c0de 100644 --- a/packages/assetpack/test/image/Compress.test.ts +++ b/packages/assetpack/test/image/Compress.test.ts @@ -41,6 +41,9 @@ describe('Compress', () => webp: true, avif: true, jpg: true, + astc: true, + basis: true, + bc7: false // Disabled due to the absence of libomp on the GitHub Actions runner: "error while loading shared libraries: libomp.so.5" }), ] }); @@ -50,10 +53,17 @@ describe('Compress', () => expect(existsSync(`${outputDir}/testPng.png`)).toBe(true); expect(existsSync(`${outputDir}/testPng.webp`)).toBe(true); expect(existsSync(`${outputDir}/testPng.avif`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.astc.ktx`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.basis.ktx2`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.bc7.dds`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.jpg`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.webp`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.avif`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.png`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.astc.ktx`)).toBe(true); + expect(existsSync(`${outputDir}/testJpg.basis.ktx2`)).toBe(true); + expect(existsSync(`${outputDir}/testJpg.bc7.dds`)).toBe(false); }); it('should compress png with 1 plugin', async () => @@ -88,6 +98,9 @@ describe('Compress', () => webp: true, jpg: false, avif: true, + astc: true, + basis: true, + bc7: false }), ], assetSettings: [{ @@ -105,9 +118,16 @@ describe('Compress', () => expect(existsSync(`${outputDir}/testPng.png`)).toBe(true); expect(existsSync(`${outputDir}/testPng.webp`)).toBe(true); expect(existsSync(`${outputDir}/testPng.avif`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.astc.ktx`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.basis.ktx2`)).toBe(true); + expect(existsSync(`${outputDir}/testPng.bc7.dds`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.jpg`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.webp`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.avif`)).toBe(true); + expect(existsSync(`${outputDir}/testJpg.astc.ktx`)).toBe(true); + expect(existsSync(`${outputDir}/testJpg.basis.ktx2`)).toBe(true); + expect(existsSync(`${outputDir}/testJpg.bc7.dds`)).toBe(false); expect(existsSync(`${outputDir}/testJpg.png`)).toBe(false); }); @@ -150,9 +170,16 @@ describe('Compress', () => expect(existsSync(`${outputDir}/testPng.png`)).toBe(true); expect(existsSync(`${outputDir}/testPng.webp`)).toBe(false); expect(existsSync(`${outputDir}/testPng.avif`)).toBe(false); + expect(existsSync(`${outputDir}/testPng.astc.ktx`)).toBe(false); + expect(existsSync(`${outputDir}/testPng.bc7.dds`)).toBe(false); + expect(existsSync(`${outputDir}/testPng.basis.ktx2`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.jpg`)).toBe(true); expect(existsSync(`${outputDir}/testJpg.webp`)).toBe(false); expect(existsSync(`${outputDir}/testJpg.avif`)).toBe(false); expect(existsSync(`${outputDir}/testJpg.png`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.astc.ktx`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.bc7.dds`)).toBe(false); + expect(existsSync(`${outputDir}/testJpg.basis.ktx2`)).toBe(false); }); }); diff --git a/packages/assetpack/test/manifest/Manifest.test.ts b/packages/assetpack/test/manifest/Manifest.test.ts index cd29400..31f344e 100644 --- a/packages/assetpack/test/manifest/Manifest.test.ts +++ b/packages/assetpack/test/manifest/Manifest.test.ts @@ -128,8 +128,9 @@ describe('Manifest', () => jpg: true, webp: true, avif: false, + astc: true, }), - texturePackerCompress(), + texturePackerCompress({ astc: true }), pixiManifest(), spineAtlasManifestMod(), ], @@ -166,8 +167,10 @@ describe('Manifest', () => src: [ 'bundle/sprite@0.5x.webp', 'bundle/sprite@0.5x.png', + 'bundle/sprite@0.5x.astc.ktx', 'bundle/sprite.webp', 'bundle/sprite.png', + 'bundle/sprite.astc.ktx' ], data: { tags: { @@ -177,10 +180,14 @@ describe('Manifest', () => }, { alias: ['bundle/tps'], - src: ['bundle/tps-0@0.5x.webp.json', + src: [ + 'bundle/tps-0@0.5x.webp.json', 'bundle/tps-0@0.5x.png.json', + 'bundle/tps-0@0.5x.astc.json', 'bundle/tps-0.webp.json', - 'bundle/tps-0.png.json'], + 'bundle/tps-0.png.json', + 'bundle/tps-0.astc.json' + ], data: { tags: { tps: true, @@ -276,6 +283,7 @@ describe('Manifest', () => jpg: true, webp: true, avif: false, + astc: true, }), pixiManifest(), ], @@ -304,8 +312,10 @@ describe('Manifest', () => src: [ 'defaultFolder/mip/sprite0@0.5x.webp', 'defaultFolder/mip/sprite0@0.5x.png', + 'defaultFolder/mip/sprite0@0.5x.astc.ktx', 'defaultFolder/mip/sprite0.webp', 'defaultFolder/mip/sprite0.png', + 'defaultFolder/mip/sprite0.astc.ktx', ], data: { tags: {}, @@ -316,8 +326,10 @@ describe('Manifest', () => src: [ 'defaultFolder/mip/sprite1@0.5x.webp', 'defaultFolder/mip/sprite1@0.5x.png', + 'defaultFolder/mip/sprite1@0.5x.astc.ktx', 'defaultFolder/mip/sprite1.webp', 'defaultFolder/mip/sprite1.png', + 'defaultFolder/mip/sprite1.astc.ktx', ], data: { tags: {}, @@ -328,8 +340,10 @@ describe('Manifest', () => src: [ 'defaultFolder/mip/sprite2@0.5x.webp', 'defaultFolder/mip/sprite2@0.5x.png', + 'defaultFolder/mip/sprite2@0.5x.astc.ktx', 'defaultFolder/mip/sprite2.webp', 'defaultFolder/mip/sprite2.png', + 'defaultFolder/mip/sprite2.astc.ktx', ], data: { tags: {}, @@ -457,6 +471,7 @@ describe('Manifest', () => compress({ webp: true, png: true, + astc: true, }), pixiManifest({ createShortcuts: true, @@ -488,8 +503,10 @@ describe('Manifest', () => src: [ 'folder/sprite@0.5x.webp', 'folder/sprite@0.5x.png', + 'folder/sprite@0.5x.astc.ktx', 'folder/sprite.webp', 'folder/sprite.png', + 'folder/sprite.astc.ktx', ], }, { @@ -671,6 +688,7 @@ describe('Manifest', () => compress({ webp: true, png: true, + astc: true, }), pixiManifest({ createShortcuts: true, @@ -702,8 +720,10 @@ describe('Manifest', () => src: [ 'folder/sprite@0.5x.webp', 'folder/sprite@0.5x.png', + 'folder/sprite@0.5x.astc.ktx', 'folder/sprite.webp', 'folder/sprite.png', + 'folder/sprite.astc.ktx', ], }, { @@ -817,6 +837,7 @@ describe('Manifest', () => compress({ webp: true, png: true, + astc: true, }), pixiManifest({ createShortcuts: false, @@ -848,8 +869,10 @@ describe('Manifest', () => src: [ 'folder/sprite@0.5x.webp', 'folder/sprite@0.5x.png', + 'folder/sprite@0.5x.astc.ktx', 'folder/sprite.webp', 'folder/sprite.png', + 'folder/sprite.astc.ktx', ], }, { diff --git a/packages/assetpack/test/spine/spineAtlasAll.test.ts b/packages/assetpack/test/spine/spineAtlasAll.test.ts index 89de492..eefc4d5 100644 --- a/packages/assetpack/test/spine/spineAtlasAll.test.ts +++ b/packages/assetpack/test/spine/spineAtlasAll.test.ts @@ -49,6 +49,7 @@ describe('Spine Atlas All', () => png: true, webp: true, jpg: true, + astc: true, }), mipmap({ resolutions: { default: 1, low: 0.5 }, @@ -59,6 +60,7 @@ describe('Spine Atlas All', () => spineAtlasCompress({ png: true, webp: true, + astc: true, }), ] }); @@ -66,6 +68,7 @@ describe('Spine Atlas All', () => await assetpack.run(); const rawAtlasWebpHalf = readFileSync(`${outputDir}/dragon@0.5x.webp.atlas`); + const rawAtlasAstcHalf = readFileSync(`${outputDir}/dragon@0.5x.astc.atlas`); const rawAtlasHalf = readFileSync(`${outputDir}/dragon@0.5x.png.atlas`); expect(rawAtlasHalf.includes('dragon@0.5x.png')).toBeTruthy(); @@ -73,6 +76,9 @@ describe('Spine Atlas All', () => expect(rawAtlasWebpHalf.includes('dragon@0.5x.webp')).toBeTruthy(); expect(rawAtlasWebpHalf.includes('dragon2@0.5x.webp')).toBeTruthy(); + + expect(rawAtlasAstcHalf.includes('dragon@0.5x.astc.ktx')).toBeTruthy(); + expect(rawAtlasAstcHalf.includes('dragon2@0.5x.astc.ktx')).toBeTruthy(); }); it('should correctly create files when Mipmap and CacheBuster are used', async () => @@ -114,6 +120,7 @@ describe('Spine Atlas All', () => png: true, webp: true, jpg: true, + astc: true }), spineAtlasMipmap({ resolutions: { default: 1, low: 0.5 }, @@ -121,6 +128,7 @@ describe('Spine Atlas All', () => spineAtlasCompress({ png: true, webp: true, + astc: true }), cacheBuster(), spineAtlasCacheBuster() @@ -128,19 +136,21 @@ describe('Spine Atlas All', () => }); await assetpack.run(); - const globPath = `${outputDir}/*.{atlas,png,webp}`; + const globPath = `${outputDir}/*.{atlas,png,webp,astc.ktx}`; const files = await glob(globPath); // need two sets of files - expect(files.length).toBe(12); - expect(files.filter((file) => file.endsWith('.atlas')).length).toBe(4); + expect(files.length).toBe(18); + expect(files.filter((file) => file.endsWith('.atlas')).length).toBe(6); expect(files.filter((file) => file.endsWith('.png')).length).toBe(4); expect(files.filter((file) => file.endsWith('.webp')).length).toBe(4); + expect(files.filter((file) => file.endsWith('.astc.ktx')).length).toBe(4); expect(files.filter((file) => file.endsWith('.jpg')).length).toBe(0); const atlasFiles = files.filter((file) => file.endsWith('.atlas')); const pngFiles = files.filter((file) => file.endsWith('.png')); const webpFiles = files.filter((file) => file.endsWith('.webp')); + const astcFiles = files.filter((file) => file.endsWith('.astc.ktx')); // check that the files are correct atlasFiles.forEach((atlasFile) => @@ -149,6 +159,7 @@ describe('Spine Atlas All', () => const isHalfSize = atlasFile.includes('@0.5x'); const isWebp = atlasFile.includes('.webp'); const isPng = atlasFile.includes('.png'); + const isAstc = atlasFile.includes('.astc'); const checkFiles = (fileList: string[], isHalfSize: boolean, isFileType: boolean) => { @@ -157,7 +168,7 @@ describe('Spine Atlas All', () => // remove the outputDir file = file.replace(`${outputDir}/`, ''); const isFileHalfSize = file.includes('@0.5x'); - const isFileFileType = file.includes(isWebp ? '.webp' : '.png'); + const isFileFileType = file.includes(isWebp ? '.webp' : isAstc ? '.astc' : '.png'); const shouldExist = isHalfSize === isFileHalfSize && isFileType === isFileFileType; expect(rawAtlas.includes(file)).toBe(shouldExist); @@ -174,6 +185,10 @@ describe('Spine Atlas All', () => { checkFiles(pngFiles, true, true); } + else if (isAstc) + { + checkFiles(astcFiles, true, true); + } } else if (isWebp) @@ -184,6 +199,10 @@ describe('Spine Atlas All', () => { checkFiles(pngFiles, false, true); } + else if (isAstc) + { + checkFiles(astcFiles, false, true); + } }); }); }); diff --git a/packages/assetpack/test/spine/spineAtlasCompress.test.ts b/packages/assetpack/test/spine/spineAtlasCompress.test.ts index cecfa68..5c379dc 100644 --- a/packages/assetpack/test/spine/spineAtlasCompress.test.ts +++ b/packages/assetpack/test/spine/spineAtlasCompress.test.ts @@ -45,16 +45,19 @@ describe('Spine Atlas Compress', () => png: true, jpg: true, webp: true, + astc: true }), spineAtlasCompress({ png: true, webp: true, + astc: true }), ] }); await assetpack.run(); + const rawAtlasAstc = readFileSync(`${outputDir}/dragon.astc.atlas`); const rawAtlasWebp = readFileSync(`${outputDir}/dragon.webp.atlas`); const rawAtlas = readFileSync(`${outputDir}/dragon.png.atlas`); @@ -63,5 +66,8 @@ describe('Spine Atlas Compress', () => expect(rawAtlasWebp.includes('dragon.webp')).toBeTruthy(); expect(rawAtlasWebp.includes('dragon2.webp')).toBeTruthy(); + + expect(rawAtlasAstc.includes('dragon.astc.ktx')).toBeTruthy(); + expect(rawAtlasAstc.includes('dragon2.astc.ktx')).toBeTruthy(); }); }); diff --git a/packages/assetpack/test/texture-packer/texturePackerAll.test.ts b/packages/assetpack/test/texture-packer/texturePackerAll.test.ts index 44b31ef..3d39700 100644 --- a/packages/assetpack/test/texture-packer/texturePackerAll.test.ts +++ b/packages/assetpack/test/texture-packer/texturePackerAll.test.ts @@ -39,10 +39,12 @@ describe('Texture Packer All', () => png: true, jpg: true, webp: true, + astc: true }), texturePackerCompress({ png: true, webp: true, + astc: true }), cacheBuster(), texturePackerCacheBuster() @@ -51,19 +53,21 @@ describe('Texture Packer All', () => await assetpack.run(); - const globPath = `${outputDir}/*.{json,png,webp}`; + const globPath = `${outputDir}/*.{json,png,webp,astc.ktx}`; const files = await glob(globPath); // need two sets of files - expect(files.length).toBe(8); - expect(files.filter((file) => file.endsWith('.json')).length).toBe(4); + expect(files.length).toBe(12); + expect(files.filter((file) => file.endsWith('.json')).length).toBe(6); expect(files.filter((file) => file.endsWith('.png')).length).toBe(2); expect(files.filter((file) => file.endsWith('.webp')).length).toBe(2); + expect(files.filter((file) => file.endsWith('.astc.ktx')).length).toBe(2); expect(files.filter((file) => file.endsWith('.jpg')).length).toBe(0); const jsonFiles = files.filter((file) => file.endsWith('.json')); const pngFiles = files.filter((file) => file.endsWith('.png')); const webpFiles = files.filter((file) => file.endsWith('.webp')); + const astcFiles = files.filter((file) => file.endsWith('.astc.ktx')); // check that the files are correct jsonFiles.forEach((jsonFile) => @@ -72,6 +76,7 @@ describe('Texture Packer All', () => const isHalfSize = jsonFile.includes('@0.5x'); const isWebp = jsonFile.includes('.webp'); const isPng = jsonFile.includes('.png'); + const isAstc = jsonFile.includes('.astc'); const checkFiles = (fileList: string[], isHalfSize: boolean, isFileType: boolean) => { @@ -80,7 +85,7 @@ describe('Texture Packer All', () => // remove the outputDir file = file.replace(`${outputDir}/`, ''); const isFileHalfSize = file.includes('@0.5x'); - const isFileFileType = file.includes(isWebp ? '.webp' : '.png'); + const isFileFileType = file.includes(isWebp ? '.webp' : isAstc ? '.astc.ktx' : '.png'); const shouldExist = isHalfSize === isFileHalfSize && isFileType === isFileFileType; shouldExist ? expect(rawJson.meta.image).toEqual(file) : expect(rawJson.meta.image).not.toEqual(file); @@ -97,6 +102,10 @@ describe('Texture Packer All', () => { checkFiles(pngFiles, true, true); } + else if (isAstc) + { + checkFiles(astcFiles, true, true); + } } else if (isWebp) @@ -107,6 +116,10 @@ describe('Texture Packer All', () => { checkFiles(pngFiles, false, true); } + else if (isAstc) + { + checkFiles(astcFiles, false, true); + } }); }); }); diff --git a/packages/assetpack/test/texture-packer/texturePackerCompress.test.ts b/packages/assetpack/test/texture-packer/texturePackerCompress.test.ts index 23784b6..d9e867f 100644 --- a/packages/assetpack/test/texture-packer/texturePackerCompress.test.ts +++ b/packages/assetpack/test/texture-packer/texturePackerCompress.test.ts @@ -22,6 +22,8 @@ describe('Texture Packer Compression', () => png: true, jpg: true, webp: true, + astc: true, + basis: true }; const assetpack = new AssetPack({ @@ -43,8 +45,12 @@ describe('Texture Packer Compression', () => const sheetPng = fs.readJSONSync(`${outputDir}/sprites.png.json`); const sheetWebp = fs.readJSONSync(`${outputDir}/sprites.webp.json`); + const sheetAstc = fs.readJSONSync(`${outputDir}/sprites.astc.json`); + const sheetBasis = fs.readJSONSync(`${outputDir}/sprites.basis.json`); expect(sheetPng.meta.image).toEqual(`sprites.png`); expect(sheetWebp.meta.image).toEqual(`sprites.webp`); + expect(sheetAstc.meta.image).toEqual(`sprites.astc.ktx`); + expect(sheetBasis.meta.image).toEqual(`sprites.basis.ktx2`); }); }); diff --git a/packages/assetpack/test/texture-packer/texturePackerManifest.test.ts b/packages/assetpack/test/texture-packer/texturePackerManifest.test.ts index f7c626e..7245c96 100644 --- a/packages/assetpack/test/texture-packer/texturePackerManifest.test.ts +++ b/packages/assetpack/test/texture-packer/texturePackerManifest.test.ts @@ -61,9 +61,14 @@ describe('Texture Packer Compression', () => const outputDir = getOutputDir(pkg, testName); createTPSFolder(testName, pkg); + const compressOpt = { + astc: true, + basis: true + }; const assetpack = new AssetPack({ - entry: inputDir, cacheLocation: getCacheDir(pkg, testName), + entry: inputDir, + cacheLocation: getCacheDir(pkg, testName), output: outputDir, cache: false, pipes: [ @@ -73,8 +78,8 @@ describe('Texture Packer Compression', () => maximumTextureSize: 512, }, }), - compress(), - texturePackerCompress(), + compress(compressOpt), + texturePackerCompress(compressOpt), pixiManifest(), ] }); @@ -91,8 +96,12 @@ describe('Texture Packer Compression', () => src: [ 'sprites-0@0.5x.webp.json', 'sprites-0@0.5x.png.json', + 'sprites-0@0.5x.basis.json', + 'sprites-0@0.5x.astc.json', 'sprites-0.webp.json', 'sprites-0.png.json', + 'sprites-0.basis.json', + 'sprites-0.astc.json', ], data: { tags: { diff --git a/packages/assetpack/vitest.config.js b/packages/assetpack/vitest.config.js index 9529413..558d2fc 100644 --- a/packages/assetpack/vitest.config.js +++ b/packages/assetpack/vitest.config.js @@ -2,6 +2,14 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + poolOptions: { + threads: { + // Due to the cpu-features and vitest threads incompatibility, see: + // https://github.com/vitest-dev/vitest/issues/1982 + // https://github.com/vitest-dev/vitest/issues/740 + singleThread: true + }, + }, // ... }, }); diff --git a/packages/docs/docs/guide/pipes/compress.mdx b/packages/docs/docs/guide/pipes/compress.mdx index 9211bbb..1d91eb2 100644 --- a/packages/docs/docs/guide/pipes/compress.mdx +++ b/packages/docs/docs/guide/pipes/compress.mdx @@ -1,11 +1,13 @@ --- sidebar_position: 4 --- + import { ImageToggle } from '@site/src/components/ImageToggle'; # Compression The compress plugin uses the Sharp library to compress images into different formats, such as JPEG, PNG, WebP, and AVIF. This helps reduce file sizes while maintaining image quality, ensuring faster load times and better performance. +This plugin also supports compressing images using the ASTC, ETC, ETC2, BCn (DXTn) and Basis supercompressed (ETC1S, UASTC) texture compression standard. ## Example @@ -24,6 +26,9 @@ export default { png: { quality: 90 }, webp: { quality: 80, alphaQuality: 80, }, avif: false, + bc7: false, + astc: false, + basis: false, }), ] }; @@ -31,12 +36,15 @@ export default { ## API -| Option | Type | Description | -| ------ | ----------------- | -------------------------------------------------------------------------------------------------------------------- | -| jpg | `object \| false` | Any settings supported by [sharp jpeg](https://sharp.pixelplumbing.com/api-output#jpeg). | -| png | `object \| false` | Any settings supported by [sharp png](https://sharp.pixelplumbing.com/api-output#png). | -| webp | `object \| false` | Any settings supported by [sharp webp](https://sharp.pixelplumbing.com/api-output#webp). | -| avif | `object \| false` | Any settings supported by [sharp avif](https://sharp.pixelplumbing.com/api-output#avif).
Defaults to `false`. | +| Option | Type | Description | +| ------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| jpg | `object \| false` | Any settings supported by [sharp jpeg](https://sharp.pixelplumbing.com/api-output#jpeg). | +| png | `object \| false` | Any settings supported by [sharp png](https://sharp.pixelplumbing.com/api-output#png). | +| webp | `object \| false` | Any settings supported by [sharp webp](https://sharp.pixelplumbing.com/api-output#webp). | +| avif | `object \| false` | Any settings supported by [sharp avif](https://sharp.pixelplumbing.com/api-output#avif).
Defaults to `false`. | +| bc7 | `object \| false` | Any settings supported by [bc7](https://github.com/ddenisyuk/gpu-tex-enc/blob/main/packages/%40gpu-tex-enc/bc/README.md).
Defaults to `false`. | +| astc | `object \| false` | Any settings supported by [astc](https://github.com/ddenisyuk/gpu-tex-enc/blob/main/packages/%40gpu-tex-enc/astc/README.md).
Defaults to `false`. | +| basis | `object \| false` | Any settings supported by [basis](https://github.com/ddenisyuk/gpu-tex-enc/blob/main/packages/%40gpu-tex-enc/basis/README.md).
Defaults to `false`. | ## Tags @@ -45,4 +53,5 @@ export default { | `nc` | `both` | If present the image(s) will not be compressed. | ### Example + diff --git a/packages/docs/docs/guide/pipes/spine.mdx b/packages/docs/docs/guide/pipes/spine.mdx index 6d406c2..3885138 100644 --- a/packages/docs/docs/guide/pipes/spine.mdx +++ b/packages/docs/docs/guide/pipes/spine.mdx @@ -10,6 +10,7 @@ AssetPack provides several plugins for transforming spine files that utilise `at ## Spine Atlas Compress The `spineAtlasCompress` plugin uses the Sharp library to compress images into different formats, such as JPEG, PNG, WebP, and AVIF. This helps reduce file sizes while maintaining image quality, ensuring faster load times and better performance. +This plugin also supports compressing images using the ASTC, ETC, ETC2, BCn (DXTn) and Basis supercompressed (ETC1S, UASTC) texture compression standard. This plugin should be used in conjunction with the [compress](/docs/guide/pipes/compress) plugin to ensure that the atlas file is compressed as well as the images. @@ -27,6 +28,9 @@ const options = { png: { quality: 90 }, webp: { quality: 80, alphaQuality: 80, }, avif: false, + bc7: false, + astc: false, + basis: false, }; export default { diff --git a/packages/docs/docs/guide/pipes/texture-packer.mdx b/packages/docs/docs/guide/pipes/texture-packer.mdx index b93578e..ef01fc2 100644 --- a/packages/docs/docs/guide/pipes/texture-packer.mdx +++ b/packages/docs/docs/guide/pipes/texture-packer.mdx @@ -81,6 +81,7 @@ export default { ## Texture Packer Compress To compress the texture atlases you can use the `texturePackerCompress` plugin. This plugin uses the Sharp library to compress images into different formats, such as JPEG, PNG, WebP, and AVIF. This helps reduce file sizes while maintaining image quality, ensuring faster load times and better performance. +This plugin also supports compressing images using the ASTC, ETC, ETC2, BCn (DXTn) and Basis supercompressed (ETC1S, UASTC) texture compression standard. ### Example @@ -96,6 +97,9 @@ const options = { png: { quality: 90 }, webp: { quality: 80, alphaQuality: 80, }, avif: false, + bc7: false, + astc: false, + basis: false, }; export default {