From 5d833762e5fe70e4a29cdc0efdb78c7e7724837a Mon Sep 17 00:00:00 2001 From: fallenoak Date: Tue, 2 Jan 2024 22:54:13 -0600 Subject: [PATCH] fix(blp): handle unusually sized mip data at very small sizes --- src/lib/blp/Blp.ts | 32 +++++++++++++++++++++++++++----- src/lib/blp/dxt.ts | 14 +++++++++++++- src/lib/blp/util.ts | 18 +++++++++++++++++- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/lib/blp/Blp.ts b/src/lib/blp/Blp.ts index 5473bd4..2c044ac 100644 --- a/src/lib/blp/Blp.ts +++ b/src/lib/blp/Blp.ts @@ -6,11 +6,18 @@ import { BLP_PIXEL_FORMAT, MAX_MIP_LEVELS, } from './const.js'; -import { dxt1ToAbgr8888, dxt3ToAbgr8888, dxt5ToAbgr8888 } from './dxt.js'; +import { + dxt1ToAbgr8888, + dxt3ToAbgr8888, + dxt5ToAbgr8888, + getDxt1Size, + getDxt3Size, + getDxt5Size, +} from './dxt.js'; import { palToAbgr8888 } from './pal.js'; import { rawAbgr8888ToArgb8888, rawArgb8888ToAbgr8888 } from './raw.js'; import * as blpIo from './io.js'; -import { calcMipLevelCount, getSizeAtMipLevel, resizeBilinear } from './util.js'; +import { calcMipLevelCount, getResizedBytes, getSizeAtMipLevel, resizeBilinear } from './util.js'; import BlpImage from './BlpImage.js'; class Blp { @@ -316,7 +323,12 @@ class Blp { #getDxt1Image(level: number, outputFormat: BLP_IMAGE_FORMAT): BlpImage { const width = getSizeAtMipLevel(this.#width, level); const height = getSizeAtMipLevel(this.#height, level); - const data = this.#images[level]; + + if (width === 0 || height === 0) { + return new BlpImage(width, height, new Uint8Array(0), outputFormat); + } + + const data = getResizedBytes(this.#images[level], getDxt1Size(width, height)); switch (outputFormat) { case BLP_IMAGE_FORMAT.IMAGE_DXT1: @@ -333,7 +345,12 @@ class Blp { #getDxt3Image(level: number, outputFormat: BLP_IMAGE_FORMAT): BlpImage { const width = getSizeAtMipLevel(this.#width, level); const height = getSizeAtMipLevel(this.#height, level); - const data = this.#images[level]; + + if (width === 0 || height === 0) { + return new BlpImage(width, height, new Uint8Array(0), outputFormat); + } + + const data = getResizedBytes(this.#images[level], getDxt3Size(width, height)); switch (outputFormat) { case BLP_IMAGE_FORMAT.IMAGE_DXT3: @@ -350,7 +367,12 @@ class Blp { #getDxt5Image(level: number, outputFormat: BLP_IMAGE_FORMAT) { const width = getSizeAtMipLevel(this.#width, level); const height = getSizeAtMipLevel(this.#height, level); - const data = this.#images[level]; + + if (width === 0 || height === 0) { + return new BlpImage(width, height, new Uint8Array(0), outputFormat); + } + + const data = getResizedBytes(this.#images[level], getDxt5Size(width, height)); switch (outputFormat) { case BLP_IMAGE_FORMAT.IMAGE_DXT5: diff --git a/src/lib/blp/dxt.ts b/src/lib/blp/dxt.ts index 3d94b1e..5267fc7 100644 --- a/src/lib/blp/dxt.ts +++ b/src/lib/blp/dxt.ts @@ -262,6 +262,18 @@ const dxtToAbgr8888 = ( return output8; }; +const getDxtSize = (width: number, height: number, blockSize: number) => { + const blockWidth = Math.ceil(width / DXT_BLOCK_WIDTH); + const blockHeight = Math.ceil(height / DXT_BLOCK_HEIGHT); + return blockWidth * blockHeight * blockSize; +}; + +const getDxt1Size = (width: number, height: number) => getDxtSize(width, height, DXT1_BLOCK_SIZE); + +const getDxt3Size = (width: number, height: number) => getDxtSize(width, height, DXT3_BLOCK_SIZE); + +const getDxt5Size = (width: number, height: number) => getDxtSize(width, height, DXT5_BLOCK_SIZE); + const dxt1ToAbgr8888 = (width: number, height: number, input: Uint8Array) => dxtToAbgr8888(width, height, input, DXT1_BLOCK_SIZE, dxt1DecompressBlock); @@ -271,4 +283,4 @@ const dxt3ToAbgr8888 = (width: number, height: number, input: Uint8Array) => const dxt5ToAbgr8888 = (width: number, height: number, input: Uint8Array) => dxtToAbgr8888(width, height, input, DXT5_BLOCK_SIZE, dxt5DecompressBlock); -export { dxt1ToAbgr8888, dxt3ToAbgr8888, dxt5ToAbgr8888 }; +export { dxt1ToAbgr8888, dxt3ToAbgr8888, dxt5ToAbgr8888, getDxt1Size, getDxt3Size, getDxt5Size }; diff --git a/src/lib/blp/util.ts b/src/lib/blp/util.ts index c7ba949..7364bd2 100644 --- a/src/lib/blp/util.ts +++ b/src/lib/blp/util.ts @@ -15,6 +15,22 @@ const calcMipLevelCount = (width: number, height: number) => { const getSizeAtMipLevel = (size: number, level: number) => (size / (1 << level)) | 0; +const getResizedBytes = (bytes: Uint8Array, size: number) => { + if (bytes.byteLength === size) { + return bytes; + } + + if (bytes.byteLength < size) { + const padded = new Uint8Array(size); + padded.set(bytes, 0); + return padded; + } + + const trimmed = new Uint8Array(size); + trimmed.set(bytes.subarray(0, size), 0); + return trimmed; +}; + const resizeBilinear = ( data: Uint8Array, width: number, @@ -63,4 +79,4 @@ const resizeBilinear = ( return newData; }; -export { calcMipLevelCount, getSizeAtMipLevel, resizeBilinear }; +export { calcMipLevelCount, getSizeAtMipLevel, getResizedBytes, resizeBilinear };