Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.0 #370

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

2.0 #370

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist/
docs/
.yarn/*.gz
.yarn/cache
*.tgz
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"semi": false
"semi": false,
"singleQuote": true
}
90 changes: 28 additions & 62 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ A [Node](https://nodejs.org/en/) module to get dimensions of any image file
- TIFF
- WebP

## Programmatic Usage
## Installation

```shell
npm install image-size --save
Expand All @@ -39,68 +39,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("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("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/fromFile')
const { images } = await imageSize('images/multi-size.ico')
for (const dimensions of images) {
console.log(dimensions.width, dimensions.height)
}
Expand All @@ -109,23 +82,23 @@ for (const dimensions of images) {
### Using a URL

```javascript
const url = require("url")
const http = require("http")
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 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) {
.on('data', function (chunk) {
chunks.push(chunk)
})
.on("end", function () {
.on('end', function () {
const buffer = Buffer.concat(chunks)
console.log(sizeOf(buffer))
console.log(imageSize(buffer))
})
})
```
Expand All @@ -136,25 +109,18 @@ You can optionally check the buffer lengths & stop downloading the image after a
### 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 Down
22 changes: 12 additions & 10 deletions bin/image-size.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node
'use strict'

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

const files = process.argv.slice(2)

Expand All @@ -13,36 +13,38 @@ 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 => {
sizes.forEach((size) => {
let greyType = ''
if (size.type) {
greyType = colorize(' (' + size.type + ')', grey)
}
console.info(
colorize(size.width, green) + greyX + colorize(size.height, green)
+ ' - ' + greyImage + greyType
colorize(size.width, green) +
greyX +
colorize(size.height, green) +
' - ' +
greyImage +
greyType
)
})
} else {
console.error('file doesn\'t exist - ', image)
}
} catch (e) {
// console.error(e.stack)
console.error(colorize(e.message, red), '-', image)
}
})
40 changes: 17 additions & 23 deletions lib/detector.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import type { imageType } from './types/index'
import { typeHandlers } from './types/index'

const keys = Object.keys(typeHandlers) as imageType[]
import { typeHandlers, types } from './types/index'

// This map helps avoid validating for every single image type
const firstBytes: { [byte: number]: imageType } = {
0x38: 'psd',
0x42: 'bmp',
0x44: 'dds',
0x47: 'gif',
0x49: 'tiff',
0x4d: 'tiff',
0x52: 'webp',
0x69: 'icns',
0x89: 'png',
0xff: 'jpg',
}
const firstBytes = new Map<number, imageType>([
[0x38, 'psd'],
[0x42, 'bmp'],
[0x44, 'dds'],
[0x47, 'gif'],
[0x49, 'tiff'],
[0x4d, 'tiff'],
[0x52, 'webp'],
[0x69, 'icns'],
[0x89, 'png'],
[0xff, 'jpg'],
])

export function detector(input: Uint8Array): imageType | undefined {
const byte = input[0]
if (byte in firstBytes) {
const type = firstBytes[byte]
if (type && typeHandlers[type].validate(input)) {
return type
}
const type = firstBytes.get(byte)
if (type && typeHandlers.get(type)!.validate(input)) {
return type
}

const finder = (key: imageType) => typeHandlers[key].validate(input)
return keys.find(finder)
return types.find((type) => typeHandlers.get(type)!.validate(input))
}
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()
})