@thi.ng/pixel-io-netpbm 2.1.89 → 2.1.91

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/package.json +9 -6
  3. package/read.js +107 -189
  4. package/write.js +86 -105
package/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-09T19:12:03Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/pixel-io-netpbm",
3
- "version": "2.1.89",
3
+ "version": "2.1.91",
4
4
  "description": "Multi-format NetPBM reader & writer support for @thi.ng/pixel",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,12 +35,13 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.11",
37
- "@thi.ng/errors": "^2.4.5",
38
- "@thi.ng/pixel": "^5.0.3"
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/errors": "^2.4.6",
40
+ "@thi.ng/pixel": "^5.0.5"
39
41
  },
40
42
  "devDependencies": {
41
43
  "@microsoft/api-extractor": "^7.38.3",
44
+ "esbuild": "^0.19.8",
42
45
  "rimraf": "^5.0.5",
43
46
  "tools": "^0.0.1",
44
47
  "typedoc": "^0.25.4",
@@ -84,5 +87,5 @@
84
87
  "parent": "@thi.ng/pixel",
85
88
  "year": 2021
86
89
  },
87
- "gitHead": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
90
+ "gitHead": "22e36fa838e5431d40165384918b395603bbd92f\n"
88
91
  }
package/read.js CHANGED
@@ -4,207 +4,125 @@ import { GRAY16 } from "@thi.ng/pixel/format/gray16";
4
4
  import { GRAY8 } from "@thi.ng/pixel/format/gray8";
5
5
  import { RGB888 } from "@thi.ng/pixel/format/rgb888";
6
6
  import { intBuffer } from "@thi.ng/pixel/int";
7
- const isLinebreak = (c) => c === 0xa;
8
- const isWS = (c) => c === 0x20 || (c >= 9 && c <= 13);
7
+ const isLinebreak = (c) => c === 10;
8
+ const isWS = (c) => c === 32 || c >= 9 && c <= 13;
9
9
  const readUntil = (src, i, end = isLinebreak) => {
10
- let res = "";
11
- for (; i < src.length; i++) {
12
- let c = src[i];
13
- if (end(c)) {
14
- i++;
15
- break;
16
- }
17
- res += String.fromCharCode(c);
10
+ let res = "";
11
+ for (; i < src.length; i++) {
12
+ let c = src[i];
13
+ if (end(c)) {
14
+ i++;
15
+ break;
18
16
  }
19
- return [res, i];
17
+ res += String.fromCharCode(c);
18
+ }
19
+ return [res, i];
20
20
  };
21
21
  const readComments = (src, acc, i) => {
22
- while (src[i] === 0x23) {
23
- // @ts-ignore
24
- const [comment, j] = readUntil(src, i);
25
- assert(j !== i, `EOF reached`);
26
- acc.push(comment.substring(1).trim());
27
- i = j;
28
- }
29
- return i;
22
+ while (src[i] === 35) {
23
+ const [comment, j] = readUntil(src, i);
24
+ assert(j !== i, `EOF reached`);
25
+ acc.push(comment.substring(1).trim());
26
+ i = j;
27
+ }
28
+ return i;
30
29
  };
31
- /**
32
- * Parses header information from given NetPBM file byte buffer.
33
- *
34
- * @param src -
35
- */
36
- export const parseHeader = (src) => {
37
- let type;
38
- let sw, sh;
39
- let norm;
40
- let max;
41
- const comments = [];
42
- let i = readComments(src, comments, 0);
43
- [type, i] = readUntil(src, i);
44
- i = readComments(src, comments, i);
45
- [sw, i] = readUntil(src, i, isWS);
46
- [sh, i] = readUntil(src, i, isWS);
47
- const width = parseInt(sw);
48
- const height = parseInt(sh);
49
- assert(width > 0 && height > 0, `invalid NetPBM header`);
50
- if (type === "P5" || type === "P6") {
51
- [norm, i] = readUntil(src, i);
52
- max = parseInt(norm);
53
- }
54
- return {
55
- type,
56
- width,
57
- height,
58
- max,
59
- start: i,
60
- comments,
61
- };
30
+ const parseHeader = (src) => {
31
+ let type;
32
+ let sw, sh;
33
+ let norm;
34
+ let max;
35
+ const comments = [];
36
+ let i = readComments(src, comments, 0);
37
+ [type, i] = readUntil(src, i);
38
+ i = readComments(src, comments, i);
39
+ [sw, i] = readUntil(src, i, isWS);
40
+ [sh, i] = readUntil(src, i, isWS);
41
+ const width = parseInt(sw);
42
+ const height = parseInt(sh);
43
+ assert(width > 0 && height > 0, `invalid NetPBM header`);
44
+ if (type === "P5" || type === "P6") {
45
+ [norm, i] = readUntil(src, i);
46
+ max = parseInt(norm);
47
+ }
48
+ return {
49
+ type,
50
+ width,
51
+ height,
52
+ max,
53
+ start: i,
54
+ comments
55
+ };
62
56
  };
63
- /**
64
- * Takes a PBM/PGM/PPM file as byte array and parses it into a
65
- * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html) of
66
- * corresponding format.
67
- *
68
- * @remarks
69
- * Depending on header information, the following rules apply:
70
- *
71
- * - only binary NetPBM formats are supported (P4,P5,P6 types)
72
- * - 1bit PBM (P4) => {@link readPBM}
73
- * - grayscale PGM (P5) => {@link readPGM8} or {@link readPGM16}
74
- * - 24bit RGB (P6) => {@link readPPM}
75
- *
76
- * Function will throw an error if image is of any other type or header is
77
- * corrupt otherwise. Any embedded comments will be discarded.
78
- *
79
- * @param src -
80
- */
81
- export const read = (src) => {
82
- const { type, width, height, max, start } = parseHeader(src);
83
- switch (type) {
84
- case "P4":
85
- return readPBM(src, start, width, height);
86
- case "P5":
87
- return max < 0x100
88
- ? readPGM8(src, start, width, height, max)
89
- : readPGM16(src, start, width, height, max);
90
- case "P6":
91
- return readPPM(src, start, width, height, max);
92
- default:
93
- unsupported(`PBM type: ${type}`);
94
- }
57
+ const read = (src) => {
58
+ const { type, width, height, max, start } = parseHeader(src);
59
+ switch (type) {
60
+ case "P4":
61
+ return readPBM(src, start, width, height);
62
+ case "P5":
63
+ return max < 256 ? readPGM8(src, start, width, height, max) : readPGM16(src, start, width, height, max);
64
+ case "P6":
65
+ return readPPM(src, start, width, height, max);
66
+ default:
67
+ unsupported(`PBM type: ${type}`);
68
+ }
95
69
  };
96
- /**
97
- * Reads pixels from given 1bit PBM file byte buffer, starting at index `i` and
98
- * returns
99
- * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html) in
100
- * `GRAY8` format (due to current lack of true 1bit format).
101
- *
102
- * @param src -
103
- * @param i -
104
- * @param width -
105
- * @param height -
106
- */
107
- export const readPBM = (src, i, width, height) => {
108
- const buf = intBuffer(width, height, GRAY8);
109
- const data = buf.data;
110
- const w1 = width - 1;
111
- for (let y = 0, j = 0; y < height; y++) {
112
- for (let x = 0; x < width; x++, j++) {
113
- data[j] = src[i] & (1 << (~x & 7)) ? 0 : 0xff;
114
- if ((x & 7) === 7 || x === w1)
115
- i++;
116
- }
70
+ const readPBM = (src, i, width, height) => {
71
+ const buf = intBuffer(width, height, GRAY8);
72
+ const data = buf.data;
73
+ const w1 = width - 1;
74
+ for (let y = 0, j = 0; y < height; y++) {
75
+ for (let x = 0; x < width; x++, j++) {
76
+ data[j] = src[i] & 1 << (~x & 7) ? 0 : 255;
77
+ if ((x & 7) === 7 || x === w1)
78
+ i++;
117
79
  }
118
- return buf;
80
+ }
81
+ return buf;
119
82
  };
120
- /**
121
- * Reads pixels from given 8bit PGM file byte buffer, starting at index `i` and
122
- * returns
123
- * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html) in
124
- * `GRAY8` format. If needed, pixel values are rescaled given `max` value
125
- * defined in PGM header (MUST be <= 0xff).
126
- *
127
- * @remarks
128
- * Reference: http://netpbm.sourceforge.net/doc/pbm.html
129
- *
130
- * @param src -
131
- * @param i -
132
- * @param width -
133
- * @param height -
134
- * @param max -
135
- */
136
- export const readPGM8 = (src, i, width, height, max = 0xff) => {
137
- const buf = intBuffer(width, height, GRAY8);
138
- const data = buf.data;
139
- if (max === 0xff) {
140
- data.set(src.subarray(i));
83
+ const readPGM8 = (src, i, width, height, max = 255) => {
84
+ const buf = intBuffer(width, height, GRAY8);
85
+ const data = buf.data;
86
+ if (max === 255) {
87
+ data.set(src.subarray(i));
88
+ } else {
89
+ max = 255 / max;
90
+ for (let j = 0, n = data.length; j < n; i++, j++) {
91
+ data[j] = src[i] * max | 0;
141
92
  }
142
- else {
143
- max = 0xff / max;
144
- for (let j = 0, n = data.length; j < n; i++, j++) {
145
- data[j] = (src[i] * max) | 0;
146
- }
147
- }
148
- return buf;
93
+ }
94
+ return buf;
149
95
  };
150
- /**
151
- * Reads pixels from given 16bit PGM file byte buffer, starting at index `i` and
152
- * returns
153
- * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html) in
154
- * `GRAY16` format. Pixel values are rescaled given `max` value defined in PGM
155
- * header (MUST be <= 0xffff).
156
- *
157
- * @remarks
158
- * Reference: http://netpbm.sourceforge.net/doc/pgm.html
159
- *
160
- * @param src -
161
- * @param i -
162
- * @param width -
163
- * @param height -
164
- * @param max -
165
- */
166
- export const readPGM16 = (src, i, width, height, max = 0xffff) => {
167
- const buf = intBuffer(width, height, GRAY16);
168
- const data = buf.data;
169
- max = 0xffff / max;
170
- for (let j = 0, n = data.length; j < n; i += 2, j++) {
171
- data[j] = (((src[i] << 8) | src[i + 1]) * max) | 0;
172
- }
173
- return buf;
96
+ const readPGM16 = (src, i, width, height, max = 65535) => {
97
+ const buf = intBuffer(width, height, GRAY16);
98
+ const data = buf.data;
99
+ max = 65535 / max;
100
+ for (let j = 0, n = data.length; j < n; i += 2, j++) {
101
+ data[j] = (src[i] << 8 | src[i + 1]) * max | 0;
102
+ }
103
+ return buf;
174
104
  };
175
- /**
176
- * Reads pixels from given 24bit PPM file byte buffer, starting at index `i` and
177
- * returns
178
- * [`IntBuffer`](https://docs.thi.ng/umbrella/pixel/classes/IntBuffer.html) in
179
- * `RGB888` format. Color channel values are rescaled given `max` value defined
180
- * in PGM header (MUST be <= 0xff).
181
- *
182
- * @remarks
183
- * Reference: http://netpbm.sourceforge.net/doc/pgm.html
184
- *
185
- * @param src -
186
- * @param i -
187
- * @param width -
188
- * @param height -
189
- * @param max -
190
- */
191
- export const readPPM = (src, i, width, height, max = 0xff) => {
192
- const buf = intBuffer(width, height, RGB888);
193
- const data = buf.data;
194
- assert(max <= 0xff, `unsupported max value: ${max}`);
195
- if (max === 0xff) {
196
- for (let j = 0, n = data.length; j < n; i += 3, j++) {
197
- data[j] = (src[i] << 16) | (src[i + 1] << 8) | src[i + 2];
198
- }
105
+ const readPPM = (src, i, width, height, max = 255) => {
106
+ const buf = intBuffer(width, height, RGB888);
107
+ const data = buf.data;
108
+ assert(max <= 255, `unsupported max value: ${max}`);
109
+ if (max === 255) {
110
+ for (let j = 0, n = data.length; j < n; i += 3, j++) {
111
+ data[j] = src[i] << 16 | src[i + 1] << 8 | src[i + 2];
199
112
  }
200
- else {
201
- max = 0xff / max;
202
- for (let j = 0, n = data.length; j < n; i += 3, j++) {
203
- data[j] =
204
- ((src[i] * max) << 16) |
205
- ((src[i + 1] * max) << 8) |
206
- (src[i + 2] * max);
207
- }
113
+ } else {
114
+ max = 255 / max;
115
+ for (let j = 0, n = data.length; j < n; i += 3, j++) {
116
+ data[j] = src[i] * max << 16 | src[i + 1] * max << 8 | src[i + 2] * max;
208
117
  }
209
- return buf;
118
+ }
119
+ return buf;
120
+ };
121
+ export {
122
+ parseHeader,
123
+ read,
124
+ readPBM,
125
+ readPGM16,
126
+ readPGM8,
127
+ readPPM
210
128
  };
package/write.js CHANGED
@@ -1,114 +1,95 @@
1
1
  import { GRAY16 } from "@thi.ng/pixel/format/gray16";
2
2
  const formatComments = (comments = ["generated by @thi.ng/pixel-io-netpbm"]) => comments.map((x) => `# ${x}`).join("\n");
3
- /**
4
- * Initializes byte array & PBM header for given {@link IntBuffer} and format
5
- * details.
6
- *
7
- * @param magic -
8
- * @param limits -
9
- * @param size -
10
- * @param buf -
11
- *
12
- * @internal
13
- */
14
3
  const initHeader = (magic, limits, size, buf, comments) => {
15
- const { width, height } = buf;
16
- let header = magic + "\n";
17
- const comm = formatComments(comments);
18
- if (comm.length)
19
- header += comm + "\n";
20
- header += `${width} ${height}\n`;
21
- if (limits > 0)
22
- header += limits + "\n";
23
- const dest = new Uint8Array(size + header.length);
24
- dest.set([...header].map((x) => x.charCodeAt(0)));
25
- return { dest, start: header.length, abgr: buf.format.toABGR };
4
+ const { width, height } = buf;
5
+ let header = magic + "\n";
6
+ const comm = formatComments(comments);
7
+ if (comm.length)
8
+ header += comm + "\n";
9
+ header += `${width} ${height}
10
+ `;
11
+ if (limits > 0)
12
+ header += limits + "\n";
13
+ const dest = new Uint8Array(size + header.length);
14
+ dest.set([...header].map((x) => x.charCodeAt(0)));
15
+ return { dest, start: header.length, abgr: buf.format.toABGR };
26
16
  };
27
- /**
28
- * Converts a {@link IntBuffer} into a 1bit PBM byte array (binary format).
29
- *
30
- * @remarks
31
- * Reference: http://netpbm.sourceforge.net/doc/pbm.html
32
- *
33
- * @param buf -
34
- * @param comments -
35
- */
36
- export const asPBM = (buf, comments) => {
37
- const { data, width, height } = buf;
38
- const { dest, start, abgr } = initHeader("P4", 0, Math.ceil(width / 8) * height, buf, comments);
39
- const w1 = width - 1;
40
- for (let y = 0, i = start, j = 0; y < height; y++) {
41
- for (let x = 0, b = 0; x <= w1; x++, j++) {
42
- const xx = ~x & 7;
43
- if (luminance(abgr(data[j])) < 128) {
44
- b |= 1 << xx;
45
- }
46
- if (xx === 0 || x === w1) {
47
- dest[i++] = b;
48
- b = 0;
49
- }
50
- }
17
+ const asPBM = (buf, comments) => {
18
+ const { data, width, height } = buf;
19
+ const { dest, start, abgr } = initHeader(
20
+ "P4",
21
+ 0,
22
+ Math.ceil(width / 8) * height,
23
+ buf,
24
+ comments
25
+ );
26
+ const w1 = width - 1;
27
+ for (let y = 0, i = start, j = 0; y < height; y++) {
28
+ for (let x = 0, b = 0; x <= w1; x++, j++) {
29
+ const xx = ~x & 7;
30
+ if (luminance(abgr(data[j])) < 128) {
31
+ b |= 1 << xx;
32
+ }
33
+ if (xx === 0 || x === w1) {
34
+ dest[i++] = b;
35
+ b = 0;
36
+ }
51
37
  }
52
- return dest;
38
+ }
39
+ return dest;
53
40
  };
54
- /**
55
- * Converts a {@link IntBuffer} into a 8bit grayscale PGM byte array (binary
56
- * format).
57
- *
58
- * @remarks
59
- * Reference: http://netpbm.sourceforge.net/doc/pgm.html
60
- *
61
- * @param buf -
62
- * @param comments -
63
- */
64
- export const asPGM = (buf, comments) => {
65
- const { data, width, height } = buf;
66
- const { dest, start, abgr } = initHeader("P5", 0xff, width * height, buf, comments);
67
- for (let i = start, j = 0; j < data.length; i++, j++) {
68
- dest[i] = luminance(abgr(data[j]));
69
- }
70
- return dest;
41
+ const asPGM = (buf, comments) => {
42
+ const { data, width, height } = buf;
43
+ const { dest, start, abgr } = initHeader(
44
+ "P5",
45
+ 255,
46
+ width * height,
47
+ buf,
48
+ comments
49
+ );
50
+ for (let i = start, j = 0; j < data.length; i++, j++) {
51
+ dest[i] = luminance(abgr(data[j]));
52
+ }
53
+ return dest;
71
54
  };
72
- /**
73
- * Converts a {@link IntBuffer} into a 16bit grayscale PGM byte array (binary
74
- * format).
75
- *
76
- * @remarks
77
- * Reference: http://netpbm.sourceforge.net/doc/pgm.html
78
- *
79
- * @param buf -
80
- * @param comments -
81
- */
82
- export const asPGM16 = (buf, comments) => {
83
- if (buf.format !== GRAY16)
84
- buf = buf.as(GRAY16);
85
- const { data, width, height } = buf;
86
- const { dest, start } = initHeader("P5", 0xffff, width * height * 2, buf, comments);
87
- for (let i = start, j = 0; j < data.length; i += 2, j++) {
88
- dest[i] = data[j] >> 8;
89
- dest[i + 1] = data[j] & 0xff;
90
- }
91
- return dest;
55
+ const asPGM16 = (buf, comments) => {
56
+ if (buf.format !== GRAY16)
57
+ buf = buf.as(GRAY16);
58
+ const { data, width, height } = buf;
59
+ const { dest, start } = initHeader(
60
+ "P5",
61
+ 65535,
62
+ width * height * 2,
63
+ buf,
64
+ comments
65
+ );
66
+ for (let i = start, j = 0; j < data.length; i += 2, j++) {
67
+ dest[i] = data[j] >> 8;
68
+ dest[i + 1] = data[j] & 255;
69
+ }
70
+ return dest;
92
71
  };
93
- /**
94
- * Converts a {@link IntBuffer} into a 24bit PPM byte array (binary format).
95
- *
96
- * @remarks
97
- * Reference: http://netpbm.sourceforge.net/doc/ppm.html
98
- *
99
- * @param buf -
100
- * @param comments -
101
- */
102
- export const asPPM = (buf, comments) => {
103
- const { data, width, height } = buf;
104
- const { dest, start, abgr } = initHeader("P6", 255, width * 3 * height, buf, comments);
105
- for (let i = start, j = 0; j < data.length; i += 3, j++) {
106
- const col = abgr(data[j]);
107
- dest[i] = col & 0xff;
108
- dest[i + 1] = (col >> 8) & 0xff;
109
- dest[i + 2] = (col >> 16) & 0xff;
110
- }
111
- return dest;
72
+ const asPPM = (buf, comments) => {
73
+ const { data, width, height } = buf;
74
+ const { dest, start, abgr } = initHeader(
75
+ "P6",
76
+ 255,
77
+ width * 3 * height,
78
+ buf,
79
+ comments
80
+ );
81
+ for (let i = start, j = 0; j < data.length; i += 3, j++) {
82
+ const col = abgr(data[j]);
83
+ dest[i] = col & 255;
84
+ dest[i + 1] = col >> 8 & 255;
85
+ dest[i + 2] = col >> 16 & 255;
86
+ }
87
+ return dest;
88
+ };
89
+ const luminance = (c) => ((c >>> 16 & 255) * 29 + (c >>> 8 & 255) * 150 + (c & 255) * 76) / 255;
90
+ export {
91
+ asPBM,
92
+ asPGM,
93
+ asPGM16,
94
+ asPPM
112
95
  };
113
- const luminance = (c) => (((c >>> 16) & 0xff) * 29 + ((c >>> 8) & 0xff) * 150 + (c & 0xff) * 76) /
114
- 255;