diff --git a/src/bitmap-info-header.ts b/src/bitmap-info-header.ts index e2f5d25..f4655aa 100644 --- a/src/bitmap-info-header.ts +++ b/src/bitmap-info-header.ts @@ -1,15 +1,15 @@ export class BitmapInfoHeader { - size: number - width: number - height: number - planes: number - bitCount: number - compression: number - sizeImage: number - xPelsPerMeter: number - yPelsPerMeter: number - clrUsed: number - clrImportant: number + readonly size: number + readonly width: number + readonly height: number + readonly planes: number + readonly bitCount: number + readonly compression: number + readonly sizeImage: number + readonly xPelsPerMeter: number + readonly yPelsPerMeter: number + readonly clrUsed: number + readonly clrImportant: number constructor( size = 40, @@ -37,6 +37,37 @@ export class BitmapInfoHeader { this.clrImportant = clrImportant } + /** + * Create bitmap info header from the buffer. + * @param buffer The bitmap info header. + */ + static from(buffer: Buffer): BitmapInfoHeader { + const size = buffer.readUInt32LE(0) + const width = buffer.readInt32LE(4) + const height = buffer.readInt32LE(8) + const planes = buffer.readUInt16LE(12) + const bitCount = buffer.readUInt16LE(14) + const compression = buffer.readUInt32LE(16) + const sizeImage = buffer.readUInt32LE(20) + const xPelsPerMeter = buffer.readInt32LE(24) + const yPelsPerMeter = buffer.readInt32LE(28) + const clrUsed = buffer.readUInt32LE(32) + const clrImportant = buffer.readUInt32LE(36) + return new BitmapInfoHeader( + size, + width, + height, + planes, + bitCount, + compression, + sizeImage, + xPelsPerMeter, + yPelsPerMeter, + clrUsed, + clrImportant + ) + } + get data(): Buffer { const buffer = Buffer.alloc(40) buffer.writeUInt32LE(this.size, 0) @@ -52,18 +83,4 @@ export class BitmapInfoHeader { buffer.writeUInt32LE(this.clrImportant, 36) return buffer } - - set data(buffer) { - this.size = buffer.readUInt32LE(0) - this.width = buffer.readInt32LE(4) - this.height = buffer.readInt32LE(8) - this.planes = buffer.readUInt16LE(12) - this.bitCount = buffer.readUInt16LE(14) - this.compression = buffer.readUInt32LE(16) - this.sizeImage = buffer.readUInt32LE(20) - this.xPelsPerMeter = buffer.readInt32LE(24) - this.yPelsPerMeter = buffer.readInt32LE(28) - this.clrUsed = buffer.readUInt32LE(32) - this.clrImportant = buffer.readUInt32LE(36) - } } diff --git a/src/ico-file-header.ts b/src/ico-file-header.ts index ed51130..f054888 100644 --- a/src/ico-file-header.ts +++ b/src/ico-file-header.ts @@ -1,7 +1,7 @@ export class IcoFileHeader { - reserved: number - type: number - count: number + readonly reserved: number + readonly type: number + readonly count: number constructor(reserved = 0, type = 1, count = 0) { this.reserved = reserved @@ -9,6 +9,17 @@ export class IcoFileHeader { this.count = count } + /** + * Create ICO file header from the buffer. + * @param buffer The ICO file header buffer. + */ + static from(buffer: Buffer): IcoFileHeader { + const reserved = buffer.readUInt16LE(0) + const type = buffer.readUInt16LE(2) + const count = buffer.readUInt16LE(4) + return new IcoFileHeader(reserved, type, count) + } + get data(): Buffer { const buffer = Buffer.alloc(6) buffer.writeUInt16LE(this.reserved, 0) @@ -16,10 +27,4 @@ export class IcoFileHeader { buffer.writeUInt16LE(this.count, 4) return buffer } - - set data(buffer) { - this.reserved = buffer.readUInt16LE(0) - this.type = buffer.readUInt16LE(2) - this.count = buffer.readUInt16LE(4) - } } diff --git a/src/ico-image.ts b/src/ico-image.ts index 817cb32..8a4199a 100644 --- a/src/ico-image.ts +++ b/src/ico-image.ts @@ -2,9 +2,10 @@ import { Bitmap } from 'jimp' import { BitmapInfoHeader } from './bitmap-info-header' export class IcoImage { - header: BitmapInfoHeader - xor: Buffer - and: Buffer + readonly header: BitmapInfoHeader + readonly xor: Buffer + readonly and: Buffer + constructor( header = new BitmapInfoHeader(), xor = Buffer.alloc(0), @@ -14,32 +15,32 @@ export class IcoImage { this.xor = xor this.and = and } - get data(): Buffer { - const list = [this.header.data, this.xor, this.and] - const totalLength = list.reduce((carry, buffer) => carry + buffer.length, 0) - return Buffer.concat(list, totalLength) - } - set data(buffer) { - this.header.data = buffer + + /** + * Create ICO image from the buffer. + * @param buffer The ICO image buffer. + */ + static from(buffer: Buffer): IcoImage { + const header = BitmapInfoHeader.from(buffer) // TODO: only 32 bpp supported // no colors when bpp is 16 or more - let pos = this.header.data.length - const xorSize = - (((this.header.width * this.header.height) / 2) * this.header.bitCount) / - 8 - this.xor = buffer.slice(pos, pos + xorSize) + let pos = header.data.length + const xorSize = (((header.width * header.height) / 2) * header.bitCount) / 8 + const xor = buffer.slice(pos, pos + xorSize) pos += xorSize const andSize = - ((this.header.width + - (this.header.width % 32 ? 32 - (this.header.width % 32) : 0)) * - this.header.height) / + ((header.width + (header.width % 32 ? 32 - (header.width % 32) : 0)) * + header.height) / 2 / 8 - this.and = buffer.slice(pos, pos + andSize) + const and = buffer.slice(pos, pos + andSize) + + return new IcoImage(header, xor, and) } + static create(bitmap: Bitmap): IcoImage { const width = bitmap.width const height = bitmap.height * 2 // image + mask @@ -99,4 +100,9 @@ export class IcoImage { return new IcoImage(header, xor, and) } + + get data(): Buffer { + const buffers = [this.header.data, this.xor, this.and] + return Buffer.concat(buffers) + } } diff --git a/src/ico-info-header.ts b/src/ico-info-header.ts index 6933513..be1a177 100644 --- a/src/ico-info-header.ts +++ b/src/ico-info-header.ts @@ -1,12 +1,12 @@ export class IcoInfoHeader { - width: number - height: number - colorCount: number - reserved: number - planes: number - bitCount: number - bytesInRes: number - imageOffset: number + readonly width: number + readonly height: number + readonly colorCount: number + readonly reserved: number + readonly planes: number + readonly bitCount: number + readonly bytesInRes: number + readonly imageOffset: number constructor( width = 0, @@ -28,6 +28,35 @@ export class IcoInfoHeader { this.imageOffset = imageOffset } + /** + * Create ICO info header from the buffer. + * @param buffer The ICO info header image buffer. + */ + static from(buffer: Buffer): IcoInfoHeader { + const width = buffer.readUInt8(0) + const height = buffer.readUInt8(1) + const colorCount = buffer.readUInt8(2) + const reserved = buffer.readUInt8(3) + const planes = buffer.readUInt16LE(4) + const bitCount = buffer.readUInt16LE(6) + const bytesInRes = buffer.readUInt32LE(8) + const imageOffset = buffer.readUInt32LE(12) + if (bitCount !== 32) { + // TODO: only 32 bpp supported + throw new Error('Only 32 bpp supported') + } + return new IcoInfoHeader( + width, + height, + colorCount, + reserved, + planes, + bitCount, + bytesInRes, + imageOffset + ) + } + get data(): Buffer { const buffer = Buffer.alloc(16) buffer.writeUInt8(this.width, 0) @@ -40,20 +69,4 @@ export class IcoInfoHeader { buffer.writeUInt32LE(this.imageOffset, 12) return buffer } - - set data(buffer) { - this.width = buffer.readUInt8(0) - this.height = buffer.readUInt8(1) - this.colorCount = buffer.readUInt8(2) - this.reserved = buffer.readUInt8(3) - this.planes = buffer.readUInt16LE(4) - this.bitCount = buffer.readUInt16LE(6) - this.bytesInRes = buffer.readUInt32LE(8) - this.imageOffset = buffer.readUInt32LE(12) - - if (this.bitCount !== 32) { - // TODO: only 32 bpp supported - throw new Error('Only 32 bpp supported') - } - } } diff --git a/src/ico.ts b/src/ico.ts index a4753fc..3cc48de 100644 --- a/src/ico.ts +++ b/src/ico.ts @@ -2,71 +2,100 @@ import Jimp from 'jimp' import { IcoFileHeader } from './ico-file-header' import { IcoInfoHeader } from './ico-info-header' import { IcoImage } from './ico-image' +import { file } from '@babel/types' export class Ico { - static readonly supportedSizes = [16, 24, 32, 48, 64, 128, 256] - - fileHeader: IcoFileHeader - infoHeaders: IcoInfoHeader[] - images: IcoImage[] - - constructor(buffer?: Buffer) { - this.fileHeader = new IcoFileHeader() - this.infoHeaders = [] - this.images = [] - if (buffer) { - this.data = buffer - } - } + static readonly supportedIconSizes = [16, 24, 32, 48, 64, 128, 256] - get data(): Buffer { - const buffers = [ - this.fileHeader.data, - ...this.infoHeaders.map((infoHeader) => infoHeader.data), - ...this.images.map((image) => image.data) - ] - return Buffer.concat(buffers) + private _fileHeader: IcoFileHeader + private _infoHeaders: ReadonlyArray + private _images: ReadonlyArray + + constructor( + fileHeader = new IcoFileHeader(), + infoHeaders: IcoInfoHeader[] = [], + images: IcoImage[] = [] + ) { + this._fileHeader = fileHeader + this._infoHeaders = infoHeaders + this._images = images } - set data(buffer) { - this.fileHeader.data = buffer + /** + * Create ICO from the icon buffer. + * @param buffer The ICO icon buffer. + */ + static from(buffer: Buffer): Ico { + const fileHeader = IcoFileHeader.from(buffer) - let pos = this.fileHeader.data.length + let pos = fileHeader.data.length const infoHeaders = [] - for (let i = 0; i < this.fileHeader.count; i++) { - const infoHeader = new IcoInfoHeader() - infoHeader.data = buffer.slice(pos) + for (let i = 0; i < fileHeader.count; i++) { + const infoHeader = IcoInfoHeader.from(buffer.slice(pos)) infoHeaders.push(infoHeader) pos += infoHeader.data.length } - this.infoHeaders = infoHeaders const images = [] - for (let i = 0; i < this.infoHeaders.length; i++) { - const { imageOffset: pos } = this.infoHeaders[i] - const image = new IcoImage() - image.data = buffer.slice(pos) + for (let i = 0; i < infoHeaders.length; i++) { + const { imageOffset: pos } = infoHeaders[i] + const image = IcoImage.from(buffer.slice(pos)) images.push(image) } - this.images = images + + return new Ico(fileHeader, infoHeaders, images) + } + + get fileHeader(): IcoFileHeader { + return this._fileHeader + } + + get infoHeaders(): ReadonlyArray { + return this._infoHeaders + } + + set infoHeaders(infoHeaders: ReadonlyArray) { + this._infoHeaders = infoHeaders + + const count = this._infoHeaders.length + + this._fileHeader = new IcoFileHeader( + this._fileHeader.reserved, + this._fileHeader.type, + count + ) } - private resetHeader(): void { - this.fileHeader.count = this.infoHeaders.length + get images(): ReadonlyArray { + return this._images + } + + set images(images: ReadonlyArray) { + this._images = images let imageOffset = - this.fileHeader.data.length + - this.infoHeaders.reduce( + this._fileHeader.data.length + + this._infoHeaders.reduce( (carry, infoHeader) => carry + infoHeader.data.length, 0 ) - this.infoHeaders = this.infoHeaders.map((infoHeader) => { + + const infoHeaders = this._infoHeaders.map((infoHeader) => { infoHeader.imageOffset = imageOffset imageOffset += infoHeader.bytesInRes return infoHeader }) } + get data(): Buffer { + const buffers = [ + this._fileHeader.data, + ...this._infoHeaders.map((infoHeader) => infoHeader.data), + ...this._images.map((image) => image.data) + ] + return Buffer.concat(buffers) + } + async appendImage(buffer: Buffer): Promise { await this.insertImage(buffer, this.fileHeader.count) } diff --git a/src/index.ts b/src/index.ts index c524aa6..523b35b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,4 @@ export * from './bitmap-info-header' export * from './ico-file-header' export * from './ico-info-header' export * from './ico-image' -import { Ico } from './ico' -export default Ico +export * from './ico'