mdx-m3-viewer
Version:
A browser WebGL model viewer. Mainly focused on models of the games Warcraft 3 and Starcraft 2.
159 lines (134 loc) • 4.07 kB
JavaScript
import {JpegImage} from '../../../thirdparty/jpg';
import BitStream from '../../common/bitstream';
import convertBitRange from '../../common/convertbitrange';
let BLP1_MAGIC = 0x31504c42;
let CONTENT_JPG = 0x0;
// let CONTENT_PALLETE = 0x1;
/**
* A BLP1 texture.
*/
export default class BlpTexture {
/**
* @param {?ArrayBuffer} buffer
*/
constructor(buffer) {
/** @member {number} */
this.content = 0;
/** @member {number} */
this.alphaBits = 0;
/** @member {number} */
this.width = 0;
/** @member {number} */
this.height = 0;
/** @member {number} */
this.type = 0;
/** @member {Uint32Array} */
this.hasMipmaps = false;
/** @member {Uint32Array} */
this.mipmapOffsets = new Uint32Array(16);
/** @member {Uint32Array} */
this.mipmapSizes = new Uint32Array(16);
/** @member {?Uint8Array} */
this.uint8array = null;
/**
* Used for JPG images.
*
* @member {?Uint8Array}
*/
this.jpgHeader = null;
/**
* Used for indexed images.
*
* @member {?Uint8Array}
*/
this.pallete = null;
if (buffer) {
this.load(buffer);
}
}
/**
* @param {ArrayBuffer} buffer
*/
load(buffer) {
// This includes the JPG header size, in case its a JPG image.
// Otherwise, the last element is ignored.
let header = new Int32Array(buffer, 0, 40);
if (header[0] !== BLP1_MAGIC) {
throw new Error('WrongMagicNumber');
}
this.content = header[1];
this.alphaBits = header[2];
this.width = header[3];
this.height = header[4];
this.type = header[5];
this.hasMipmaps = header[6];
for (let i = 0; i < 16; i++) {
this.mipmapOffsets[i] = header[7 + i];
this.mipmapSizes[i] = header[23 + i];
}
this.uint8array = new Uint8Array(buffer);
if (this.content === CONTENT_JPG) {
this.jpgHeader = new Uint8Array(buffer, 160, header[39]);
} else {
this.pallete = new Uint8Array(buffer, 156, 1024);
}
}
/**
* @param {number} level
* @return {ImageData}
*/
getMipmap(level) {
let uint8array = this.uint8array;
let offset = this.mipmapOffsets[level];
let size = this.mipmapSizes[level];
let imageData;
if (this.content === CONTENT_JPG) {
let jpgHeader = this.jpgHeader;
let data = new Uint8Array(jpgHeader.length + size);
let jpegImage = new JpegImage();
data.set(jpgHeader);
data.set(uint8array.subarray(offset, offset + size), jpgHeader.length);
jpegImage.parse(data);
// The JPG data might not actually match the correct mipmap size.
imageData = new ImageData(jpegImage.width, jpegImage.height);
jpegImage.getData(imageData);
} else {
let pallete = this.pallete;
let width = Math.max(this.width / (1 << level), 1); // max of 1 because for non-square textures one dimension will eventually be <1.
let height = Math.max(this.height / (1 << level), 1);
let size = width * height;
let alphaBits = this.alphaBits;
let bitStream;
let bitsToByte;
imageData = new ImageData(width, height);
if (alphaBits > 0) {
bitStream = new BitStream(uint8array.buffer, offset + size, Math.ceil((size * alphaBits) / 8));
bitsToByte = convertBitRange(alphaBits, 8);
}
let data = imageData.data;
for (let i = 0; i < size; i++) {
let dataIndex = i * 4;
let paletteIndex = uint8array[offset + i] * 4;
// BGRA->RGBA
data[dataIndex] = pallete[paletteIndex + 2];
data[dataIndex + 1] = pallete[paletteIndex + 1];
data[dataIndex + 2] = pallete[paletteIndex];
if (alphaBits > 0) {
data[dataIndex + 3] = bitStream.readBits(alphaBits) * bitsToByte;
} else {
data[dataIndex + 3] = 255;
}
}
}
return imageData;
}
/**
* @return {number}
*/
mipmaps() {
if (this.hasMipmaps) {
return Math.ceil(Math.log2(Math.max(this.width, this.height))) + 1;
}
return 1;
}
}