Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
fiahfy committed Nov 14, 2019
1 parent 271e7f1 commit e57e77c
Show file tree
Hide file tree
Showing 14 changed files with 581 additions and 690 deletions.
129 changes: 126 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- markdownlint-disable MD024 -->
# ico

![badge](https://github.com/fiahfy/ico/workflows/Node.js%20Package/badge.svg)
Expand All @@ -19,7 +20,7 @@ import fs from 'fs'
import Ico from '@fiahfy/ico'

const buf = fs.readFileSync('icon.ico')
const ico = new Ico(buf)
const ico = Ico.from(buf)
console.log(ico.fileHeader) // IcoFileHeader { reserved: 0, type: 1, count: 7 }
console.log(ico.infoHeaders[0]) // IcoInfoHeader { width: 16, height: 16, ... }
```
Expand All @@ -34,16 +35,138 @@ const ico = new Ico()
let buf

buf = fs.readFileSync('128x128.png')
await ico.appendImage(buf)
image = IcoImage.fromPNG(buf)
ico.append(image)

buf = fs.readFileSync('256x256.png')
await ico.appendImage(buf)
image = IcoImage.fromPNG(buf)
ico.append(image)

/* Some other PNG files */

fs.writeFileSync('icon.ico', ico.data)
```

## API

### Class: Ico

#### static from(buffer)

Create ICO from the icon buffer.

##### buffer

Type: `Buffer`

The ICO icon buffer.

#### append(image)

Adds ICO image at the end.

##### image

Type: `IcoImage`

The ICO Image to append.

#### insert(image, index)

Inserts ICO image at the specified position.

##### image

Type: `IcoImage`

The ICO Image to insert.

##### index

Type: `number`

The position at which to insert the ICO Image.

#### remove(index)

Removes ICO image at the specified position.

##### index

Type: `number`

The position of the ICO Image to remove.

#### fileHeader

Type: `IcoFileHeader`

Return the file header on the ICO.

#### infoHeaders

Type: `IcoInfoHeader[]`

Return the ICO info header on the ICO.

#### images

Type: `IcoImage[]`

Return the ICO images on the ICO.

#### data

Type: `Buffer`

Return the ICO buffer.

### Class: IcoImage

#### static from(buffer)

Create ICO image from the buffer.

##### buffer

Type: `Buffer`

The ICO image buffer.

#### static fromPNG(buffer)

Create ICO Image from the PNG image buffer.

##### buffer

Type: `Buffer`

The PNG image buffer.

### Class: IcoInfoHeader

#### static from(buffer)

Create ICO info header from the buffer.

##### buffer

Type: `Buffer`

The ICO info header buffer.

### Class: IcoFileHeader

#### static from(buffer)

Create ICO file header from the buffer.

##### buffer

Type: `Buffer`

The ICO file header buffer.

## Specifications

### Supported Size
Expand Down
12 changes: 5 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"url": "https://github.com/fiahfy/ico/issues"
},
"dependencies": {
"jimp": "^0.8.5"
"pngjs": "^3.4.0"
},
"devDependencies": {
"@types/jest": "^24.0.22",
"@types/jest": "^24.0.23",
"@types/node": "^12.12.7",
"@typescript-eslint/eslint-plugin": "^2.6.1",
"@typescript-eslint/parser": "^2.6.1",
"@types/pngjs": "^3.4.0",
"@typescript-eslint/eslint-plugin": "^2.7.0",
"@typescript-eslint/parser": "^2.7.0",
"eslint": "^6.6.0",
"eslint-config-prettier": "^6.5.0",
"eslint-plugin-prettier": "^3.1.1",
Expand All @@ -26,9 +27,6 @@
"ts-jest": "^24.1.0",
"typescript": "^3.7.2"
},
"engines": {
"node": ">=8"
},
"files": [
"dist"
],
Expand Down
6 changes: 3 additions & 3 deletions src/bitmap-info-header.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export class BitmapInfoHeader {
readonly size: number
readonly size: 40
readonly width: number
readonly height: number
readonly planes: number
Expand All @@ -12,7 +12,7 @@ export class BitmapInfoHeader {
readonly clrImportant: number

constructor(
size = 40,
size: 40 = 40,
width = 0,
height = 0,
planes = 0,
Expand Down Expand Up @@ -42,7 +42,7 @@ export class BitmapInfoHeader {
* @param buffer The bitmap info header.
*/
static from(buffer: Buffer): BitmapInfoHeader {
const size = buffer.readUInt32LE(0)
const size = buffer.readUInt32LE(0) as 40
const width = buffer.readInt32LE(4)
const height = buffer.readInt32LE(8)
const planes = buffer.readUInt16LE(12)
Expand Down
80 changes: 50 additions & 30 deletions src/ico-image.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Bitmap } from 'jimp'
import { PNG } from 'pngjs'
import { BitmapInfoHeader } from './bitmap-info-header'
import { Ico } from './ico'

export class IcoImage {
readonly header: BitmapInfoHeader
Expand Down Expand Up @@ -41,41 +42,42 @@ export class IcoImage {
return new IcoImage(header, xor, and)
}

static create(bitmap: Bitmap): IcoImage {
const width = bitmap.width
const height = bitmap.height * 2 // image + mask
const planes = 1
const bitCount = (bitmap as any).bpp * 8 // byte per pixel * 8
/**
* Create ICO Image from the PNG image buffer.
* @param buffer The PNG image buffer.
*/
static fromPNG(buffer: Buffer): IcoImage {
const png = IcoImage.readPNG(buffer)
if (!png) {
throw new TypeError('Image must be PNG format')
}

const xorSize = bitmap.height * bitmap.width * (bitmap as any).bpp
const andSize =
((bitmap.width + (bitmap.width % 32 ? 32 - (bitmap.width % 32) : 0)) *
bitmap.height) /
8
const sizeImage = xorSize + andSize
const width = png.width
let height = png.height
if (width !== height) {
throw new TypeError('Image must be squre')
}
const supported = Ico.supportedIconSizes.includes(width)
if (!supported) {
throw new TypeError('No supported size')
}

const header = new BitmapInfoHeader(
40,
width,
height,
planes,
bitCount,
0,
sizeImage
)
height *= 2 // image + mask
const planes = 1
const bitCount = (png as any).bpp * 8 // byte per pixel * 8

const xors = []
let andBits: number[] = []

// Convert Top/Left to Bottom/Left
for (let y = bitmap.height - 1; y >= 0; y--) {
for (let x = 0; x < bitmap.width; x++) {
for (let y = height - 1; y >= 0; y--) {
for (let x = 0; x < width; x++) {
// RGBA to BGRA
const pos = (y * bitmap.width + x) * (bitmap as any).bpp
const red = bitmap.data.slice(pos, pos + 1)
const green = bitmap.data.slice(pos + 1, pos + 2)
const blue = bitmap.data.slice(pos + 2, pos + 3)
const alpha = bitmap.data.slice(pos + 3, pos + 4)
const pos = (y * width + x) * (png as any).bpp
const red = png.data.slice(pos, pos + 1)
const green = png.data.slice(pos + 1, pos + 2)
const blue = png.data.slice(pos + 2, pos + 3)
const alpha = png.data.slice(pos + 3, pos + 4)
xors.push(blue)
xors.push(green)
xors.push(red)
Expand All @@ -95,8 +97,18 @@ export class IcoImage {
ands.push(buffer)
}

const xor = Buffer.concat(xors, xorSize)
const and = Buffer.concat(ands, andSize)
const xor = Buffer.concat(xors)
const and = Buffer.concat(ands)

const header = new BitmapInfoHeader(
40,
width,
height,
planes,
bitCount,
0,
xor.length + and.length
)

return new IcoImage(header, xor, and)
}
Expand All @@ -105,4 +117,12 @@ export class IcoImage {
const buffers = [this.header.data, this.xor, this.and]
return Buffer.concat(buffers)
}

private static readPNG(buffer: Buffer): PNG | undefined {
try {
return PNG.sync.read(buffer)
} catch (e) {
return undefined
}
}
}
Loading

0 comments on commit e57e77c

Please sign in to comment.