-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a3f4023
commit a660ba4
Showing
30 changed files
with
2,124 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,124 @@ | ||
# image-size | ||
A stream based solution of https://github.com/image-size/image-size | ||
# @khoohaoyit/image-size | ||
|
||
> A stream based image size extractor in [Node](https://nodejs.org) | ||
Features: | ||
- Support variaty of formats | ||
- Low memory footprint | ||
- API compatible with [image-size](https://github.com/image-size/image-size) | ||
- Fine control over when to stop parsing | ||
- No dependancy | ||
|
||
## Installation | ||
```sh | ||
npm install @khoohaoyit/image-size | ||
``` | ||
|
||
## Usage | ||
|
||
### Processing buffer | ||
```ts | ||
import { SizeExtractor } from "@khoohaoyit/image-size"; | ||
import { finished } from 'stream/promises'; | ||
|
||
const extractor = new SizeExtractor({ passthrough: false }); | ||
await finished( | ||
extractor | ||
.end(buffer) | ||
); | ||
console.log(extractor.imageSize); // Compatable with `image-size` | ||
``` | ||
|
||
### Extract image size while writing to a file | ||
```ts | ||
import { SizeExtractor } from "@khoohaoyit/image-size"; | ||
import { pipeline } from 'stream/promises'; | ||
import { createWriteStream } from 'fs'; | ||
|
||
const extractor = new SizeExtractor({ passthrough: true }); | ||
await pipeline( | ||
await fetch('https://github.githubassets.com/assets/gc_banner_dark-b73fa80c5473.png') | ||
.then(res => res.body), | ||
extractor, | ||
createWriteStream('imageA.png'), | ||
); | ||
console.log(extractor.sizes); | ||
``` | ||
|
||
### Stop processing once it has done parsing all formats | ||
```ts | ||
import { SizeExtractor } from "@khoohaoyit/image-size"; | ||
import { pipeline } from 'stream/promises'; | ||
import { createReadStream } from 'fs'; | ||
|
||
const extractor = new SizeExtractor({ passthrough: false }); | ||
const controller = new AbortController; | ||
await pipeline( | ||
createReadStream('imageA.png'), | ||
extractor | ||
.once('parseAllDone', () => controller.abort()), | ||
{ signal: controller.signal }, | ||
).catch(err => { | ||
if (controller.signal.aborted) | ||
return; | ||
throw err; | ||
}); | ||
console.log(extractor.sizes); | ||
``` | ||
|
||
## Library Comparison | ||
| | [@khoohaoyit/image-size](https://github.com/KhooHaoYit/image-size.git) | [probe-image-size](https://github.com/nodeca/probe-image-size) | [image-size](https://github.com/image-size/image-size) | | ||
| :-: | :-: | :-: | :-: | | ||
| Stream based | Yes | Yes | No | | ||
| Support sync | No | Yes | Yes | | ||
| `dds` | Yes | No | Yes | | ||
| `icns` | Yes | No | Yes | | ||
| `j2c` | Yes | No | Yes | | ||
| `jp2` | Yes | No | Yes | | ||
| `ktx` | Yes | No | Yes | | ||
| `pnm` | Yes | No | Yes | | ||
| `tga` | Yes | No | Yes | | ||
| `avif` | No | Yes | No | | ||
| `heic` | No | Yes | No | | ||
| `heif` | No | Yes | No | | ||
|
||
The listed library on top all supports `ico`, `cur`, `bmp`, `gif`, `jpg`, `png`, `psd`, `svg`, `tiff`, and `webp` | ||
|
||
## Documentation | ||
|
||
### `new SizeExtractor(options)` | ||
- `options.passthrough: boolean` | ||
- Passes the input to output if `true` | ||
- `options.whitelistFormats?: string[]` | ||
- Whitelist specific formats | ||
- `options.blacklistFormats?: string[]` | ||
- Blacklist specific formats, ignored if `whitelistFormats` is set | ||
|
||
### `SizeExtractor.imageSize` | ||
|
||
The API compatible of [image-size](https://github.com/image-size/image-size) result | ||
|
||
### `SizeExtractor.sizes` | ||
|
||
The extracted sizes emitted from `'size'` | ||
|
||
### Event: `'size'` | ||
- `data: { width: number, height: number }` | ||
- `format: string` | ||
|
||
Emitted when a format has successfully extracted the image size | ||
|
||
### Event: `'parseError'` | ||
- `error: unknown` | ||
- `format: string` | ||
|
||
Emitted when a format encountered unexpected error while parsing | ||
|
||
### Event: `'parseDone'` | ||
- `format: string` | ||
|
||
Emitted when a format has ended parsing | ||
|
||
### Event: `'parseAllDone'` | ||
|
||
Emitted when all formats has ended parsing |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
const TYPE_ICON = 1; | ||
const TYPE_CURSOR = 2; | ||
const SIZE_HEADER = 2 + 2 + 2; | ||
const SIZE_IMAGE_ENTRY = 1 + 1 + 1 + 1 + 2 + 2 + 4 + 4; | ||
|
||
const extractSize = (targetImageType: number) => { | ||
return async function* (stream) { | ||
const reserved = await stream.readUInt16LE(0); | ||
const imageCount = await stream.readUInt16LE(4); | ||
if (reserved !== 0 || imageCount === 0) | ||
return; | ||
const imageType = await stream.readUInt16LE(2); | ||
if (imageType !== targetImageType) | ||
return; | ||
for ( | ||
let amount = imageCount, offset = SIZE_HEADER | ||
; amount | ||
; --amount, offset += SIZE_IMAGE_ENTRY | ||
) { | ||
yield { | ||
width: await stream.readUint8(offset) || 256, | ||
height: await stream.readUint8(offset + 1) || 256, | ||
}; | ||
} | ||
} satisfies ExtractSize; | ||
} | ||
|
||
export const extractIcoSize = extractSize(TYPE_ICON); | ||
export const extractCurSize = extractSize(TYPE_CURSOR); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractBmpSize = async function* (stream) { | ||
if (await stream.read(0, 2) + '' !== 'BM') | ||
return; | ||
return yield { | ||
width: await stream.readUint32LE(18), | ||
height: Math.abs(await stream.readInt32LE(22)), | ||
}; | ||
} satisfies ExtractSize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractDdsSize = async function* (stream) { | ||
if (await stream.readString(0, 4) !== 'DDS ') | ||
return; | ||
return yield { | ||
height: await stream.readUint32LE(12), | ||
width: await stream.readUint32LE(16), | ||
}; | ||
} satisfies ExtractSize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractGifSize = async function* (stream) { | ||
if (!/^GIF8[79]a$/.test(await stream.read(0, 6) + '')) | ||
return; | ||
return yield { | ||
width: await stream.readUint16LE(6), | ||
height: await stream.readUint16LE(8), | ||
}; | ||
} satisfies ExtractSize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractIcnsSize = async function* (stream) { | ||
if (await stream.readString(0, 4) !== 'icns') | ||
return; | ||
const fileLength = await stream.readUint32BE(FILE_LENGTH_OFFSET); | ||
let imageOffset = SIZE_HEADER; | ||
do { | ||
const iconType = await stream.readString(imageOffset, ENTRY_LENGTH_OFFSET); | ||
const size = ICON_TYPE_SIZE[iconType]; | ||
if (!size) | ||
throw new Error(`Unknown iconType: ${iconType}`); | ||
yield { width: size, height: size, type: iconType }; | ||
imageOffset += await stream.readUint32BE(imageOffset + ENTRY_LENGTH_OFFSET); | ||
} while (imageOffset !== fileLength); | ||
} satisfies ExtractSize; | ||
|
||
const SIZE_HEADER = 4 + 4; | ||
const FILE_LENGTH_OFFSET = 4; | ||
const ENTRY_LENGTH_OFFSET = 4; | ||
const ICON_TYPE_SIZE: Record<string, number | undefined> = { | ||
ICON: 32, | ||
'ICN#': 32, | ||
// m => 16 x 16 | ||
'icm#': 16, | ||
icm4: 16, | ||
icm8: 16, | ||
// s => 16 x 16 | ||
'ics#': 16, | ||
ics4: 16, | ||
ics8: 16, | ||
is32: 16, | ||
s8mk: 16, | ||
icp4: 16, | ||
// l => 32 x 32 | ||
icl4: 32, | ||
icl8: 32, | ||
il32: 32, | ||
l8mk: 32, | ||
icp5: 32, | ||
ic11: 32, | ||
// h => 48 x 48 | ||
ich4: 48, | ||
ich8: 48, | ||
ih32: 48, | ||
h8mk: 48, | ||
// . => 64 x 64 | ||
icp6: 64, | ||
ic12: 32, | ||
// t => 128 x 128 | ||
it32: 128, | ||
t8mk: 128, | ||
ic07: 128, | ||
// . => 256 x 256 | ||
ic08: 256, | ||
ic13: 256, | ||
// . => 512 x 512 | ||
ic09: 512, | ||
ic14: 512, | ||
// . => 1024 x 1024 | ||
ic10: 1024, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
import { | ||
extractCurSize, | ||
extractIcoSize, | ||
} from "./[ico,cur]"; | ||
import { extractBmpSize } from "./bmp"; | ||
import { extractDdsSize } from "./dds"; | ||
import { extractGifSize } from "./gif"; | ||
import { extractIcnsSize } from "./icns"; | ||
import { extractJ2cSize } from "./j2c"; | ||
import { extractJp2Size } from "./jp2"; | ||
import { extractJpgSize } from "./jpg"; | ||
import { extractKtxSize } from "./ktx"; | ||
import { extractPngSize } from "./png"; | ||
import { extractPnmSize } from "./pnm"; | ||
import { extractPsdSize } from "./psd"; | ||
import { extractSvgSize } from "./svg"; | ||
import { extractTgaSize } from "./tga"; | ||
import { extractTiffSize } from "./tiff"; | ||
import { extractWebpSize } from "./webp"; | ||
|
||
export const formats = <const>[ | ||
['ico', extractIcoSize], | ||
['cur', extractCurSize], | ||
|
||
['bmp', extractBmpSize], | ||
['dds', extractDdsSize], | ||
['gif', extractGifSize], | ||
['icns', extractIcnsSize], | ||
['j2c', extractJ2cSize], | ||
['jp2', extractJp2Size], | ||
['jpg', extractJpgSize], | ||
['ktx', extractKtxSize], | ||
['png', extractPngSize], | ||
['pnm', extractPnmSize], | ||
['psd', extractPsdSize], | ||
['svg', extractSvgSize], | ||
['tga', extractTgaSize], | ||
['tiff', extractTiffSize], | ||
['webp', extractWebpSize], | ||
] satisfies ReadonlyArray<readonly [string, ExtractSize]>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractJ2cSize = async function* (stream) { | ||
if (await stream.readUInt32BE(0) !== 0xff4fff51) | ||
return; | ||
return yield { | ||
width: await stream.readUint32BE(8), | ||
height: await stream.readUint32BE(12), | ||
}; | ||
} satisfies ExtractSize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { ExtractSize } from "../utils/sizeExtractor"; | ||
|
||
export const extractJp2Size = async function* (stream) { | ||
const signatureLength = await stream.readUInt32BE(0); | ||
const signature = await stream.readString(4, 4); | ||
if (signature !== BoxTypes.jp__ || signatureLength < 1) | ||
return; | ||
const ftypeBoxStart = signatureLength + 4; | ||
const ftypBoxLength = await stream.readUInt32BE(signatureLength); | ||
if (await stream.readString(ftypeBoxStart, 4) !== BoxTypes.ftyp) | ||
return; | ||
let ihdrOffset = signatureLength + 4 + ftypBoxLength; | ||
switch (await stream.readString(ihdrOffset, 4)) { | ||
default: | ||
return; | ||
case BoxTypes.rreq: { | ||
ihdrOffset += 4; | ||
const unit = await stream.readUint8(ihdrOffset); | ||
let offset = 1 + 2 * unit; | ||
const numStdFlags = await stream.readUInt16BE(ihdrOffset + offset); | ||
const flagsLength = numStdFlags * (2 + unit); | ||
offset += 2 + flagsLength; | ||
const numVendorFeatures = await stream.readUInt16BE(ihdrOffset + offset); | ||
const featuresLength = numVendorFeatures * (16 + unit); | ||
const rreqLength = offset + 2 + featuresLength; | ||
ihdrOffset += 4 + rreqLength; | ||
ihdrOffset += 8; | ||
} break; | ||
case BoxTypes.jp2h: { | ||
ihdrOffset += 8; | ||
} break; | ||
} | ||
return yield { | ||
height: await stream.readUint32BE(ihdrOffset + 4), | ||
width: await stream.readUint32BE(ihdrOffset + 8), | ||
}; | ||
} satisfies ExtractSize; | ||
|
||
enum BoxTypes { | ||
ftyp = 'ftyp', | ||
ihdr = 'ihdr', | ||
jp2h = 'jp2h', | ||
jp__ = 'jP ', | ||
rreq = 'rreq', | ||
xml_ = 'xml ', | ||
}; |
Oops, something went wrong.