Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

texture-compressor

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

texture-compressor - npm Package Compare versions

Comparing version 0.0.10 to 0.1.0

docs/example/example-astc-10x10.ktx

151

lib/compressWithASTC.js
// Native
const fs = require('fs');
const os = require('os');
const path = require('path');
// Vendor
const fsExtra = require('fs-extra');
const jimp = require('jimp');
// Arguments

@@ -31,7 +36,97 @@ const {

splitFlagAndValue,
getTempDirectory,
getTempFilename,
} = require('./utilities');
function processAndReturnAsBuffer(toolPath, toolFlags = [], outputPath) {
return createProcess(toolPath, toolFlags)
.then(() => fsExtra.readFile(outputPath));
}
function runCompression(flippedImage) {
// Bitrate flag (2.0 bbp = 8x8 blocksize)
const bitrateFlag = convertNumberToDecimalString(bitrate);
const blockSizes = ['4x4', '5x4', '5x5', '6x5', '6x6', '8x5', '8x6', '8x8', '10x5', '10x6', '10x8', '10x10', '12x10', '12x12'];
// Quality flag
const qualityOptions = ['-veryfast', '-fast', '-medium', '-thorough', '-exhaustive'];
const qualityPicker = (Math.floor(quality / 2.1));
const qualityFlag = qualityOptions[qualityPicker]; // One of the five options
// Flag mapping
const flagMapping = [
'-cl', flippedImage, // Encode with LDR-linear submode
output,
`${bitrateFlag}`,
'-j', os.cpus().length,
`${qualityFlag}`,
];
// Transparent mapping, tool doesn't accept empty flags
if (transparent) {
flagMapping.push('-alphablend');
}
const toolPath = path.join(compressionToolDirectory, 'astcenc');
const toolFlags = flags ? splitFlagAndValue(createFlagsForTool(flags)) : [];
const combinedFlags = [...flagMapping, ...toolFlags];
// Astcenc does not support writing to KTX by directly so we prepend it with a KTX header
processAndReturnAsBuffer(toolPath, combinedFlags, output)
.then((buffer) => {
// https://github.com/AnalyticalGraphicsInc/gltf-pipeline/blob/a28152503f28be88051de8df85f5f6a350169e8b/lib/compressTexture.js#L436-L485
const blockWidth = buffer.readUInt8(4);
const blockHeight = buffer.readUInt8(5);
const xsize = [buffer.readUInt8(7), buffer.readUInt8(8), buffer.readUInt8(9)];
const ysize = [buffer.readUInt8(10), buffer.readUInt8(11), buffer.readUInt8(12)];
const pixelHeight = xsize[0] + 256 * xsize[1] + 65536 * xsize[2];
const pixelWidth = ysize[0] + 256 * ysize[1] + 65536 * ysize[2];
const blockSize = `${blockWidth}x${blockHeight}`;
const glInternalFormat = 0x93B0 + blockSizes.indexOf(blockSize);
const glBaseInternalFormat = 0x1908; // gl.RGBA
const imageData = buffer.slice(16);
const imageSize = imageData.length;
// KTX header
const ktxHeader = Buffer.allocUnsafe(68);
const identifier = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A];
for (let i = 0; i < 12; i++) {
ktxHeader.writeUInt8(identifier[i], i);
}
ktxHeader.writeUInt32LE(0x04030201, 12); // endianness
ktxHeader.writeUInt32LE(0, 16); // glType
ktxHeader.writeUInt32LE(1, 20); // glTypeSize
ktxHeader.writeUInt32LE(0, 24); // glFormat
ktxHeader.writeUInt32LE(glInternalFormat, 28); // glInternalFormat
ktxHeader.writeUInt32LE(glBaseInternalFormat, 32); // glBaseInternalFormat
ktxHeader.writeUInt32LE(pixelWidth, 36); // pixelWidth
ktxHeader.writeUInt32LE(pixelHeight, 40); // pixelHeight
ktxHeader.writeUInt32LE(0, 44); // pixelDepth
ktxHeader.writeUInt32LE(0, 48); // numberOfArrayElements
ktxHeader.writeUInt32LE(1, 52); // numberOfFaces
ktxHeader.writeUInt32LE(1, 56); // numberOfMipmapLevels
ktxHeader.writeUInt32LE(0, 60); // bytesOfKeyValueData
ktxHeader.writeUInt32LE(imageSize, 64); // imageSize
console.log(blockSize, imageSize);
const result = Buffer.concat([ktxHeader, imageData]);
fs.writeFile(output, result, 'binary', (error) => {
if (error) {
console.error(error);
} else {
console.log(`Succesfully written file to ${output}`);
}
});
});
}
const compressWithASTC = () => {
const inputFileExtension = getFileExtension(input);
const outputFileExtension = getFileExtension(output);
const tempDirectory = getTempDirectory();
const tempFilename = getTempFilename();

@@ -45,31 +140,35 @@ if (!ASTC_SUPPORTED_OUTPUT_TYPES.includes(outputFileExtension)) {

if (ASTC_SUPPORTED_INPUT_TYPES.includes(inputFileExtension)) {
// Bitrate flag (2.0 bbp = 8x8 blocksize)
// 4x4, 5x4, 5x5, 6x5, 6x6, 8x5, 8x6, 8x8, 10x5, 10x6, 10x8, 10x10, 12x10, 12x12
const bitrateFlag = convertNumberToDecimalString(bitrate);
// File reading necssary for temporary Y flipping because Astcenc flips the image internally
jimp.read(input)
.then((file) => {
// Flip Y (Astcenc flips the image during encoding)
file.flip(false, true);
// Quality flag
const qualityOptions = ['-veryfast', '-fast', '-medium', '-thorough', '-exhaustive'];
const qualityPicker = (Math.floor(quality / 2.1));
const qualityFlag = qualityOptions[qualityPicker]; // One of the five options
// Write temporary file to a temporary directory
fsExtra.ensureDir(tempDirectory)
.then(() => {
file.getBuffer(jimp.AUTO, (error, buffer) => {
if (error) {
console.error(error);
}
// Flag mapping
const flagMapping = [
'-cl', input,
output,
`${bitrateFlag}`,
'-j', os.cpus().length,
`${qualityFlag}`,
];
// Write file out manually as jimp.write doesn't offer a callback after writing
fs.writeFile(`${tempDirectory}/${tempFilename}`, buffer, (error) => {
if (error) {
console.error(error);
} else {
// Compress flipped instead of the direct input
runCompression(`${tempDirectory}/${tempFilename}`);
// Transparent mapping, tool doesn't accept empty flags
if (transparent) {
flagMapping.push('-alphablend');
}
const toolPath = path.join(compressionToolDirectory, 'astcenc');
const toolFlags = flags ? splitFlagAndValue(createFlagsForTool(flags)) : [];
const combinedFlags = [...flagMapping, ...toolFlags];
createProcess(toolPath, combinedFlags);
// Clean up the temporary directory and the temporary image
fsExtra.remove(`${tempDirectory}/${tempFilename}`);
fsExtra.remove(tempDirectory);
}
});
});
});
})
.catch((error) => {
console.error(error);
});
} else {

@@ -76,0 +175,0 @@ console.error(`${inputFileExtension} is not supported.`);

21

lib/compressWithETC.js
// Native
const os = require('os');
const path = require('path');

@@ -49,15 +48,21 @@

} else if (compression === 'etc2') {
compressionFlag = (transparent) ? 'RGBA8' : 'RGB8';
compressionFlag = (transparent) ? 'ETC2_RGBA' : 'ETC2_RGB';
}
// Quality flag
const qualityOptions = ['etcfast', 'etcslow', 'etcfastperceptual', 'etcslowperceptual'];
const qualityPicker = (Math.floor(quality / 2.1));
const qualityFlag = qualityOptions[qualityPicker]; // One of the five options
// Flag mapping
const flagMapping = [
input,
'-format', `${compressionFlag}`,
'-effort', `${quality * 10.0}`,
'-jobs', os.cpus().length,
'-output', output,
'-i', input,
'-o', output,
'-f', `${compressionFlag}`,
'-q', `${qualityFlag}`,
'-square', '+',
'-pot', '+',
];
const toolPath = path.join(compressionToolDirectory, 'EtcTool');
const toolPath = path.join(compressionToolDirectory, 'PVRTexToolCLI');
const toolFlags = flags ? splitFlagAndValue(createFlagsForTool(flags)) : [];

@@ -64,0 +69,0 @@

@@ -46,11 +46,11 @@ // Native

if (transparent) {
if (bitrate === 2) {
if (parseFloat(bitrate) === 2) {
compressionFlag = 'PVRTC1_2';
} else if (bitrate === 4) {
} else if (parseFloat(bitrate) === 4) {
compressionFlag = 'PVRTC1_4';
}
} else if (!transparent) {
if (bitrate === 2) {
if (parseFloat(bitrate) === 2) {
compressionFlag = 'PVRTC1_2_RGB';
} else if (bitrate === 4) {
} else if (parseFloat(bitrate) === 4) {
compressionFlag = 'PVRTC1_4_RGB';

@@ -57,0 +57,0 @@ }

@@ -47,10 +47,3 @@ // Native

if (compression === 'dxt1') {
if (transparent && outputFileExtension === '.crn') {
console.log('Crunch compressor does not support DXT1 with alpha, the alpha channel will be ignored.');
compressionFlag = '-DXT1';
} else if (transparent) {
compressionFlag = '-DXT1A';
} else {
compressionFlag = '-DXT1';
}
compressionFlag = transparent ? '-DXT1A' : '-DXT1';
} else if (compression === 'dxt3') {

@@ -63,13 +56,6 @@ compressionFlag = '-DXT3';

// Quality flag
let CRNQualityFlag;
let DXTQualityFlag;
const qualityOptions = ['superfast', 'fast', 'normal', 'better', 'uber'];
const qualityPicker = (Math.floor(quality / 2.1));
const DXTQualityFlag = qualityOptions[qualityPicker]; // One of the five options
if (outputFileExtension === '.crn') {
CRNQualityFlag = quality * 25.5; // [0 - 255]
} else {
const qualityOptions = ['superfast', 'fast', 'normal', 'better', 'uber'];
const qualityPicker = (Math.floor(quality / 2.1));
DXTQualityFlag = qualityOptions[qualityPicker]; // One of the five options
}
// Flag mapping

@@ -79,5 +65,5 @@ const flagMapping = [

'-out', output,
'-fileformat', 'ktx',
`${compressionFlag}`,
CRNQualityFlag === Number ? '-quality' : '-dxtQuality',
CRNQualityFlag === Number ? `${CRNQualityFlag}` : `${DXTQualityFlag}`,
`${DXTQualityFlag}`,
'-helperThreads', os.cpus().length,

@@ -84,0 +70,0 @@ '-mipMode', 'None',

@@ -9,17 +9,17 @@ const constants = {

ASTC_SUPPORTED_INPUT_TYPES: ['.jpeg', '.jpg', '.png', '.bmp', '.gif'],
ASTC_SUPPORTED_OUTPUT_TYPES: ['.dds'],
ASTC_SUPPORTED_OUTPUT_TYPES: ['.ktx'],
ETC_COMPRESSION_TYPES: ['etc1', 'etc2'],
ETC_SUPPORTED_INPUT_TYPES: ['.png'],
ETC_SUPPORTED_OUTPUT_TYPES: ['.dds'],
ETC_SUPPORTED_INPUT_TYPES: ['.jpeg', '.jpg', '.png', '.bmp'],
ETC_SUPPORTED_OUTPUT_TYPES: ['.ktx'],
PVR_COMPRESSION_TYPES: ['pvrtc1'],
PVR_SUPPORTED_INPUT_TYPES: ['.jpeg', '.jpg', '.png', '.bmp'],
PVR_SUPPORTED_OUTPUT_TYPES: ['.dds', '.pvr'],
PVR_SUPPORTED_OUTPUT_TYPES: ['.ktx'],
S3TC_COMPRESSION_TYPES: ['dxt1', 'dxt3', 'dxt5'],
S3TC_SUPPORTED_INPUT_TYPES: ['.jpeg', '.jpg', '.png', '.bmp', '.gif'],
S3TC_SUPPORTED_OUTPUT_TYPES: ['.dds', '.crn'],
S3TC_SUPPORTED_OUTPUT_TYPES: ['.ktx'],
};
module.exports = constants;
// Vendor
const {
spawn,
} = require('child_process');
const { spawn } = require('child_process');
const Promise = require('bluebird');

@@ -6,0 +4,0 @@

@@ -7,2 +7,3 @@ // Native

const imageSize = require('image-size');
const uuid = require('uuid');

@@ -32,4 +33,14 @@ const utilities = {

splitFlagAndValue: flags => [].concat(...flags.map(flag => flag.split(' '))),
getTempFilename() {
return uuid.v4();
},
getTempDirectory() {
const tempDirectory = os.tmpdir();
const randomId = uuid.v4();
return path.join(tempDirectory, randomId);
},
};
module.exports = utilities;
{
"name": "texture-compressor",
"version": "0.0.10",
"description": "CLI tool for texture compression using ASTC, ETC, PVRTC and S3TC in DDS or PVR containers.",
"version": "0.1.0",
"description": "CLI tool for texture compression using ASTC, ETC, PVRTC and S3TC in a KTX container.",
"main": "index.js",
"scripts": {
"start": "http-server --cors -s -o",
"lint": "eslint lib/**/*.js",

@@ -18,3 +19,6 @@ "deploy": "npm version patch && npm publish && git push origin"

"bluebird": "^3.5.1",
"image-size": "^0.6.1"
"fs-extra": "^5.0.0",
"image-size": "^0.6.1",
"jimp": "^0.2.28",
"uuid": "^3.2.1"
},

@@ -24,8 +28,5 @@ "devDependencies": {

"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0"
"eslint-plugin-import": "^2.8.0",
"http-server": "^0.11.1"
},
"directories": {
"doc": "docs",
"lib": "lib"
},
"repository": {

@@ -32,0 +33,0 @@ "type": "git",

@@ -7,3 +7,3 @@ # Texture compressor

CLI tool for texture compression using ASTC, ETC, PVRTC and S3TC in DDS or PVR containers.
CLI tool for texture compression using ASTC, ETC, PVRTC and S3TC in a KTX container.

@@ -18,13 +18,65 @@ Easily compress for all hardware using sensible defaults but with the option to pass any custom flag directly to the tool.

## Live demo
[Live demo](https://timvanscherpenzeel.github.io/texture-compressor/)
## Docs
The source code of the `KTX loader` is available on in [docs/main.js](https://github.com/TimvanScherpenzeel/texture-compressor/blob/master/docs/main.js) in the `docs` folder.
## Support table
| Device | OS | OS version | Browser | Browser version | ASTC | ETC | ETC1 | S3TC | PVRTC |
| ------------------------| ------- | ---------- | ------------- | --------------- | ---- | --- | ---- | ---- | ----- |
| Apple iPad 5th | iOS | 11.0.3 | Mobile Safari | 11.0 | | | | | X |
| Apple iPad Air 2 | iOS | 8.4 | Mobile Safari | 8.0 | | | | | X |
| Apple iPad Mini 3 | iOS | 8.1.2 | Mobile Safari | 8.0 | | | | | X |
| Apple iPad Pro | iOS | 11.2.1 | Mobile Safari | 11.0 | | | | | X |
| Apple iPhone 5S | iOS | 8.1.3 | Mobile Safari | 8.0 | | | | | X |
| Apple iPhone 6 Plus | iOS | 8.1 | Mobile Safari | 8.0 | | | | | X |
| Apple iPhone 6 | iOS | 8.1.3 | Mobile Safari | 8.0 | | | | | X |
| Apple iPhone 6S Plus | iOS | 9.0.1 | Mobile Safari | 9.0 | | | | | X |
| Apple iPhone 6S | iOS | 9.1 | Mobile Safari | 9.0 | | | | | X |
| Apple iPhone 7 | iOS | 10.3.1 | Mobile Safari | 10.0 | | | | | X |
| Apple iPhone 8 | iOS | 11.0 | Mobile Safari | 11.0 | | | | | X |
| Apple iPhone 8 | iOS | 11.0 | Mobile Safari | 11.0 | | | | | X |
| Apple iPhone SE | iOS | 11.2.1 | Mobile Safari | 11.0 | | | | | X |
| Apple iPhone X | iOS | 11.2 | Mobile Safari | 11.0 | | | | | X |
| Google Nexus 5X | Android | 7.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Google Nexus 6P | Android | 7.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Google Pixel 2 | Android | 8.0.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Google Pixel 2 | Android | 8.0.0 | Firefox | 51.0 | | X | X | | |
| Google Pixel | Android | 7.1 | Chrome | 63.0.3239.111 | X | | X | | |
| Google Pixel | Android | 8.0.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Google Pixel | Android | 8.0.0 | Firefox | 51.0 | | X | X | | |
| LG G5 | Android | 6.0.1 | Chrome | 63.0.3239.111 | X | | X | | |
| MacOS High Sierra | Mac OS | 10.13.3 | Chrome | 65.0.3325.181 | | | | X | |
| MacOS High Sierra | Mac OS | 10.13 | Firefox | 59.0 | | | | X | |
| MacOS High Sierra | Mac OS | 10.13.3 | Safari | 11.0.3 | | | | X | |
| Motorola Moto X 2nd Gen | Android | 6.0 | Chrome | 63.0.3239.111 | | | X | | |
| Samsung S6 | Android | 5.0.2 | Chrome | 63.0.3239.111 | X | | X | | |
| Samsung S7 | Android | 6.0.1 | Chrome | 63.0.3239.111 | X | | X | | |
| Samsung S7 | Android | 6.0.1 | Firefox | 51.0 | | X | X | | |
| Samsung S8 | Android | 7.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Samsung S8 | Android | 7.0 | Firefox | 51.0 | | X | X | | |
| Samsung S8+ | Android | 7.0 | Chrome | 63.0.3239.111 | X | | X | | |
| Samsung S8+ | Android | 7.0 | Firefox | 51.0 | | X | X | | |
| Windows 10 | Windows | 10 | Chrome | 65.0.3325.146 | | | X | X | |
| Windows 10 | Windows | 10 | Edge | 14.14393 | | | | X | |
| Windows 10 | Windows | 10 | Edge | 15.15063 | | | | X | |
| Windows 10 | Windows | 10 | Edge | 16.16299 | | | | X | |
| Windows 10 | Windows | 10 | Firefox | 59.0 | | | | X | |
| Windows 10 | Windows | 10 | IE | 11.0 | | | | X | |
| Windows 7 | Windows | 7 | IE | 10.0 | | | | | |
| Windows 7 | Windows | 7 | IE | 11.0 | | | | X | |
| Windows 8 | Windows | 8 | IE | 10.0 | | | | | |
## Example
### ASTC
```sh
# ASTC - Supported by all iOS devices with an A8 processor or higher (iPhone 6+)
# Astcenc - https://github.com/ARM-software/astc-encoder
# DDS container
# Extension - https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_astc/
$ node ./bin/texture-compressor -i ./example/example.png -o ./example/example-astc.dds -m astc -c astc
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-astc.ktx -m astc -c astc
```

@@ -35,8 +87,8 @@

```sh
# ETC1 & ETC2 - Supported by most Android devices
# Etc2comp - https://github.com/google/etc2comp
# DDS container
# PVRTexTool - https://community.imgtec.com/developers/powervr/tools/pvrtextool/
# Extension - https://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_etc/
$ node ./bin/texture-compressor -i ./example/example.png -o ./example/example-etc1.dds -m etc -c etc1
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-etc1.ktx -m etc -c etc1
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-etc2.ktx -m etc -c etc2
```

@@ -47,8 +99,6 @@

```sh
# PVRTC1 - Supported by all iOS devices and some Android devices
# PVRTexTool - https://community.imgtec.com/developers/powervr/tools/pvrtextool/
# PVR container
# Extension - http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_pvrtc/
$ node ./bin/texture-compressor -i ./example/example.png -o ./example/example-pvrtc1.pvr -m pvr -c pvrtc1
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-pvrtc1.ktx -m pvr -c pvrtc1
```

@@ -59,8 +109,10 @@

```sh
# DXT1 & DXT3 & DXT5 - Supported by all desktop devices and some Android devices
# Crunch - https://code.google.com/archive/p/crunch/
# CRN & DDS container
# Extension - http://www.khronos.org/registry/webgl/extensions/WEBGL_compressed_texture_s3tc/
$ node ./bin/texture-compressor -i ./example/example.png -o ./example/example-dxt5.dds -m s3tc -c dxt5
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-dxt1.ktx -m s3tc -c dxt1
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-dxt3.ktx -m s3tc -c dxt3
$ node ./bin/texture-compressor -i ./docs/example/example.png -o ./docs/example/example-dxt5.ktx -m s3tc -c dxt5
```

@@ -72,4 +124,4 @@

-c, --compression ['astc', 'etc1', 'etc2', 'pvrtc1', 'dxt1', 'dxt3', 'dxt5'] [required]
-i, --input [example: ./example/example.png] [required]
-o, --output [example: ./example/example.dds] [required]
-i, --input [example: ./docs/example/example.png] [required]
-o, --output [example: ./docs/example/example.ktx] [required]
-m, --method ['astc', 'etc', 'pvr', 's3tc'] [required]

@@ -98,4 +150,3 @@

- [astcenc](https://raw.githubusercontent.com/ARM-software/astc-encoder/master/license.txt)
- [EtcTool](https://raw.githubusercontent.com/google/etc2comp/master/LICENSE)
- [PVRTexToolCLI](https://community.imgtec.com/developers/powervr/sdk-end-user-licence-agreement/)
- [crunch](https://raw.githubusercontent.com/Unvanquished/crunch/master/license.txt)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc