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)
302 lines • 12.9 kB
JavaScript
import { OutputBuffer } from '../common/output-buffer.js';
import { LibError } from '../error/lib-error.js';
import { PvrBitUtility } from './pvr/pvr-bit-utility.js';
import { PvrColorBoundingBox } from './pvr/pvr-color-bounding-box.js';
import { PvrColorRgb } from './pvr/pvr-color-rgb.js';
import { PvrColorRgba } from './pvr/pvr-color-rgba.js';
import { PvrFormat } from './pvr/pvr-format.js';
import { PvrPacket } from './pvr/pvr-packet.js';
export class PvrEncoder {
get supportsAnimation() {
return this._supportsAnimation;
}
constructor(format = PvrFormat.auto) {
this._supportsAnimation = false;
this._format = format;
}
static calculateBoundingBoxRgb(bitmap, blockX, blockY) {
const pixel = (x, y) => {
const p = bitmap.getPixel(blockX + x, blockY + y);
return new PvrColorRgb(Math.trunc(p.r), Math.trunc(p.g), Math.trunc(p.b));
};
const cbb = new PvrColorBoundingBox(pixel(0, 0), pixel(0, 0));
cbb.add(pixel(1, 0));
cbb.add(pixel(2, 0));
cbb.add(pixel(3, 0));
cbb.add(pixel(0, 1));
cbb.add(pixel(1, 1));
cbb.add(pixel(1, 2));
cbb.add(pixel(1, 3));
cbb.add(pixel(2, 0));
cbb.add(pixel(2, 1));
cbb.add(pixel(2, 2));
cbb.add(pixel(2, 3));
cbb.add(pixel(3, 0));
cbb.add(pixel(3, 1));
cbb.add(pixel(3, 2));
cbb.add(pixel(3, 3));
return cbb;
}
static calculateBoundingBoxRgba(bitmap, blockX, blockY) {
const pixel = (x, y) => {
const p = bitmap.getPixel(blockX + x, blockY + y);
return new PvrColorRgba(Math.trunc(p.r), Math.trunc(p.g), Math.trunc(p.b), Math.trunc(p.a));
};
const cbb = new PvrColorBoundingBox(pixel(0, 0), pixel(0, 0));
cbb.add(pixel(1, 0));
cbb.add(pixel(2, 0));
cbb.add(pixel(3, 0));
cbb.add(pixel(0, 1));
cbb.add(pixel(1, 1));
cbb.add(pixel(1, 2));
cbb.add(pixel(1, 3));
cbb.add(pixel(2, 0));
cbb.add(pixel(2, 1));
cbb.add(pixel(2, 2));
cbb.add(pixel(2, 3));
cbb.add(pixel(3, 0));
cbb.add(pixel(3, 1));
cbb.add(pixel(3, 2));
cbb.add(pixel(3, 3));
return cbb;
}
encode(opt) {
const output = new OutputBuffer();
let format = this._format;
let pvrtc = undefined;
switch (format) {
case PvrFormat.auto:
if (opt.image.numChannels === 3) {
pvrtc = this.encodeRgb4bpp(opt.image);
format = PvrFormat.rgb4;
}
else {
pvrtc = this.encodeRgba4bpp(opt.image);
format = PvrFormat.rgba4;
}
break;
case PvrFormat.rgb2:
pvrtc = this.encodeRgb4bpp(opt.image);
break;
case PvrFormat.rgba2:
pvrtc = this.encodeRgba4bpp(opt.image);
break;
case PvrFormat.rgb4:
pvrtc = this.encodeRgb4bpp(opt.image);
break;
case PvrFormat.rgba4:
pvrtc = this.encodeRgba4bpp(opt.image);
break;
}
const version = 55727696;
const flags = 0;
const pixelFormat = format - 1;
const channelOrder = 0;
const colorSpace = 0;
const channelType = 0;
const height = opt.image.height;
const width = opt.image.width;
const depth = 1;
const numSurfaces = 1;
const numFaces = 1;
const mipmapCount = 1;
const metaDataSize = 0;
output.writeUint32(version);
output.writeUint32(flags);
output.writeUint32(pixelFormat);
output.writeUint32(channelOrder);
output.writeUint32(colorSpace);
output.writeUint32(channelType);
output.writeUint32(height);
output.writeUint32(width);
output.writeUint32(depth);
output.writeUint32(numSurfaces);
output.writeUint32(numFaces);
output.writeUint32(mipmapCount);
output.writeUint32(metaDataSize);
output.writeBytes(pvrtc);
return output.getBytes();
}
encodeRgb4bpp(bitmap) {
if (bitmap.width !== bitmap.height) {
throw new LibError('PVRTC requires a square image.');
}
if (!PvrBitUtility.isPowerOf2(bitmap.width)) {
throw new LibError('PVRTC requires a power-of-two sized image.');
}
const size = bitmap.width;
const blocks = Math.trunc(size / 4);
const blockMask = blocks - 1;
const outputData = new Uint8Array(Math.trunc((bitmap.width * bitmap.height) / 2));
const packet = new PvrPacket(outputData);
const p0 = new PvrPacket(outputData);
const p1 = new PvrPacket(outputData);
const p2 = new PvrPacket(outputData);
const p3 = new PvrPacket(outputData);
for (let y = 0; y < blocks; ++y) {
for (let x = 0; x < blocks; ++x) {
const cbb = PvrEncoder.calculateBoundingBoxRgb(bitmap, x, y);
packet.setBlock(x, y);
packet.usePunchthroughAlpha = false;
packet.setColorRgbA(cbb.min);
packet.setColorRgbB(cbb.max);
}
}
const factors = PvrPacket.bilinearFactors;
for (let y = 0, y4 = 0; y < blocks; ++y, y4 += 4) {
for (let x = 0, x4 = 0; x < blocks; ++x, x4 += 4) {
let factorIndex = 0;
let modulationData = 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 pi = bitmap.getPixel(x4 + px, y4 + py);
const r = Math.trunc(pi.r);
const g = Math.trunc(pi.g);
const b = Math.trunc(pi.b);
const d = cb.sub(ca);
const p = new PvrColorRgb(r * 16, g * 16, b * 16);
const v = p.sub(ca);
const projection = v.dotProd(d) * 16;
const lengthSquared = d.dotProd(d);
if (projection > 3 * lengthSquared) {
modulationData++;
}
if (projection > 8 * lengthSquared) {
modulationData++;
}
if (projection > 13 * lengthSquared) {
modulationData++;
}
modulationData = PvrBitUtility.rotateRight(modulationData, 2);
factorIndex++;
}
}
packet.setBlock(x, y);
packet.modulationData = modulationData;
}
}
return outputData;
}
encodeRgba4bpp(bitmap) {
if (bitmap.width !== bitmap.height) {
throw new LibError('PVRTC requires a square image.');
}
if (!PvrBitUtility.isPowerOf2(bitmap.width)) {
throw new LibError('PVRTC requires a power-of-two sized image.');
}
const size = bitmap.width;
const blocks = Math.trunc(size / 4);
const blockMask = blocks - 1;
const outputData = new Uint8Array(Math.trunc((bitmap.width * bitmap.height) / 2));
const packet = new PvrPacket(outputData);
const p0 = new PvrPacket(outputData);
const p1 = new PvrPacket(outputData);
const p2 = new PvrPacket(outputData);
const p3 = new PvrPacket(outputData);
for (let y = 0, y4 = 0; y < blocks; ++y, y4 += 4) {
for (let x = 0, x4 = 0; x < blocks; ++x, x4 += 4) {
const cbb = PvrEncoder.calculateBoundingBoxRgba(bitmap, x4, y4);
packet.setBlock(x, y);
packet.usePunchthroughAlpha = false;
packet.setColorRgbaA(cbb.min);
packet.setColorRgbaB(cbb.max);
}
}
const factors = PvrPacket.bilinearFactors;
for (let y = 0, y4 = 0; y < blocks; ++y, y4 += 4) {
for (let x = 0, x4 = 0; x < blocks; ++x, x4 += 4) {
let factorIndex = 0;
let modulationData = 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 bp = bitmap.getPixel(x4 + px, y4 + py);
const r = Math.trunc(bp.r);
const g = Math.trunc(bp.g);
const b = Math.trunc(bp.b);
const a = Math.trunc(bp.a);
const d = cb.sub(ca);
const p = new PvrColorRgba(r * 16, g * 16, b * 16, a * 16);
const v = p.sub(ca);
const projection = v.dotProd(d) * 16;
const lengthSquared = d.dotProd(d);
if (projection > 3 * lengthSquared) {
modulationData++;
}
if (projection > 8 * lengthSquared) {
modulationData++;
}
if (projection > 13 * lengthSquared) {
modulationData++;
}
modulationData = PvrBitUtility.rotateRight(modulationData, 2);
factorIndex++;
}
}
packet.setBlock(x, y);
packet.modulationData = modulationData;
}
}
return outputData;
}
}
//# sourceMappingURL=pvr-encoder.js.map