image-in-browser
Version:
Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)
553 lines • 20.2 kB
JavaScript
import { InputBuffer } from '../common/input-buffer.js';
import { MemoryImage } from '../image/image.js';
import { ImageFormat } from './image-format.js';
import { PvrAppleInfo } from './pvr/pvr-apple-info.js';
import { PvrPacket } from './pvr/pvr-packet.js';
import { Pvr2Info } from './pvr/pvr2-info.js';
import { Pvr3Info } from './pvr/pvr3-info.js';
export class PvrDecoder {
get format() {
return ImageFormat.pvr;
}
get numFrames() {
return 1;
}
decodePvr3Header(bytes) {
const input = new InputBuffer({
buffer: bytes,
});
const size = input.readUint32();
if (size !== PvrDecoder.pvrHeaderSize) {
return undefined;
}
const version = input.readUint32();
const pvr3Signature = 0x03525650;
if (version !== pvr3Signature) {
return undefined;
}
const flags = input.readUint32();
const format = input.readUint32();
const order = [input.read(), input.read(), input.read(), input.read()];
const colorSpace = input.readUint32();
const channelType = input.readUint32();
const height = input.readUint32();
const width = input.readUint32();
const depth = input.readUint32();
const numSurfaces = input.readUint32();
const numFaces = input.readUint32();
const mipCount = input.readUint32();
const metadataSize = input.readUint32();
const info = new Pvr3Info({
flags: flags,
format: format,
order: order,
colorSpace: colorSpace,
channelType: channelType,
height: height,
width: width,
depth: depth,
numSurfaces: numSurfaces,
numFaces: numFaces,
mipCount: mipCount,
metadataSize: metadataSize,
});
return info;
}
decodePvr2Header(bytes) {
const input = new InputBuffer({
buffer: bytes,
});
const size = input.readUint32();
if (size !== PvrDecoder.pvrHeaderSize) {
return undefined;
}
const height = input.readUint32();
const width = input.readUint32();
const mipCount = input.readUint32();
const flags = input.readUint32();
const texDataSize = input.readUint32();
const bitsPerPixel = input.readUint32();
const redMask = input.readUint32();
const greenMask = input.readUint32();
const blueMask = input.readUint32();
const alphaMask = input.readUint32();
const magic = input.readUint32();
const numTex = input.readUint32();
const info = new Pvr2Info({
height: height,
width: width,
mipCount: mipCount,
flags: flags,
texDataSize: texDataSize,
bitsPerPixel: bitsPerPixel,
redMask: redMask,
greenMask: greenMask,
blueMask: blueMask,
alphaMask: alphaMask,
magic: magic,
numTex: numTex,
});
const pvr2Signature = 0x21525650;
if (info.magic !== pvr2Signature) {
return undefined;
}
return info;
}
decodeApplePvrtcHeader(bytes) {
const fileSize = bytes.length;
const input = new InputBuffer({
buffer: bytes,
});
const sz = input.readUint32();
if (sz !== 0) {
return undefined;
}
let height = input.readUint32();
let width = input.readUint32();
const mipCount = input.readUint32();
const flags = input.readUint32();
const texDataSize = input.readUint32();
const bitsPerPixel = input.readUint32();
const redMask = input.readUint32();
const greenMask = input.readUint32();
const blueMask = input.readUint32();
const magic = input.readUint32();
const info = new PvrAppleInfo({
height: height,
width: width,
mipCount: mipCount,
flags: flags,
texDataSize: texDataSize,
bitsPerPixel: bitsPerPixel,
redMask: redMask,
greenMask: greenMask,
blueMask: blueMask,
magic: magic,
});
const appleSignature = 0x21525650;
if (info.magic === appleSignature) {
return undefined;
}
let mode = 1;
let res = 8;
if (fileSize === 32) {
mode = 0;
res = 8;
}
else {
let shift = 0;
const test2bpp = 0x40;
const test4bpp = 0x80;
while (shift < 10) {
const s2 = shift << 1;
if (((test2bpp << s2) & fileSize) !== 0) {
res = 16 << shift;
mode = 1;
break;
}
if (((test4bpp << s2) & fileSize) !== 0) {
res = 16 << shift;
mode = 0;
break;
}
++shift;
}
if (shift === 10) {
return undefined;
}
}
width = res;
height = res;
const bpp = (mode + 1) * 2;
if (bpp === 4) {
return undefined;
}
info.width = width;
info.height = height;
info.bitsPerPixel = bpp;
return info;
}
decodePvr2(data) {
const length = data.length;
const pvrTexCubemap = 1 << 12;
const pvrPixelTypeMask = 0xff;
const pvrTypeRgba4444 = 0x10;
const pvrTypeRgba5551 = 0x11;
const pvrTypeRgba8888 = 0x12;
const pvrTypeRgb565 = 0x13;
const pvrTypeRgb555 = 0x14;
const pvrTypeRgb888 = 0x15;
const pvrTypeI8 = 0x16;
const pvrTypeAI8 = 0x17;
const pvrTypePvrtc2 = 0x18;
const pvrTypePvrtc4 = 0x19;
if (length < PvrDecoder.pvrHeaderSize || this._info === undefined) {
return undefined;
}
const info = this._info;
const input = new InputBuffer({
buffer: data,
});
input.skip(PvrDecoder.pvrHeaderSize);
let numTex = info.numTex;
if (numTex < 1) {
numTex = (info.flags & pvrTexCubemap) !== 0 ? 6 : 1;
}
if (numTex !== 1) {
return undefined;
}
if ((info.width * info.height * info.bitsPerPixel) / 8 >
length - PvrDecoder.pvrHeaderSize) {
return undefined;
}
const pType = info.flags & pvrPixelTypeMask;
switch (pType) {
case pvrTypeRgba4444: {
const image = new MemoryImage({
width: info.width,
height: info.height,
numChannels: 4,
});
for (const p of image) {
const v1 = input.read();
const v2 = input.read();
const a = (v1 & 0x0f) << 4;
const b = v1 & 0xf0;
const g = (v2 & 0x0f) << 4;
const r = v2 & 0xf0;
p.r = r;
p.g = g;
p.b = b;
p.a = a;
}
return image;
}
case pvrTypeRgba5551: {
const image = new MemoryImage({
width: info.width,
height: info.height,
numChannels: 4,
});
for (const p of image) {
const v = input.readUint16();
const r = (v & 0xf800) >> 8;
const g = (v & 0x07c0) >> 3;
const b = (v & 0x003e) << 2;
const a = (v & 0x0001) !== 0 ? 255 : 0;
p.r = r;
p.g = g;
p.b = b;
p.a = a;
}
return image;
}
case pvrTypeRgba8888: {
const image = new MemoryImage({
width: info.width,
height: info.height,
numChannels: 4,
});
for (const p of image) {
p.r = input.read();
p.g = input.read();
p.b = input.read();
p.a = input.read();
}
return image;
}
case pvrTypeRgb565: {
const image = new MemoryImage({
width: info.width,
height: info.height,
});
for (const p of image) {
const v = input.readUint16();
const b = (v & 0x001f) << 3;
const g = (v & 0x07e0) >> 3;
const r = (v & 0xf800) >> 8;
p.r = r;
p.g = g;
p.b = b;
}
return image;
}
case pvrTypeRgb555: {
const image = new MemoryImage({
width: info.width,
height: info.height,
});
for (const p of image) {
const v = input.readUint16();
const r = (v & 0x001f) << 3;
const g = (v & 0x03e0) >> 2;
const b = (v & 0x7c00) >> 7;
p.r = r;
p.g = g;
p.b = b;
}
return image;
}
case pvrTypeRgb888: {
const image = new MemoryImage({
width: info.width,
height: info.height,
});
for (const p of image) {
p.r = input.read();
p.g = input.read();
p.b = input.read();
}
return image;
}
case pvrTypeI8: {
const image = new MemoryImage({
width: info.width,
height: info.height,
numChannels: 1,
});
for (const p of image) {
const i = input.read();
p.r = i;
}
return image;
}
case pvrTypeAI8: {
const image = new MemoryImage({
width: info.width,
height: info.height,
numChannels: 4,
});
for (const p of image) {
const a = input.read();
const i = input.read();
p.r = i;
p.g = i;
p.b = i;
p.a = a;
}
return image;
}
case pvrTypePvrtc2:
return undefined;
case pvrTypePvrtc4:
return info.alphaMask === 0
? this.decodeRgb4bpp(info.width, info.height, input.toUint8Array())
: this.decodeRgba4bpp(info.width, info.height, input.toUint8Array());
}
return undefined;
}
decodePvr3(data) {
if (this._info instanceof Pvr3Info) {
return undefined;
}
const pvr3Pvrtc4bppRgb = 2;
const pvr3Pvrtc4bppRgba = 3;
const input = new InputBuffer({
buffer: data,
});
input.skip(PvrDecoder.pvrHeaderSize);
const info = this._info;
input.skip(info.metadataSize);
if (info.order[0] === 0) {
switch (info.format) {
case pvr3Pvrtc4bppRgb:
return this.decodeRgb4bpp(info.width, info.height, input.toUint8Array());
case pvr3Pvrtc4bppRgba:
return this.decodeRgba4bpp(info.width, info.height, input.toUint8Array());
}
}
return undefined;
}
countBits(x) {
let _x = x;
_x = (_x - ((_x >> 1) & 0x55555555)) & 0xffffffff;
_x = ((_x & 0x33333333) + ((_x >> 2) & 0x33333333)) & 0xffffffff;
_x = (_x + (_x >> 4)) & 0xffffffff;
_x &= 0xf0f0f0f;
_x = ((_x * 0x01010101) & 0xffffffff) >> 24;
return _x;
}
decodeRgb4bpp(width, height, data) {
const result = new MemoryImage({
width: width,
height: height,
});
const blocks = Math.trunc(width / 4);
const blockMask = blocks - 1;
const packet = new PvrPacket(data);
const p0 = new PvrPacket(data);
const p1 = new PvrPacket(data);
const p2 = new PvrPacket(data);
const p3 = new PvrPacket(data);
const factors = PvrPacket.bilinearFactors;
const weights = PvrPacket.weights;
for (let y = 0, y4 = 0; y < blocks; ++y, y4 += 4) {
for (let x = 0, x4 = 0; x < blocks; ++x, x4 += 4) {
packet.setBlock(x, y);
let mod = packet.modulationData;
const weightIndex = packet.usePunchthroughAlpha ? 4 : 0;
let factorIndex = 0;
for (let py = 0; py < 4; ++py) {
const yOffset = py < 2 ? -1 : 0;
const y0 = (y + yOffset) & blockMask;
const y1 = (y0 + 1) & blockMask;
for (let px = 0; px < 4; ++px) {
const xOffset = px < 2 ? -1 : 0;
const x0 = (x + xOffset) & blockMask;
const x1 = (x0 + 1) & blockMask;
p0.setBlock(x0, y0);
p1.setBlock(x1, y0);
p2.setBlock(x0, y1);
p3.setBlock(x1, y1);
const ca = p0
.getColorRgbA()
.mul(factors[factorIndex][0])
.add(p1
.getColorRgbA()
.mul(factors[factorIndex][1])
.add(p2
.getColorRgbA()
.mul(factors[factorIndex][2])
.add(p3.getColorRgbA().mul(factors[factorIndex][3]))));
const cb = p0
.getColorRgbB()
.mul(factors[factorIndex][0])
.add(p1
.getColorRgbB()
.mul(factors[factorIndex][1])
.add(p2
.getColorRgbB()
.mul(factors[factorIndex][2])
.add(p3.getColorRgbB().mul(factors[factorIndex][3]))));
const w = weights[(weightIndex + mod) & 3];
const r = (ca.r * w[0] + cb.r * w[1]) >> 7;
const g = (ca.g * w[0] + cb.g * w[1]) >> 7;
const b = (ca.b * w[0] + cb.b * w[1]) >> 7;
result.setPixelRgb(px + x4, py + y4, r, g, b);
mod >>= 2;
factorIndex++;
}
}
}
}
return result;
}
decodeRgba4bpp(width, height, data) {
const result = new MemoryImage({
width: width,
height: height,
numChannels: 4,
});
const blocks = Math.trunc(width / 4);
const blockMask = blocks - 1;
const packet = new PvrPacket(data);
const p0 = new PvrPacket(data);
const p1 = new PvrPacket(data);
const p2 = new PvrPacket(data);
const p3 = new PvrPacket(data);
const factors = PvrPacket.bilinearFactors;
const weights = PvrPacket.weights;
for (let y = 0, y4 = 0; y < blocks; ++y, y4 += 4) {
for (let x = 0, x4 = 0; x < blocks; ++x, x4 += 4) {
packet.setBlock(x, y);
let mod = packet.modulationData;
const weightIndex = packet.usePunchthroughAlpha ? 4 : 0;
let factorIndex = 0;
for (let py = 0; py < 4; ++py) {
const yOffset = py < 2 ? -1 : 0;
const y0 = (y + yOffset) & blockMask;
const y1 = (y0 + 1) & blockMask;
for (let px = 0; px < 4; ++px) {
const xOffset = px < 2 ? -1 : 0;
const x0 = (x + xOffset) & blockMask;
const x1 = (x0 + 1) & blockMask;
p0.setBlock(x0, y0);
p1.setBlock(x1, y0);
p2.setBlock(x0, y1);
p3.setBlock(x1, y1);
const ca = p0
.getColorRgbaA()
.mul(factors[factorIndex][0])
.add(p1
.getColorRgbaA()
.mul(factors[factorIndex][1])
.add(p2
.getColorRgbaA()
.mul(factors[factorIndex][2])
.add(p3.getColorRgbaA().mul(factors[factorIndex][3]))));
const cb = p0
.getColorRgbaB()
.mul(factors[factorIndex][0])
.add(p1
.getColorRgbaB()
.mul(factors[factorIndex][1])
.add(p2
.getColorRgbaB()
.mul(factors[factorIndex][2])
.add(p3.getColorRgbaB().mul(factors[factorIndex][3]))));
const w = weights[(weightIndex + mod) & 3];
const r = (ca.r * w[0] + cb.r * w[1]) >> 7;
const g = (ca.g * w[0] + cb.g * w[1]) >> 7;
const b = (ca.b * w[0] + cb.b * w[1]) >> 7;
const a = (ca.a * w[2] + cb.a * w[3]) >> 7;
result.setPixelRgba(px + x4, py + y4, r, g, b, a);
mod >>= 2;
factorIndex++;
}
}
}
}
return result;
}
isValidFile(bytes) {
return this.startDecode(bytes) !== undefined;
}
startDecode(bytes) {
if (this.countBits(bytes.length) === 1) {
const info = this.decodeApplePvrtcHeader(bytes);
if (info !== undefined) {
this._data = bytes;
return (this._info = info);
}
}
{
const info = this.decodePvr3Header(bytes);
if (info !== undefined) {
this._data = bytes;
return (this._info = info);
}
}
{
const info = this.decodePvr2Header(bytes);
if (info !== undefined) {
this._data = bytes;
return (this._info = info);
}
}
return undefined;
}
decode(opt) {
var _a;
if (this.startDecode(opt.bytes) === undefined) {
return undefined;
}
return this.decodeFrame((_a = opt.frameIndex) !== null && _a !== void 0 ? _a : 0);
}
decodeFrame(_frameIndex) {
if (this._info === undefined || this._data === undefined) {
return undefined;
}
if (this._info instanceof PvrAppleInfo) {
return this.decodeRgba4bpp(this._info.width, this._info.height, this._data);
}
else if (this._info instanceof Pvr2Info) {
return this.decodePvr2(this._data);
}
else if (this._info instanceof Pvr3Info) {
return this.decodePvr3(this._data);
}
return undefined;
}
}
PvrDecoder.pvrHeaderSize = 52;
//# sourceMappingURL=pvr-decoder.js.map