image-hash 6.0.3 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -86
- package/lib/block-hash.d.ts +7 -1
- package/lib/block-hash.js +30 -59
- package/lib/block-hash.js.map +1 -1
- package/lib/index.d.ts +16 -5
- package/lib/index.js +69 -172
- package/lib/index.js.map +1 -1
- package/package.json +35 -22
package/README.md
CHANGED
|
@@ -7,117 +7,119 @@ Supports JPG, PNG and WebP
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm i
|
|
10
|
+
npm i image-hash
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Requires Node.js 22.12 or later.
|
|
14
|
+
|
|
13
15
|
## Use
|
|
14
16
|
|
|
17
|
+
`imageHash` returns a promise that resolves to the hash as a hex string.
|
|
18
|
+
|
|
15
19
|
```javascript
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// remote file simple
|
|
19
|
-
imageHash('https://ichef-1.bbci.co.uk/news/660/cpsprodpb/7F76/production/_95703623_mediaitem95703620.jpg', 16, true, (error, data) => {
|
|
20
|
-
if (error) throw error;
|
|
21
|
-
console.log(data);
|
|
22
|
-
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// remote file with fetch config object
|
|
26
|
-
const config = {
|
|
27
|
-
url: 'https://ichef-1.bbci.co.uk/news/660/cpsprodpb/7F76/production/_95703623_mediaitem95703620.jpg'
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
imageHash(config, 16, true, (error, data) => {
|
|
31
|
-
if (error) throw error;
|
|
32
|
-
console.log(data);
|
|
33
|
-
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
//local file
|
|
37
|
-
imageHash('./_95695590_tv039055678.jpg', 16, true, (error, data) => {
|
|
38
|
-
if (error) throw error;
|
|
39
|
-
console.log(data);
|
|
40
|
-
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
//Buffer
|
|
44
|
-
const fBuffer = fs.readFileSync(__dirname + '/example/_95695591_tv039055678.jpeg');
|
|
45
|
-
imageHash({
|
|
46
|
-
ext: 'image/jpeg',
|
|
47
|
-
data: fBuffer
|
|
48
|
-
}, 16, true, (error, data) => {
|
|
49
|
-
if(error) throw error;
|
|
50
|
-
console.log(data);
|
|
51
|
-
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
//Buffer, without ext arg
|
|
55
|
-
const fBuffer = fs.readFileSync(__dirname + '/example/_95695591_tv039055678.jpeg');
|
|
56
|
-
imageHash({
|
|
57
|
-
data: fBuffer
|
|
58
|
-
}, 16, true, (error, data) => {
|
|
59
|
-
if(error) throw error;
|
|
60
|
-
console.log(data);
|
|
61
|
-
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
62
|
-
});
|
|
63
|
-
```
|
|
20
|
+
import { readFile } from 'node:fs/promises';
|
|
21
|
+
import { imageHash } from 'image-hash';
|
|
64
22
|
|
|
65
|
-
|
|
23
|
+
// local file
|
|
24
|
+
const hash = await imageHash('./_95695590_tv039055678.jpg', 16, true);
|
|
25
|
+
console.log(hash);
|
|
26
|
+
// 0773063f063f36070e070a070f378e7f1f000fff0fff020103f00ffb0f810ff0
|
|
66
27
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
28
|
+
// bits and precise are optional, and default to 16 and true
|
|
29
|
+
await imageHash('./_95695590_tv039055678.jpg');
|
|
30
|
+
|
|
31
|
+
// remote file
|
|
32
|
+
await imageHash('https://ichef-1.bbci.co.uk/news/660/cpsprodpb/7F76/production/_95703623_mediaitem95703620.jpg', 16, true);
|
|
33
|
+
|
|
34
|
+
// remote file with fetch options
|
|
35
|
+
await imageHash({
|
|
36
|
+
url: 'https://ichef-1.bbci.co.uk/news/660/cpsprodpb/7F76/production/_95703623_mediaitem95703620.jpg',
|
|
37
|
+
headers: { authorization: 'Bearer token' },
|
|
38
|
+
signal: AbortSignal.timeout(10_000),
|
|
39
|
+
}, 16, true);
|
|
70
40
|
|
|
71
|
-
//
|
|
72
|
-
|
|
41
|
+
// Buffer
|
|
42
|
+
const data = await readFile('./_95695591_tv039055678.jpeg');
|
|
43
|
+
await imageHash(data, 16, true);
|
|
44
|
+
|
|
45
|
+
// Buffer, with mime type and file name
|
|
46
|
+
await imageHash({ ext: 'image/jpeg', data, name: 'photo.jpeg' }, 16, true);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Errors (file not found, unsupported image type, failed requests) reject the promise, so use `try`/`catch` or `.catch()`.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
try {
|
|
53
|
+
await imageHash('./missing.jpg');
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(error);
|
|
56
|
+
}
|
|
73
57
|
```
|
|
74
58
|
|
|
75
|
-
|
|
76
|
-
|
|
59
|
+
The package is ESM, but CommonJS projects can still `require` it:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const { imageHash } = require('image-hash');
|
|
77
63
|
|
|
64
|
+
imageHash('./_95695590_tv039055678.jpg').then((hash) => console.log(hash));
|
|
65
|
+
```
|
|
78
66
|
|
|
79
|
-
|
|
67
|
+
## API
|
|
80
68
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
|
86
|
-
|
|
|
69
|
+
```typescript
|
|
70
|
+
imageHash(src: string | Buffer | UrlRequestObject | BufferObject, bits?: number, precise?: boolean): Promise<string>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
| Argument | Type | Description | Default | Example |
|
|
74
|
+
| -------- | ---- | ----------- | ------- | ------- |
|
|
75
|
+
| src | `string`, `Buffer` or `object` | A file path, an `http(s)` URL, a `Buffer`, a request object with a remote `url`, or a buffer object (see below) | Required | `'./image.jpg'` |
|
|
76
|
+
| bits | `number` | The number of bits in a row. The more bits, the more unique the hash. Must be a positive integer. | `16` | `8` |
|
|
77
|
+
| precise | `boolean` | Whether a precision algorithm is used. `true` Precise but slower, non-overlapping blocks. `false` Quick and crude, non-overlapping blocks. Method 2 is recommended as a good tradeoff between speed and good matches on any image size. The quick ones are only advisable when the image width and height are an even multiple of the number of blocks used. | `true` | `false` |
|
|
87
78
|
|
|
88
|
-
|
|
79
|
+
### Source Object Types
|
|
89
80
|
|
|
90
81
|
```typescript
|
|
91
|
-
//
|
|
92
|
-
interface UrlRequestObject {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Buffer Object
|
|
82
|
+
// Request object. Any other fetch options (headers, signal, ...) are passed to fetch.
|
|
83
|
+
interface UrlRequestObject extends RequestInit {
|
|
84
|
+
url: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Buffer object
|
|
100
88
|
interface BufferObject {
|
|
101
|
-
ext?: string
|
|
102
|
-
data: Buffer
|
|
103
|
-
name?: string // file name
|
|
104
|
-
}
|
|
89
|
+
ext?: string; // mime type of the image, detected from the data if omitted
|
|
90
|
+
data: Buffer;
|
|
91
|
+
name?: string; // file name, used to check the extension matches the mime type
|
|
92
|
+
}
|
|
105
93
|
```
|
|
106
94
|
|
|
107
|
-
|
|
95
|
+
## Settings
|
|
108
96
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
Image hash will log out warnings if environment variable `VERBOSE` is set.
|
|
98
|
+
|
|
99
|
+
## Upgrading from v7
|
|
100
|
+
|
|
101
|
+
- The callback argument has been removed. `imageHash` now returns a promise.
|
|
102
|
+
- `bits` and `precise` are optional.
|
|
103
|
+
- A `Buffer` can be passed directly.
|
|
104
|
+
- The package is now ESM and requires Node.js 22.12 or later.
|
|
113
105
|
|
|
114
106
|
## Development
|
|
115
107
|
|
|
116
|
-
|
|
108
|
+
Built with TypeScript, ESLint and Vitest, using pnpm.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pnpm install
|
|
112
|
+
pnpm test
|
|
113
|
+
pnpm run lint
|
|
114
|
+
pnpm run typecheck
|
|
115
|
+
pnpm run build
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Publishing
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
Every push to `master` stages a new patch version on npm. The CI workflow bumps the patch version, stages it with `npm stage publish`, then pushes the version commit and tag back to `master`. The version goes live once a maintainer approves it on npm with 2FA. For a major or minor release, set `version` in `package.json` to the version before the one you want, e.g. `9.0.0` to publish `9.0.1`.
|
|
119
121
|
|
|
120
|
-
|
|
122
|
+
Staging uses [npm trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token is stored in GitHub. It is configured on npmjs.com under the package's Settings → Trusted Publisher, with the GitHub repository `danm/image-hash`, the workflow file `ci.yml`, and only staged publishing allowed.
|
|
121
123
|
|
|
122
124
|
## Credit
|
|
123
125
|
|
package/lib/block-hash.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
export interface ImageData {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
data: ArrayLike<number>;
|
|
5
|
+
}
|
|
6
|
+
export type HashMethod = 1 | 2;
|
|
7
|
+
declare const _default: (imgData: ImageData, bits: number, method: HashMethod) => string;
|
|
2
8
|
export default _default;
|
package/lib/block-hash.js
CHANGED
|
@@ -9,7 +9,7 @@ const translateBlocksToBits = (blocks, pixelsPerBlock) => {
|
|
|
9
9
|
const newblocks = blocks;
|
|
10
10
|
const halfBlockValue = (pixelsPerBlock * 256 * 3) / 2;
|
|
11
11
|
const bandsize = blocks.length / 4;
|
|
12
|
-
// Compare medians across four
|
|
12
|
+
// Compare medians across four horizontal bands
|
|
13
13
|
for (let i = 0; i < 4; i += 1) {
|
|
14
14
|
const m = median(blocks.slice(i * bandsize, (i + 1) * bandsize));
|
|
15
15
|
for (let j = i * bandsize; j < (i + 1) * bandsize; j += 1) {
|
|
@@ -32,6 +32,13 @@ const bitsToHexhash = (bitsArray) => {
|
|
|
32
32
|
}
|
|
33
33
|
return hex.join('');
|
|
34
34
|
};
|
|
35
|
+
const pixelValue = (data, index) => {
|
|
36
|
+
const alpha = data.data[index + 3];
|
|
37
|
+
if (alpha === 0) {
|
|
38
|
+
return 765;
|
|
39
|
+
}
|
|
40
|
+
return data.data[index] + data.data[index + 1] + data.data[index + 2];
|
|
41
|
+
};
|
|
35
42
|
const bmvbhashEven = (data, bits) => {
|
|
36
43
|
const blocksizeX = Math.floor(data.width / bits);
|
|
37
44
|
const blocksizeY = Math.floor(data.height / bits);
|
|
@@ -43,14 +50,7 @@ const bmvbhashEven = (data, bits) => {
|
|
|
43
50
|
for (let ix = 0; ix < blocksizeX; ix += 1) {
|
|
44
51
|
const cx = x * blocksizeX + ix;
|
|
45
52
|
const cy = y * blocksizeY + iy;
|
|
46
|
-
|
|
47
|
-
const alpha = data.data[ii + 3];
|
|
48
|
-
if (alpha === 0) {
|
|
49
|
-
total += 765;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
total += data.data[ii] + data.data[ii + 1] + data.data[ii + 2];
|
|
53
|
-
}
|
|
53
|
+
total += pixelValue(data, (cy * data.width + cx) * 4);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
result.push(total);
|
|
@@ -60,37 +60,20 @@ const bmvbhashEven = (data, bits) => {
|
|
|
60
60
|
return bitsToHexhash(result);
|
|
61
61
|
};
|
|
62
62
|
const bmvbhash = (data, bits) => {
|
|
63
|
-
const result = [];
|
|
64
|
-
let weightTop;
|
|
65
|
-
let weightBottom;
|
|
66
|
-
let weightLeft;
|
|
67
|
-
let weightRight;
|
|
68
|
-
let blockTop;
|
|
69
|
-
let blockBottom;
|
|
70
|
-
let blockLeft;
|
|
71
|
-
let blockRight;
|
|
72
|
-
let yMod;
|
|
73
|
-
let yFrac;
|
|
74
|
-
let yInt;
|
|
75
|
-
let xMod;
|
|
76
|
-
let xFrac;
|
|
77
|
-
let xInt;
|
|
78
|
-
const blocks = [];
|
|
79
63
|
const evenX = data.width % bits === 0;
|
|
80
64
|
const evenY = data.height % bits === 0;
|
|
81
65
|
if (evenX && evenY) {
|
|
82
66
|
return bmvbhashEven(data, bits);
|
|
83
67
|
}
|
|
84
68
|
// initialize blocks array with 0s
|
|
85
|
-
|
|
86
|
-
blocks.push([]);
|
|
87
|
-
for (let j = 0; j < bits; j += 1) {
|
|
88
|
-
blocks[i].push(0);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
69
|
+
const blocks = Array.from({ length: bits }, () => new Array(bits).fill(0));
|
|
91
70
|
const blockWidth = data.width / bits;
|
|
92
71
|
const blockHeight = data.height / bits;
|
|
93
72
|
for (let y = 0; y < data.height; y += 1) {
|
|
73
|
+
let weightTop;
|
|
74
|
+
let weightBottom;
|
|
75
|
+
let blockTop;
|
|
76
|
+
let blockBottom;
|
|
94
77
|
if (evenY) {
|
|
95
78
|
// don't bother dividing y, if the size evenly divides by bits
|
|
96
79
|
blockBottom = Math.floor(y / blockHeight);
|
|
@@ -99,9 +82,9 @@ const bmvbhash = (data, bits) => {
|
|
|
99
82
|
weightBottom = 0;
|
|
100
83
|
}
|
|
101
84
|
else {
|
|
102
|
-
yMod = (y + 1) % blockHeight;
|
|
103
|
-
yFrac = yMod - Math.floor(yMod);
|
|
104
|
-
yInt = yMod - yFrac;
|
|
85
|
+
const yMod = (y + 1) % blockHeight;
|
|
86
|
+
const yFrac = yMod - Math.floor(yMod);
|
|
87
|
+
const yInt = yMod - yFrac;
|
|
105
88
|
weightTop = 1 - yFrac;
|
|
106
89
|
weightBottom = yFrac;
|
|
107
90
|
// y_int will be 0 on bottom/right borders and on block boundaries
|
|
@@ -115,15 +98,11 @@ const bmvbhash = (data, bits) => {
|
|
|
115
98
|
}
|
|
116
99
|
}
|
|
117
100
|
for (let x = 0; x < data.width; x += 1) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
avgvalue = data.data[ii] + data.data[ii + 1] + data.data[ii + 2];
|
|
126
|
-
}
|
|
101
|
+
const avgvalue = pixelValue(data, (y * data.width + x) * 4);
|
|
102
|
+
let weightLeft;
|
|
103
|
+
let weightRight;
|
|
104
|
+
let blockLeft;
|
|
105
|
+
let blockRight;
|
|
127
106
|
if (evenX) {
|
|
128
107
|
blockRight = Math.floor(x / blockWidth);
|
|
129
108
|
blockLeft = blockRight;
|
|
@@ -131,9 +110,9 @@ const bmvbhash = (data, bits) => {
|
|
|
131
110
|
weightRight = 0;
|
|
132
111
|
}
|
|
133
112
|
else {
|
|
134
|
-
xMod = (x + 1) % blockWidth;
|
|
135
|
-
xFrac = xMod - Math.floor(xMod);
|
|
136
|
-
xInt = xMod - xFrac;
|
|
113
|
+
const xMod = (x + 1) % blockWidth;
|
|
114
|
+
const xFrac = xMod - Math.floor(xMod);
|
|
115
|
+
const xInt = xMod - xFrac;
|
|
137
116
|
weightLeft = 1 - xFrac;
|
|
138
117
|
weightRight = xFrac;
|
|
139
118
|
// x_int will be 0 on bottom/right borders and on block boundaries
|
|
@@ -153,25 +132,17 @@ const bmvbhash = (data, bits) => {
|
|
|
153
132
|
blocks[blockBottom][blockRight] += avgvalue * weightBottom * weightRight;
|
|
154
133
|
}
|
|
155
134
|
}
|
|
156
|
-
|
|
157
|
-
for (let j = 0; j < bits; j += 1) {
|
|
158
|
-
result.push(blocks[i][j]);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
135
|
+
const result = blocks.flat();
|
|
161
136
|
translateBlocksToBits(result, blockWidth * blockHeight);
|
|
162
137
|
return bitsToHexhash(result);
|
|
163
138
|
};
|
|
164
139
|
export default (imgData, bits, method) => {
|
|
165
|
-
let hash;
|
|
166
140
|
if (method === 1) {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
else if (method === 2) {
|
|
170
|
-
hash = bmvbhash(imgData, bits);
|
|
141
|
+
return bmvbhashEven(imgData, bits);
|
|
171
142
|
}
|
|
172
|
-
|
|
173
|
-
|
|
143
|
+
if (method === 2) {
|
|
144
|
+
return bmvbhash(imgData, bits);
|
|
174
145
|
}
|
|
175
|
-
|
|
146
|
+
throw new Error('Bad hashing method');
|
|
176
147
|
};
|
|
177
148
|
//# sourceMappingURL=block-hash.js.map
|
package/lib/block-hash.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block-hash.js","sourceRoot":"","sources":["../src/block-hash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"block-hash.js","sourceRoot":"","sources":["../src/block-hash.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
import { Buffer } from 'buffer';
|
|
2
|
-
export interface UrlRequestObject {
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
export interface UrlRequestObject extends RequestInit {
|
|
3
|
+
url: string;
|
|
4
|
+
/** @deprecated Ignored. Kept for backwards compatibility. */
|
|
3
5
|
encoding?: string | null;
|
|
4
|
-
url: string | null;
|
|
5
|
-
[key: string]: unknown;
|
|
6
6
|
}
|
|
7
7
|
export interface BufferObject {
|
|
8
|
+
/** Mime type of the image, e.g. `image/jpeg`. Detected from the data if omitted. */
|
|
8
9
|
ext?: string;
|
|
9
10
|
data: Buffer;
|
|
11
|
+
/** File name, used to check the file extension matches the mime type. */
|
|
10
12
|
name?: string;
|
|
11
13
|
}
|
|
12
|
-
export
|
|
14
|
+
export type ImageSource = string | Buffer | UrlRequestObject | BufferObject;
|
|
15
|
+
/**
|
|
16
|
+
* Create a perceptual hash of an image.
|
|
17
|
+
*
|
|
18
|
+
* @param src A file path, an http(s) URL, a Buffer, a `BufferObject` or a `UrlRequestObject`.
|
|
19
|
+
* @param bits The number of bits in a row. The more bits, the more unique the hash.
|
|
20
|
+
* @param precise `true` for the precise but slower algorithm, `false` for the quick one.
|
|
21
|
+
* @returns The hash as a hex string.
|
|
22
|
+
*/
|
|
23
|
+
export declare const imageHash: (src: ImageSource, bits?: number, precise?: boolean) => Promise<string>;
|
package/lib/index.js
CHANGED
|
@@ -1,188 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import { Buffer } from 'buffer';
|
|
12
|
-
import { fileTypeFromFile, fileTypeFromBuffer } from 'file-type';
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import webp from '@cwasm/webp';
|
|
5
|
+
import { fileTypeFromBuffer } from 'file-type';
|
|
13
6
|
import jpeg from 'jpeg-js';
|
|
14
7
|
import { PNG } from 'pngjs';
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
8
|
+
import blockhash from './block-hash.js';
|
|
9
|
+
const decoders = new Map([
|
|
10
|
+
['image/png', { extensions: ['png'], decode: (data) => PNG.sync.read(data) }],
|
|
11
|
+
['image/jpeg', { extensions: ['jpg', 'jpeg'], decode: (data) => jpeg.decode(data) }],
|
|
12
|
+
['image/webp', { extensions: ['webp'], decode: (data) => webp.decode(data) }],
|
|
13
|
+
]);
|
|
14
|
+
const isRemoteUrl = (src) => /^https?:\/\//i.test(src);
|
|
15
|
+
const isBufferObject = (src) => ('data' in src && Buffer.isBuffer(src.data));
|
|
16
|
+
const isUrlRequestObject = (src) => ('url' in src && typeof src.url === 'string' && src.url.length > 0);
|
|
17
|
+
const fetchImage = async (src) => {
|
|
18
|
+
// `encoding` is a leftover from the `request` library and is not a valid fetch option
|
|
19
|
+
const { url, encoding, ...init } = typeof src === 'string' ? { url: src } : src;
|
|
20
|
+
const response = await fetch(url, init);
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
await response.body?.cancel();
|
|
23
|
+
throw new Error(`Failed to fetch image. HTTP status: ${response.status} ${response.statusText}`);
|
|
26
24
|
}
|
|
25
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
26
|
+
return { data, name: new URL(response.url || url).pathname };
|
|
27
27
|
};
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const loadImage = async (src) => {
|
|
29
|
+
if (typeof src === 'string') {
|
|
30
|
+
if (isRemoteUrl(src)) {
|
|
31
|
+
return fetchImage(src);
|
|
32
|
+
}
|
|
33
|
+
return { data: await readFile(src), name: src };
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (Buffer.isBuffer(src)) {
|
|
36
|
+
return { data: src };
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const decoded = webp.decode(data);
|
|
41
|
-
const res = blockhash(decoded, bits, method ? 2 : 1);
|
|
42
|
-
cb(null, res);
|
|
38
|
+
if (isBufferObject(src)) {
|
|
39
|
+
return { data: src.data, name: src.name, mime: src.ext || undefined };
|
|
43
40
|
}
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
if (isUrlRequestObject(src)) {
|
|
42
|
+
return fetchImage(src);
|
|
46
43
|
}
|
|
44
|
+
throw new TypeError('Unrecognized image source');
|
|
47
45
|
};
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const isBufferObject = (obj) => {
|
|
53
|
-
const casted = obj;
|
|
54
|
-
return Buffer.isBuffer(casted.data)
|
|
55
|
-
|| (Buffer.isBuffer(casted.data) && (casted.ext && casted.ext.length > 0));
|
|
56
|
-
};
|
|
57
|
-
// eslint-disable-next-line
|
|
58
|
-
export const imageHash = (oldSrc, bits, method, cb) => {
|
|
59
|
-
const src = oldSrc;
|
|
60
|
-
const getFileType = (data) => __awaiter(void 0, void 0, void 0, function* () {
|
|
61
|
-
if (typeof src !== 'string' && isBufferObject(src) && src.ext) {
|
|
62
|
-
return {
|
|
63
|
-
mime: src.ext,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
if (Buffer.isBuffer(data)) {
|
|
67
|
-
return fileTypeFromBuffer(data);
|
|
68
|
-
}
|
|
69
|
-
if (typeof src === 'string') {
|
|
70
|
-
return fileTypeFromFile(src);
|
|
71
|
-
}
|
|
72
|
-
return '';
|
|
73
|
-
});
|
|
74
|
-
const checkFileType = (name, data) => {
|
|
75
|
-
getFileType(data).then((type) => {
|
|
76
|
-
// what is the image type
|
|
77
|
-
if (!type) {
|
|
78
|
-
cb(new Error('Mime type not found'));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (name && name.lastIndexOf('.') > 0) {
|
|
82
|
-
const ext = name
|
|
83
|
-
.split('.')
|
|
84
|
-
.pop()
|
|
85
|
-
.toLowerCase();
|
|
86
|
-
if (ext === 'png' && type.mime === 'image/png') {
|
|
87
|
-
processPNG(data, bits, method, cb);
|
|
88
|
-
}
|
|
89
|
-
else if ((ext === 'jpg' || ext === 'jpeg') && type.mime === 'image/jpeg') {
|
|
90
|
-
processJPG(data, bits, method, cb);
|
|
91
|
-
}
|
|
92
|
-
else if (ext === 'webp' && type.mime === 'image/webp') {
|
|
93
|
-
processWebp(data, bits, method, cb);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
cb(new Error(`Unrecognized file extension, mime type or mismatch, ext: ${ext} / mime: ${type.mime}`));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
if (process.env.verbose)
|
|
101
|
-
console.warn('No file extension found, attempting mime typing.');
|
|
102
|
-
if (type.mime === 'image/png') {
|
|
103
|
-
processPNG(data, bits, method, cb);
|
|
104
|
-
}
|
|
105
|
-
else if (type.mime === 'image/jpeg') {
|
|
106
|
-
processJPG(data, bits, method, cb);
|
|
107
|
-
}
|
|
108
|
-
else if (type.mime === 'image/webp') {
|
|
109
|
-
processWebp(data, bits, method, cb);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
cb(new Error(`Unrecognized mime type: ${type.mime}`));
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}).catch((err) => {
|
|
116
|
-
cb(err);
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
const fetchRemoteImage = (remoteSrc) => __awaiter(void 0, void 0, void 0, function* () {
|
|
120
|
-
if (fetch && typeof fetch !== 'function') {
|
|
121
|
-
cb(new Error('Global fetch API is not available. Node.js 18+ is required.'));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const requestUrl = typeof remoteSrc === 'string' ? remoteSrc : remoteSrc.url;
|
|
125
|
-
if (!requestUrl) {
|
|
126
|
-
cb(new Error('No URL provided for remote image.'));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
let init;
|
|
130
|
-
if (typeof remoteSrc !== 'string') {
|
|
131
|
-
init = Object.assign({}, remoteSrc);
|
|
132
|
-
delete init.url;
|
|
133
|
-
delete init.encoding;
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
const response = yield fetch(requestUrl, init);
|
|
137
|
-
if (!response || !response.ok) {
|
|
138
|
-
const status = response ? `${response.status} ${response.statusText}` : 'Unknown status';
|
|
139
|
-
throw new Error(`Failed to fetch image. HTTP status: ${status}`);
|
|
140
|
-
}
|
|
141
|
-
const arrayBuffer = yield response.arrayBuffer();
|
|
142
|
-
const buffer = Buffer.from(arrayBuffer);
|
|
143
|
-
let pathname = '';
|
|
144
|
-
try {
|
|
145
|
-
const url = new URL(response.url || requestUrl);
|
|
146
|
-
pathname = url.pathname;
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
pathname = '';
|
|
150
|
-
}
|
|
151
|
-
checkFileType(pathname, buffer);
|
|
152
|
-
}
|
|
153
|
-
catch (error) {
|
|
154
|
-
cb(error);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
const handleReadFile = (err, res) => {
|
|
158
|
-
if (err) {
|
|
159
|
-
cb(new Error(err));
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
checkFileType(src, res);
|
|
163
|
-
};
|
|
164
|
-
// check source
|
|
165
|
-
// is source assigned
|
|
166
|
-
if (src === undefined) {
|
|
167
|
-
cb(new Error('No image source provided'));
|
|
168
|
-
return;
|
|
46
|
+
const getDecoder = async ({ data, name, mime }) => {
|
|
47
|
+
const type = mime ?? (await fileTypeFromBuffer(data))?.mime;
|
|
48
|
+
if (!type) {
|
|
49
|
+
throw new Error('Mime type not found');
|
|
169
50
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
51
|
+
const decoder = decoders.get(type);
|
|
52
|
+
const ext = name ? path.extname(name).slice(1).toLowerCase() : '';
|
|
53
|
+
if (ext) {
|
|
54
|
+
if (!decoder?.extensions.includes(ext)) {
|
|
55
|
+
throw new Error(`Unrecognized file extension, mime type or mismatch, ext: ${ext} / mime: ${type}`);
|
|
56
|
+
}
|
|
57
|
+
return decoder;
|
|
174
58
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
59
|
+
if (process.env.VERBOSE)
|
|
60
|
+
console.warn('No file extension found, attempting mime typing.');
|
|
61
|
+
if (!decoder) {
|
|
62
|
+
throw new Error(`Unrecognized mime type: ${type}`);
|
|
178
63
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
64
|
+
return decoder;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create a perceptual hash of an image.
|
|
68
|
+
*
|
|
69
|
+
* @param src A file path, an http(s) URL, a Buffer, a `BufferObject` or a `UrlRequestObject`.
|
|
70
|
+
* @param bits The number of bits in a row. The more bits, the more unique the hash.
|
|
71
|
+
* @param precise `true` for the precise but slower algorithm, `false` for the quick one.
|
|
72
|
+
* @returns The hash as a hex string.
|
|
73
|
+
*/
|
|
74
|
+
export const imageHash = async (src, bits = 16, precise = true) => {
|
|
75
|
+
if (src == null) {
|
|
76
|
+
throw new TypeError('No image source provided');
|
|
182
77
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
fs.readFile(src, handleReadFile);
|
|
78
|
+
if (!Number.isInteger(bits) || bits <= 0) {
|
|
79
|
+
throw new TypeError(`bits must be a positive integer, received: ${String(bits)}`);
|
|
186
80
|
}
|
|
81
|
+
const image = await loadImage(src);
|
|
82
|
+
const decoder = await getDecoder(image);
|
|
83
|
+
return blockhash(decoder.decode(image.data), bits, precise ? 2 : 1);
|
|
187
84
|
};
|
|
188
85
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "image-hash",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "Create a hash from an image",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./lib/index.d.ts",
|
|
9
|
+
"default": "./lib/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"main": "./lib/index.js",
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
7
14
|
"files": [
|
|
8
15
|
"lib"
|
|
9
16
|
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.12.0"
|
|
19
|
+
},
|
|
20
|
+
"packageManager": "pnpm@10.34.9",
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"lint:fix": "eslint . --fix"
|
|
28
|
+
},
|
|
10
29
|
"repository": {
|
|
11
30
|
"type": "git",
|
|
12
31
|
"url": "git+https://github.com/danm/image-hash.git"
|
|
@@ -25,26 +44,20 @@
|
|
|
25
44
|
"homepage": "https://github.com/danm/image-hash#readme",
|
|
26
45
|
"dependencies": {
|
|
27
46
|
"@cwasm/webp": "^0.1.5",
|
|
28
|
-
"file-type": "^
|
|
29
|
-
"jpeg-js": "^0.4.
|
|
47
|
+
"file-type": "^22.2.0",
|
|
48
|
+
"jpeg-js": "^0.4.4",
|
|
30
49
|
"pngjs": "^7.0.0"
|
|
31
50
|
},
|
|
32
51
|
"devDependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@
|
|
35
|
-
"
|
|
36
|
-
"@types/pngjs": "^6.0.
|
|
37
|
-
"
|
|
38
|
-
"eslint
|
|
39
|
-
"
|
|
40
|
-
"typescript": "
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
"scripts": {
|
|
44
|
-
"test": "vitest run",
|
|
45
|
-
"test:watch": "vitest",
|
|
46
|
-
"build": "tsc",
|
|
47
|
-
"lint": "eslint src/*.ts",
|
|
48
|
-
"lint:fix": "eslint src/*.ts --fix"
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
54
|
+
"@types/node": "^22.20.5",
|
|
55
|
+
"@types/pngjs": "^6.0.5",
|
|
56
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
57
|
+
"eslint": "^10.13.0",
|
|
58
|
+
"globals": "^17.13.0",
|
|
59
|
+
"typescript": "~6.0.3",
|
|
60
|
+
"typescript-eslint": "^8.72.0",
|
|
61
|
+
"vitest": "^5.0.5"
|
|
49
62
|
}
|
|
50
|
-
}
|
|
63
|
+
}
|