diff --git a/src/renderware/txd/TxdParser.ts b/src/renderware/txd/TxdParser.ts index cd9ffd0..d0378ad 100644 --- a/src/renderware/txd/TxdParser.ts +++ b/src/renderware/txd/TxdParser.ts @@ -1,6 +1,11 @@ import { RwFile } from '../RwFile'; - -const dxt = require('dxt-js'); +import { ImageDecoder } from '../utils/ImageDecoder' +import { + D3DFormat, + PaletteType, + PlatformType, + RasterFormat +} from '../utils/ImageFormatEnums' export interface RwTxd { textureDictionary: RwTextureDictionary, @@ -90,21 +95,23 @@ export class TxdParser extends RwFile { const mipmapCount = this.readUint8(); const rasterType = this.readUint8(); - const _isPAL4 = rasterType & 0x4000; - const _isPAL8 = rasterType & 0x2000; - - const compressionFlags = this.readUint8(); + const compressionFlags = this.readUint8(); // Is "dxtType" for III/VC + // SA const alpha = (compressionFlags & (1 << 0)) !== 0; const cubeTexture = (compressionFlags & (1 << 1)) !== 0; const autoMipMaps = (compressionFlags & (1 << 2)) !== 0; const compressed = (compressionFlags & (1 << 3)) !== 0; + const paletteType = (rasterFormat >> 13) & 0b11; + let mipWidth = width; let mipHeight = height; let mipmaps: number[][] = []; + const palette = (paletteType !== PaletteType.PALETTE_NONE ? this.readPalette(paletteType, depth) : new Uint8Array(0)); + for (let i = 0; i < mipmapCount; i++) { const rasterSize = this.readUint32(); @@ -114,25 +121,26 @@ export class TxdParser extends RwFile { // Raw RGBA presentation let bitmap: number[]; - if (compressed || d3dFormat.includes('DXT')) { - bitmap = Array.from(dxt.decompress(raster, mipWidth, mipHeight, dxt.flags[d3dFormat])); - } else { - // TODO: Make raster format an enum and add more formats - // All formats are in D3D9 color order (BGRA), so we swap them - - switch (rasterFormat) { - // FORMAT_8888, depth 32 (D3DFMT_A8R8G8B8) - case 0x0500: - // FORMAT_888 (RGB 8 bits each, D3DFMT_X8R8G8B8) - case 0x0600: - for (let i = 0; i < raster.length; i += 4) { - // Fancy array destructuring, just swaps R and B values - [raster[i], raster[i + 2]] = [raster[i + 2], raster[i]]; - } - break; - } - - bitmap = Array.from(raster); + if (palette.length !== 0) { + const rasterFormatsWithoutAlpha = [ + RasterFormat.RASTER_565, + RasterFormat.RASTER_LUM, + RasterFormat.RASTER_888, + RasterFormat.RASTER_555 + ]; + + const hasAlpha = ((platformId === PlatformType.D3D9 && alpha) || (platformId == PlatformType.D3D8 && !rasterFormatsWithoutAlpha.includes(rasterFormat))); + + bitmap = Array.from(this.getBitmapWithPalette(paletteType, depth, hasAlpha, raster, palette, width, height)); + } + else if (platformId === PlatformType.D3D8 && compressionFlags !== 0) { + bitmap = Array.from(this.getBitmapWithDXT('DXT' + compressionFlags, raster, width, height)); + } + else if (platformId === PlatformType.D3D9 && compressed) { + bitmap = Array.from(this.getBitmapWithDXT(d3dFormat, raster, width, height)); + } + else { + bitmap = Array.from(this.getBitmapWithRasterFormat(rasterFormat, raster, width, height)) } mipmaps.push(bitmap); @@ -162,4 +170,65 @@ export class TxdParser extends RwFile { mipmaps, }; } + + public readPalette(paletteType: number, depth: number): Uint8Array { + const size = (paletteType === PaletteType.PALETTE_8 ? 1024 : (depth === 4 ? 64 : 128)) + + return this.read(size); + } + + public getBitmapWithPalette(paletteType: number, depth: number, hasAlpha: boolean, raster: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array { + if (paletteType !== PaletteType.PALETTE_8 && depth == 4) { + return (hasAlpha + ? ImageDecoder.pal4(raster, palette, width, height) + : ImageDecoder.pal4NoAlpha(raster, palette, width, height) + ); + } + + return (hasAlpha + ? ImageDecoder.pal8(raster, palette, width, height) + : ImageDecoder.pal8NoAlpha(raster, palette, width, height) + ) + } + + public getBitmapWithDXT(dxtType:string, raster: Uint8Array, width: number, height: number): Uint8Array { + switch (dxtType) { + case D3DFormat.D3D_DXT1: + return ImageDecoder.bc1(raster, width, height); + case D3DFormat.D3D_DXT2: + return ImageDecoder.bc2(raster, width, height, true); + case D3DFormat.D3D_DXT3: + return ImageDecoder.bc2(raster, width, height, false); + case D3DFormat.D3D_DXT4: + return ImageDecoder.bc3(raster, width, height, true); + case D3DFormat.D3D_DXT5: + return ImageDecoder.bc3(raster, width, height, false); + // LUM8_A8 has compressed flag + case D3DFormat.D3DFMT_A8L8: + return ImageDecoder.lum8a8(raster, width, height); + default: + return new Uint8Array(0); + } + } + + public getBitmapWithRasterFormat (rasterFormat: number, raster: Uint8Array, width: number, height: number): Uint8Array { + switch (rasterFormat) { + case RasterFormat.RASTER_1555: + return ImageDecoder.bgra1555(raster, width, height); + case RasterFormat.RASTER_565: + return ImageDecoder.bgra565(raster, width, height); + case RasterFormat.RASTER_4444: + return ImageDecoder.bgra4444(raster, width, height); + case RasterFormat.RASTER_LUM: + return ImageDecoder.lum8(raster, width, height); + case RasterFormat.RASTER_8888: + return ImageDecoder.bgra8888(raster, width, height); + case RasterFormat.RASTER_888: + return ImageDecoder.bgra888(raster, width, height); + case RasterFormat.RASTER_555: + return ImageDecoder.bgra555(raster, width, height); + default: + return new Uint8Array(0); + } + } } diff --git a/src/renderware/utils/ImageDecoder.ts b/src/renderware/utils/ImageDecoder.ts new file mode 100644 index 0000000..ab9bd77 --- /dev/null +++ b/src/renderware/utils/ImageDecoder.ts @@ -0,0 +1,571 @@ +// Source: https://github.com/Parik27/DragonFF/blob/master/gtaLib/txd.py +export class ImageDecoder { + + static readUInt16LE(buf: Uint8Array, offset: number): number { + return buf[offset] | (buf[offset + 1] << 8); + } + + static readUInt32LE(buf: Uint8Array, offset: number): number { + return ( + buf[offset] | + (buf[offset + 1] << 8) | + (buf[offset + 2] << 16) | + (buf[offset + 3] << 24) + ); + } + + static decode565(bits: number): [number, number, number] { + const r = (bits >> 11) & 0b11111; + const g = (bits >> 5) & 0b111111; + const b = bits & 0b11111; + + return [ + (r << 3) | (r >> 2), + (g << 2) | (g >> 4), + (b << 3) | (b >> 2) + ]; + } + + static decode555(bits:number): [number, number, number] { + const r = Math.round(((bits >> 10) & 0b11111) * 255 / 31); + const g = Math.round(((bits >> 5) & 0b11111) * 255 / 31); + const b = Math.round((bits & 0b11111) * 255 / 31); + return [r, g, b]; + } + + static decode1555(bits: number): [number, number, number, number] { + const a = Math.round(((bits >> 15) & 0b1) * 255); + const r = Math.round(((bits >> 10) & 0b11111) * 255 / 31); + const g = Math.round(((bits >> 5) & 0b11111) * 255 / 31); + const b = Math.round((bits & 0b11111) * 255 / 31); + return [a, r, g, b]; + } + + static decode4444(bits: number): [number, number, number, number] { + const a = Math.round(((bits >> 12) & 0b1111) * 255 / 15); + const r = Math.round(((bits >> 8) & 0b1111) * 255 / 15); + const g = Math.round(((bits >> 4) & 0b1111) * 255 / 15); + const b = Math.round((bits & 0b1111) * 255 / 15); + return [a, r, g, b]; + } + + /* + bc1 - block compression format, using for DXT1 + compress 4x4 block of pixels + format: + +---------------+ + | color0 | color0 in palette. 16bit (RGB 565 format) + +---------------+ + | color1 | color1 in palette. 16bit (RGB 565 format) + +---+---+---+---+ + | a | b | c | d | a-p color palette index 2bit * 16 + +---+---+---+---+ + | e | f | g | h | + +---+---+---+---+ + | i | j | k | l | + +---+---+---+---+ + | m | n | o | p | total: 8byte in 4x4 colors + +---+---+---+---+ + + color2 and color3 in the palette are calculated by interpolating other colors or choosing the average between them. + color0 > color1 => interpolation, else => average + */ + static bc1(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + const colorPalette = new Uint8Array(16); + let offset = 0; + + for (let y = 0; y < height; y += 4) { + for (let x = 0; x < width; x += 4) { + const color0 = ImageDecoder.readUInt16LE(data, offset); + const color1 = ImageDecoder.readUInt16LE(data, offset + 2); + let colorBits = ImageDecoder.readUInt32LE(data, offset + 4); + offset += 8; + + let [c0r, c0g, c0b] = ImageDecoder.decode565(color0); + let [c1r, c1g, c1b] = ImageDecoder.decode565(color1); + + if (color0 > color1) { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (2 * c0r + c1r) / 3; + colorPalette[9] = (2 * c0g + c1g) / 3; + colorPalette[10] = (2 * c0b + c1b) / 3; + colorPalette[12] = (c0r + 2 * c1r) / 3; + colorPalette[13] = (c0g + 2 * c1g) / 3; + colorPalette[14] = (c0b + 2 * c1b) / 3; + } + else { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (c0r + c1r) >> 1; + colorPalette[9] = (c0g + c1g) >> 1; + colorPalette[10] = (c0b + c1b) >> 1; + colorPalette[12] = 0; colorPalette[13] = 0; colorPalette[14] = 0; + } + + const baseIndex = (y * width + x) * 4; + for (let k = 0; k < 16; k++) { + const colorIdx = colorBits & 0x3; + colorBits >>>= 2; + + const j = k >> 2; + const i = k & 3; + const idx = baseIndex + ((j * width + i) << 2); + + rgba[idx + 0] = colorPalette[colorIdx * 4]; + rgba[idx + 1] = colorPalette[colorIdx * 4 + 1]; + rgba[idx + 2] = colorPalette[colorIdx * 4 + 2]; + + if (color0 <= color1 && colorIdx === 3) { + rgba[idx + 3] = 0; + } + else { + rgba[idx + 3] = 255; + } + } + } + } + + return rgba; + } + + /* + bc2 - block compression format, using for DXT2 and DXT3 + compress 4x4 block of pixels with 4x4 4bit alpha + format: + +---+---+---+---+ + | a | b | c | d | a-p pixel alpha. 4bit * 16 + +---+---+---+---+ + | e | f | g | h | + +---+---+---+---+ + | i | j | k | l | + +---+---+---+---+ + | m | n | o | p | + +---+---+---+---+ + | | bc1 color compression. 8byte + | bc1 block | + | | total: 16byte in 4x4 colors + +---------------+ + + in DXT2, the color data is interpreted as being premultiplied by alpha + */ + static bc2(data: Uint8Array, width: number, height: number, premultiplied: boolean): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + const colorPalette = new Uint8Array(16); + + const alphaTable = new Uint8Array(16); + for (let i = 0; i < 16; i++) { + alphaTable[i] = (i * 255 + 7.5) / 15 | 0; + } + + let offset = 0; + + for (let y = 0; y < height; y += 4) { + for (let x = 0; x < width; x += 4) { + const alpha0 = ImageDecoder.readUInt32LE(data, offset); + const alpha1 = ImageDecoder.readUInt32LE(data, offset + 4); + offset += 8; + + const color0 = ImageDecoder.readUInt16LE(data, offset); + const color1 = ImageDecoder.readUInt16LE(data, offset + 2); + let colorBits = ImageDecoder.readUInt32LE(data, offset + 4); + offset += 8; + + let [c0r, c0g, c0b] = ImageDecoder.decode565(color0); + let [c1r, c1g, c1b] = ImageDecoder.decode565(color1); + + if (color0 > color1) { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (2 * c0r + c1r) / 3; + colorPalette[9] = (2 * c0g + c1g) / 3; + colorPalette[10] = (2 * c0b + c1b) / 3; + colorPalette[12] = (c0r + 2 * c1r) / 3; + colorPalette[13] = (c0g + 2 * c1g) / 3; + colorPalette[14] = (c0b + 2 * c1b) / 3; + } + else { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (c0r + c1r) >> 1; + colorPalette[9] = (c0g + c1g) >> 1; + colorPalette[10] = (c0b + c1b) >> 1; + colorPalette[12] = 0; colorPalette[13] = 0; colorPalette[14] = 0; + } + + const baseIndex = ((y * width + x) << 2); + + for (let k = 0; k < 16; k++) { + const j = k >> 2; + const i = k & 3; + + const idx = baseIndex + ((((j * width + i) << 2))); + + const colorIdx = colorBits & 0x3; + colorBits >>>= 2; + + const bitPos = (j << 2) + i; + const byteIndex = bitPos >> 3; + const shift = (bitPos & 7) << 2; + + const alpha4 = ((byteIndex === 0 ? alpha0 : alpha1) >>> shift) & 0xF; + const alpha = alphaTable[alpha4]; + + rgba[idx + 0] = colorPalette[colorIdx * 4]; + rgba[idx + 1] = colorPalette[colorIdx * 4 + 1]; + rgba[idx + 2] = colorPalette[colorIdx * 4 + 2]; + rgba[idx + 3] = alpha; + + + if (premultiplied && alpha > 0 && alpha < 255) { + const factor = 255 / alpha; + rgba[idx + 0] = Math.min(255, Math.round(rgba[idx + 0] * factor)); + rgba[idx + 1] = Math.min(255, Math.round(rgba[idx + 1] * factor)); + rgba[idx + 2] = Math.min(255, Math.round(rgba[idx + 2] * factor)); + } + } + } + } + + return rgba; + } + + /* + bc3 - block compression format, using for DXT4 and DXT5 + compress 4x4 block of pixels with alpha + format: + +---------------+ + | alpha0 | min alpha value. 8bit + +---------------+ + | alpha1 | max alpha value. 8bit + +---+---+---+---+ + | a | b | c | d | bc1-like alpha block but 3bit * 16 (index in alpha palette) + +---+---+---+---+ + | e | f | g | h | + +---+---+---+---+ + | i | j | k | l | + +---+---+---+---+ + | m | n | o | p | + +---+---+---+---+ + | | bc1 color compression. 8byte + | bc1 block | + | | total: 16byte in 4x4 colors + +---------------+ + + in DXT4, the color data is interpreted as being premultiplied by alpha + */ + static bc3(data: Uint8Array, width: number, height: number, premultiplied: boolean): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + const alphaPalette = new Uint8Array(8); + const colorPalette = new Uint8Array(16); + const alphaIndices = new Uint8Array(16); + let offset = 0; + + for (let y = 0; y < height; y += 4) { + for (let x = 0; x < width; x += 4) { + const alpha0 = data[offset++]; + const alpha1 = data[offset++]; + + const alphaBits = data.subarray(offset, offset + 6); + offset += 6; + + const color0 = ImageDecoder.readUInt16LE(data, offset); + const color1 = ImageDecoder.readUInt16LE(data, offset + 2); + let colorBits = ImageDecoder.readUInt32LE(data, offset + 4); + offset += 8; + + let [c0r, c0g, c0b] = ImageDecoder.decode565(color0); + let [c1r, c1g, c1b] = ImageDecoder.decode565(color1); + + if (color0 > color1) { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (2 * c0r + c1r) / 3; + colorPalette[9] = (2 * c0g + c1g) / 3; + colorPalette[10] = (2 * c0b + c1b) / 3; + colorPalette[12] = (c0r + 2 * c1r) / 3; + colorPalette[13] = (c0g + 2 * c1g) / 3; + colorPalette[14] = (c0b + 2 * c1b) / 3; + } + else { + colorPalette[0] = c0r; colorPalette[1] = c0g; colorPalette[2] = c0b; + colorPalette[4] = c1r; colorPalette[5] = c1g; colorPalette[6] = c1b; + colorPalette[8] = (c0r + c1r) >> 1; + colorPalette[9] = (c0g + c1g) >> 1; + colorPalette[10] = (c0b + c1b) >> 1; + colorPalette[12] = 0; colorPalette[13] = 0; colorPalette[14] = 0; + } + + if (alpha0 > alpha1) { + alphaPalette[0] = alpha0; + alphaPalette[1] = alpha1; + alphaPalette[2] = (alpha0 * 6 + alpha1 * 1 + 3) / 7; + alphaPalette[3] = (alpha0 * 5 + alpha1 * 2 + 3) / 7; + alphaPalette[4] = (alpha0 * 4 + alpha1 * 3 + 3) / 7; + alphaPalette[5] = (alpha0 * 3 + alpha1 * 4 + 3) / 7; + alphaPalette[6] = (alpha0 * 2 + alpha1 * 5 + 3) / 7; + alphaPalette[7] = (alpha0 * 1 + alpha1 * 6 + 3) / 7; + } + else { + alphaPalette[0] = alpha0; + alphaPalette[1] = alpha1; + alphaPalette[2] = (alpha0 * 4 + alpha1 * 1 + 2) / 5; + alphaPalette[3] = (alpha0 * 3 + alpha1 * 2 + 2) / 5; + alphaPalette[4] = (alpha0 * 2 + alpha1 * 3 + 2) / 5; + alphaPalette[5] = (alpha0 * 1 + alpha1 * 4 + 2) / 5; + alphaPalette[6] = 0; + alphaPalette[7] = 255; + } + + for (let k = 0; k < 16; k++) { + const bitOffset = k * 3; + const byteOffset = bitOffset >> 3; + const shift = bitOffset & 7; + + if (shift <= 5) { + alphaIndices[k] = (alphaBits[byteOffset] >> shift) & 0x7; + } + else { + const part1 = (alphaBits[byteOffset] >> shift) & 0x7; + const part2 = (alphaBits[byteOffset + 1] << (8 - shift)) & 0x7; + alphaIndices[k] = part1 | part2; + } + } + + const baseIndex = (y * width + x) << 2; + let bits = colorBits; + + for (let k = 0; k < 16; k++) { + const j = k >> 2; + const i = k & 3; + + const idx = baseIndex + ((((j * width + i) << 2))); + const colorIdx = bits & 0x3; + bits >>>= 2; + + const alpha = alphaPalette[alphaIndices[k] & 0x7]; + + rgba[idx + 0] = colorPalette[colorIdx * 4]; + rgba[idx + 1] = colorPalette[colorIdx * 4 + 1]; + rgba[idx + 2] = colorPalette[colorIdx * 4 + 2]; + rgba[idx + 3] = alpha; + + if (premultiplied && alpha > 0 && alpha < 255) { + const factor = 255 / alpha; + rgba[idx] = Math.min(255, Math.round(rgba[idx] * factor)); + rgba[idx + 1] = Math.min(255, Math.round(rgba[idx + 1] * factor)); + rgba[idx + 2] = Math.min(255, Math.round(rgba[idx + 2] * factor)); + } + } + } + } + + return rgba; + } + + static bgra1555(data: Uint8Array, width: number, height: number): Uint8Array { + const rbga = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i += 2) { + const color = ImageDecoder.readUInt16LE(data, i); + const [a, r, g, b] = ImageDecoder.decode1555(color); + + rbga[offset++] = r; + rbga[offset++] = g; + rbga[offset++] = b; + rbga[offset++] = a; + } + + return rbga; + } + + static bgra4444(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i += 2) { + const color = ImageDecoder.readUInt16LE(data, i); + const [a, r, g, b] = ImageDecoder.decode4444(color); + + rgba[offset++] = r; + rgba[offset++] = g; + rgba[offset++] = b; + rgba[offset++] = a; + } + + return rgba; + } + + static bgra555(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i += 2) { + const color = ImageDecoder.readUInt16LE(data, i); + const [r, g, b] = ImageDecoder.decode555(color); + + rgba[offset++] = r; + rgba[offset++] = g; + rgba[offset++] = b; + rgba[offset++] = 0xff; + } + + return rgba; + } + + static bgra565(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i += 2) { + const color = ImageDecoder.readUInt16LE(data, i); + const [r, g, b] = ImageDecoder.decode565(color); + + rgba[offset++] = r; + rgba[offset++] = g; + rgba[offset++] = b; + rgba[offset++] = 0xff; + } + + return rgba; + } + + static bgra888(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + for (let i = 0; i < data.length; i += 4) { + rgba[i + 0] = data[i + 2]; + rgba[i + 1] = data[i + 1]; + rgba[i + 2] = data[i + 0]; + rgba[i + 3] = 0xff; + } + + return rgba; + } + + static bgra8888(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + for (let i = 0; i < data.length; i += 4) { + rgba[i + 0] = data[i + 2]; + rgba[i + 1] = data[i + 1]; + rgba[i + 2] = data[i + 0]; + rgba[i + 3] = data[i + 3]; + } + + return rgba; + } + + static lum8(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + + for (let i = 0; i < data.length; i++) { + const offset = i * 4; + const luminance = data[i]; + rgba[offset + 0] = luminance; // R + rgba[offset + 1] = luminance; // G + rgba[offset + 2] = luminance; // B + rgba[offset + 3] = 0xff; + } + + return rgba; + } + + static lum8a8(data: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i += 2) { + const luminance = data[i]; + const alpha = data[i + 1]; + + rgba[offset++] = luminance; + rgba[offset++] = luminance; + rgba[offset++] = luminance; + rgba[offset++] = alpha; + } + + return rgba; + } + + static pal4(data: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i++) { + const b = data[i]; + const idx1 = (b >> 4) & 0xf; + const idx2 = b & 0xf; + + // Copying RGBA from the palette for two pixels + rgba[offset++] = palette[idx1 * 4 + 0]; // R + rgba[offset++] = palette[idx1 * 4 + 1]; // G + rgba[offset++] = palette[idx1 * 4 + 2]; // B + rgba[offset++] = palette[idx1 * 4 + 3]; // A + + rgba[offset++] = palette[idx2 * 4 + 0]; // R + rgba[offset++] = palette[idx2 * 4 + 1]; // G + rgba[offset++] = palette[idx2 * 4 + 2]; // B + rgba[offset++] = palette[idx2 * 4 + 3]; // A + } + + return rgba; + } + + static pal4NoAlpha(data: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + let offset = 0; + + for (let i = 0; i < data.length; i++) { + const b = data[i]; + const colorIndex1 = (b >> 4) & 0xf; + const colorIndex2 = b & 0xf; + + // First pixel + rgba[offset++] = palette[colorIndex1 * 4 + 0]; // R + rgba[offset++] = palette[colorIndex1 * 4 + 1]; // G + rgba[offset++] = palette[colorIndex1 * 4 + 2]; // B + rgba[offset++] = 0xff; + + // Second pixel + rgba[offset++] = palette[colorIndex2 * 4 + 0]; // R + rgba[offset++] = palette[colorIndex2 * 4 + 1]; // G + rgba[offset++] = palette[colorIndex2 * 4 + 2]; // B + rgba[offset++] = 0xff; + } + + return rgba; + } + + static pal8(data: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + + for (let i = 0; i < data.length; i++) { + const colorIndex = data[i]; + + // Copy RGBA from palette + rgba[i * 4 + 0] = palette[colorIndex * 4 + 0]; // R + rgba[i * 4 + 1] = palette[colorIndex * 4 + 1]; // G + rgba[i * 4 + 2] = palette[colorIndex * 4 + 2]; // B + rgba[i * 4 + 3] = palette[colorIndex * 4 + 3]; // A + } + + return rgba; + } + + static pal8NoAlpha(data: Uint8Array, palette: Uint8Array, width: number, height: number): Uint8Array { + const rgba = new Uint8Array(4 * width * height); + + for (let i = 0; i < data.length; i++) { + const colorIndex = data[i]; + + // Copy RGB from palette + rgba[i * 4 + 0] = palette[colorIndex * 4 + 0]; // R + rgba[i * 4 + 1] = palette[colorIndex * 4 + 1]; // G + rgba[i * 4 + 2] = palette[colorIndex * 4 + 2]; // B + rgba[i * 4 + 3] = 0xff; + } + + return rgba; + } +} \ No newline at end of file diff --git a/src/renderware/utils/ImageFormatEnums.ts b/src/renderware/utils/ImageFormatEnums.ts new file mode 100644 index 0000000..c2c983e --- /dev/null +++ b/src/renderware/utils/ImageFormatEnums.ts @@ -0,0 +1,28 @@ +export enum RasterFormat { + RASTER_1555 = 0x0100, + RASTER_565 = 0x0200, + RASTER_4444 = 0x0300, + RASTER_LUM = 0x0400, + RASTER_8888 = 0x0500, + RASTER_888 = 0x0600, + RASTER_555 = 0x0a00, +} + +export enum D3DFormat { + D3DFMT_A8L8 = "3", + D3D_DXT1 = "DXT1", + D3D_DXT2 = "DXT2", + D3D_DXT3 = "DXT3", + D3D_DXT4 = "DXT4", + D3D_DXT5 = "DXT5", +} + +export enum PaletteType { + PALETTE_NONE = 0, + PALETTE_8 = 1, +} + +export enum PlatformType { + D3D8 = 0x8, + D3D9 = 0x9, +} \ No newline at end of file