Skip to content

Commit

Permalink
only support promises API, and remove the queue dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
netroy committed Nov 13, 2023
1 parent 8cd7d16 commit 5bc2278
Show file tree
Hide file tree
Showing 17 changed files with 212 additions and 370 deletions.
126 changes: 48 additions & 78 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@ A [Node](https://nodejs.org/en/) module to get dimensions of any image file

## Supported formats

* BMP
* CUR
* DDS
* GIF
* ICNS
* ICO
* J2C
* JP2
* JPEG
* KTX
* PNG
* PNM (PAM, PBM, PFM, PGM, PPM)
* PSD
* SVG
* TGA
* TIFF
* WebP

## Programmatic Usage
- BMP
- CUR
- DDS
- GIF
- ICNS
- ICO
- J2C
- JP2
- JPEG
- KTX
- PNG
- PNM (PAM, PBM, PFM, PGM, PPM)
- PSD
- SVG
- TGA
- TIFF
- WebP

## Installation

```shell
npm install image-size --save
Expand All @@ -38,67 +38,41 @@ or
yarn add image-size
```

### Synchronous
## Programmatic Usage

### Passing in a Buffer/Uint8Array

```javascript
const sizeOf = require('image-size')
const dimensions = sizeOf('images/funny-cats.png')
console.log(dimensions.width, dimensions.height)
const { imageSize } = require('image-size')
const { width, height } = imageSize(bufferObject)
console.log(width, height)
```

### Asynchronous
### Reading from a file

```javascript
const sizeOf = require('image-size')
sizeOf('images/funny-cats.png', function (err, dimensions) {
console.log(dimensions.width, dimensions.height)
})
const { imageSize } = require('image-size/fromFile')
const dimensions = await imageSize('images/funny-cats.png')
console.log(dimensions.width, dimensions.height)
```

NOTE: The asynchronous version doesn't work if the input is a Buffer. Use synchronous version instead.

Also, the asynchronous functions have a default concurrency limit of **100**
NOTE: Reading from files haa a default concurrency limit of **100**
To change this limit, you can call the `setConcurrency` function like this:

```javascript
const sizeOf = require('image-size')
const sizeOf = require('image-size/fromFile')
sizeOf.setConcurrency(123456)
```

### Using promises (nodejs 10.x+)

```javascript
const { promisify } = require('node:util')
const sizeOf = promisify(require('image-size'))
sizeOf('images/funny-cats.png')
.then(dimensions => { console.log(dimensions.width, dimensions.height) })
.catch(err => console.error(err))
```

### Async/Await (Typescript & ES7)

```javascript
const { promisify } = require('node:util')
const sizeOf = promisify(require('image-size'))
(async () => {
try {
const dimensions = await sizeOf('images/funny-cats.png')
console.log(dimensions.width, dimensions.height)
} catch (err) {
console.error(err)
}
})().then(c => console.log(c))
```

### Multi-size

If the target file is an icon (.ico) or a cursor (.cur), the `width` and `height` will be the ones of the first found image.
If the target file/buffer is an icon (.ico) or a cursor (.cur), the `width` and `height` will be the ones of the first found image.

An additional `images` array is available and returns the dimensions of all the available images

```javascript
const sizeOf = require('image-size')
const images = sizeOf('images/multi-size.ico').images
const { imageSize } = require('image-size')
const { images } = imageSize('images/multi-size.ico')
for (const dimensions of images) {
console.log(dimensions.width, dimensions.height)
}
Expand All @@ -110,45 +84,42 @@ for (const dimensions of images) {
const url = require('node:url')
const http = require('node:http')

const sizeOf = require('image-size')
const { imageSize } = require('image-size')

const imgUrl = 'http://my-amazing-website.com/image.jpeg'
const options = url.parse(imgUrl)

http.get(options, function (response) {
const chunks = []
response.on('data', function (chunk) {
chunks.push(chunk)
}).on('end', function() {
const buffer = Buffer.concat(chunks)
console.log(sizeOf(buffer))
})
response
.on('data', function (chunk) {
chunks.push(chunk)
})
.on('end', function () {
const buffer = Buffer.concat(chunks)
console.log(imageSize(buffer))
})
})
```

You can optionally check the buffer lengths & stop downloading the image after a few kilobytes.
**You don't need to download the entire image**

### Disabling certain image types
```javascript
const imageSize = require('image-size')
imageSize.disableTypes(['tiff', 'ico'])
```

### Disabling all file-system reads
```javascript
const imageSize = require('image-size')
imageSize.disableFS(true)
const { disableTypes } = require('image-size')
disableTypes(['tiff', 'ico'])
```

### JPEG image orientation

If the orientation is present in the JPEG EXIF metadata, it will be returned by the function. The orientation value is a [number between 1 and 8](https://exiftool.org/TagNames/EXIF.html#:~:text=0x0112,8%20=%20Rotate%20270%20CW) representing a type of orientation.

```javascript
const sizeOf = require('image-size')
const dimensions = sizeOf('images/photo.jpeg')
console.log(dimensions.orientation)
const { imageSize } = require('image-size/fromFile')
const { width, height, orientation } = imageSize('images/photo.jpeg')
console.log(width, height, orientation)
```

## Command-Line Usage (CLI)
Expand All @@ -169,7 +140,6 @@ followed by
image-size image1 [image2] [image3] ...
```


## Credits

not a direct port, but an attempt to have something like
Expand Down
8 changes: 3 additions & 5 deletions bin/image-size.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

const fs = require('node:fs')
const path = require('node:path')
const { imageSize } = require('../dist/cjs/node')
const { imageSize } = require('../dist/cjs/fromFile')

const files = process.argv.slice(2)

Expand All @@ -13,20 +13,19 @@ if (!files.length) {
}

const red = ['\x1B[31m', '\x1B[39m']
// const bold = ['\x1B[1m', '\x1B[22m']
const grey = ['\x1B[90m', '\x1B[39m']
const green = ['\x1B[32m', '\x1B[39m']

function colorize(text, color) {
return color[0] + text + color[1]
}

files.forEach(function (image) {
files.forEach(async (image) => {
try {
if (fs.existsSync(path.resolve(image))) {
const greyX = colorize('x', grey)
const greyImage = colorize(image, grey)
const size = imageSize(image)
const size = await imageSize(image)
const sizes = size.images || [size]
sizes.forEach((size) => {
let greyType = ''
Expand All @@ -46,7 +45,6 @@ files.forEach(function (image) {
console.error('file doesn\'t exist - ', image)
}
} catch (e) {
// console.error(e.stack)
console.error(colorize(e.message, red), '-', image)
}
})
62 changes: 62 additions & 0 deletions lib/fromFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as fs from 'node:fs'
import * as path from 'node:path'

import { lookup } from './lookup'
import type { ISizeCalculationResult } from './types/interface'

// Maximum input size, with a default of 512 kilobytes.
// TO-DO: make this adaptive based on the initial signature of the image
const MaxInputSize = 512 * 1024

type Job = {
filePath: string
resolve: (value: ISizeCalculationResult) => void
reject: (error: Error) => void
}

// This queue is for async `fs` operations, to avoid reaching file-descriptor limits
const queue: Job[] = []

let concurrency = 100
export const setConcurrency = (c: number): void => {
concurrency = c
}

const processQueue = async () => {
const jobs = queue.splice(0, concurrency)
const promises = jobs.map(async ({ filePath, resolve, reject }) => {
let handle: fs.promises.FileHandle
try {
handle = await fs.promises.open(path.resolve(filePath), 'r')
} catch (err) {
return reject(err as Error)
}
try {
const { size } = await handle.stat()
if (size <= 0) {
throw new Error('Empty file')
}
const inputSize = Math.min(size, MaxInputSize)
const input = new Uint8Array(inputSize)
await handle.read(input, 0, inputSize, 0)
resolve(lookup(input))
} catch (err) {
reject(err as Error)
} finally {
await handle.close()
}
})

await Promise.allSettled(promises)

if (queue.length) setTimeout(processQueue, 100)
}

/**
* @param {string} filePath - relative/absolute path of the image file
*/
export const imageSize = async (filePath: string) =>
new Promise<ISizeCalculationResult>((resolve, reject) => {
queue.push({ filePath, resolve, reject })
processQueue()
})
14 changes: 1 addition & 13 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
import type { ISizeCalculationResult } from './types/interface'
import { lookup } from './lookup'

export default imageSize
export function imageSize(input: Uint8Array): ISizeCalculationResult

/**
* @param {Uint8Array} input - Uint8Array of the image file
*/
export function imageSize(input: Uint8Array): ISizeCalculationResult {
// Handle Uint8Array input
return lookup(input)
}
export { lookup as imageSize, disableTypes, types } from './lookup'

0 comments on commit 5bc2278

Please sign in to comment.