strom-research 1.0.0
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/LICENSE +373 -0
- package/README.md +142 -0
- package/assets/lang/cs.json +302 -0
- package/assets/lang/de.json +302 -0
- package/assets/method/core.md +43 -0
- package/assets/method/enrich.md +11 -0
- package/assets/method/intake.md +30 -0
- package/assets/method/link.md +28 -0
- package/assets/method/locate.md +28 -0
- package/assets/method/narrate.md +13 -0
- package/assets/method/reading.md +62 -0
- package/assets/method/recording.md +59 -0
- package/assets/method/request.md +10 -0
- package/assets/method/verify.md +17 -0
- package/assets/plugins/README.md +23 -0
- package/assets/plugins/connectors/DISCOVERY.md +159 -0
- package/assets/plugins/connectors/README.md +376 -0
- package/assets/plugins/connectors/sdk.ts +168 -0
- package/assets/plugins/connectors/template.ts +38 -0
- package/assets/plugins/gitignore +4 -0
- package/dist/agents/files.js +313 -0
- package/dist/agents/global.js +257 -0
- package/dist/agents/launch.js +36 -0
- package/dist/agents/profiles.js +95 -0
- package/dist/brief/brief.js +345 -0
- package/dist/cli/commit.js +44 -0
- package/dist/cli/context.js +311 -0
- package/dist/cli/execute.js +154 -0
- package/dist/cli/fixes.js +78 -0
- package/dist/cli/format.js +53 -0
- package/dist/cli/help.js +59 -0
- package/dist/cli/main.js +152 -0
- package/dist/cli/menu.js +212 -0
- package/dist/cli/registry.js +96 -0
- package/dist/cli/ui.js +266 -0
- package/dist/cli/wizard.js +142 -0
- package/dist/cli.js +14 -0
- package/dist/commands/analysis.js +622 -0
- package/dist/commands/batch.js +181 -0
- package/dist/commands/checks.js +153 -0
- package/dist/commands/connectors.js +1377 -0
- package/dist/commands/guide.js +160 -0
- package/dist/commands/index.js +19 -0
- package/dist/commands/intake.js +234 -0
- package/dist/commands/media.js +406 -0
- package/dist/commands/meta.js +195 -0
- package/dist/commands/output.js +117 -0
- package/dist/commands/people.js +664 -0
- package/dist/commands/read.js +199 -0
- package/dist/commands/research.js +139 -0
- package/dist/commands/session.js +605 -0
- package/dist/commands/setup.js +465 -0
- package/dist/commands/sources.js +634 -0
- package/dist/commands/start.js +383 -0
- package/dist/commands/story.js +75 -0
- package/dist/commands/tasks.js +436 -0
- package/dist/commands/trees.js +128 -0
- package/dist/core/actions.js +852 -0
- package/dist/core/age.js +95 -0
- package/dist/core/apps.js +74 -0
- package/dist/core/assets.js +34 -0
- package/dist/core/awake.js +33 -0
- package/dist/core/browser.js +281 -0
- package/dist/core/calibration.js +48 -0
- package/dist/core/check.js +112 -0
- package/dist/core/chromium.js +88 -0
- package/dist/core/config.js +348 -0
- package/dist/core/connector.js +811 -0
- package/dist/core/deps.js +73 -0
- package/dist/core/dialog.js +61 -0
- package/dist/core/errors.js +89 -0
- package/dist/core/evidence.js +58 -0
- package/dist/core/frontier.js +219 -0
- package/dist/core/gdate.js +77 -0
- package/dist/core/git.js +300 -0
- package/dist/core/guard.js +124 -0
- package/dist/core/http2.js +76 -0
- package/dist/core/import.js +541 -0
- package/dist/core/install.js +28 -0
- package/dist/core/integrity.js +219 -0
- package/dist/core/json.js +87 -0
- package/dist/core/lang.js +70 -0
- package/dist/core/live.js +244 -0
- package/dist/core/lock.js +112 -0
- package/dist/core/logins.js +67 -0
- package/dist/core/media.js +223 -0
- package/dist/core/model.js +101 -0
- package/dist/core/net.js +366 -0
- package/dist/core/open.js +29 -0
- package/dist/core/paths.js +84 -0
- package/dist/core/people.js +283 -0
- package/dist/core/phrases.js +85 -0
- package/dist/core/queue.js +113 -0
- package/dist/core/reader.js +76 -0
- package/dist/core/records.js +105 -0
- package/dist/core/roles.js +30 -0
- package/dist/core/schema.js +261 -0
- package/dist/core/seal.js +77 -0
- package/dist/core/self.js +40 -0
- package/dist/core/session.js +155 -0
- package/dist/core/shortcut.js +90 -0
- package/dist/core/stories.js +61 -0
- package/dist/core/stromapp.js +138 -0
- package/dist/core/text.js +104 -0
- package/dist/core/tree.js +507 -0
- package/dist/core/uninstall.js +128 -0
- package/dist/core/update.js +193 -0
- package/dist/core/validate.js +260 -0
- package/dist/core/views.js +164 -0
- package/dist/core/which.js +51 -0
- package/dist/core/workers.js +42 -0
- package/dist/gedcom/export.js +454 -0
- package/dist/gedcom/labels.js +103 -0
- package/dist/gedcom/lines.js +91 -0
- package/dist/gedcom/parse.js +53 -0
- package/dist/gedcom/validate.js +183 -0
- package/dist/image/image.js +223 -0
- package/dist/image/index.js +114 -0
- package/dist/image/jpeg-decode.js +552 -0
- package/dist/image/jpeg-encode.js +254 -0
- package/dist/image/png.js +241 -0
- package/dist/runners/antigravity.js +70 -0
- package/dist/runners/claude.js +179 -0
- package/dist/runners/codex.js +45 -0
- package/dist/runners/index.js +13 -0
- package/dist/runners/jsonl.js +86 -0
- package/dist/runners/opencode.js +50 -0
- package/dist/runners/runner.js +63 -0
- package/dist/runners/script.js +58 -0
- package/package.json +44 -0
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
// JPEG decoder: baseline and progressive (Huffman), any chroma subsampling,
|
|
2
|
+
// restart intervals, JFIF / Adobe (RGB, YCbCr, CMYK, YCCK). Archive scans are
|
|
3
|
+
// JPEG almost always; nothing else is needed to crop and enlarge them.
|
|
4
|
+
// Follows ITU T.81 and the structure of libjpeg's decoder.
|
|
5
|
+
const ZIGZAG = new Int32Array([
|
|
6
|
+
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29,
|
|
7
|
+
22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
|
|
8
|
+
]);
|
|
9
|
+
export class JpegError extends Error {
|
|
10
|
+
}
|
|
11
|
+
const LOOKUP_BITS = 9;
|
|
12
|
+
function buildHuffman(counts, values) {
|
|
13
|
+
const maxcode = new Int32Array(18).fill(-1);
|
|
14
|
+
const valptr = new Int32Array(17);
|
|
15
|
+
const mincode = new Int32Array(17);
|
|
16
|
+
const lookup = new Uint16Array(1 << LOOKUP_BITS);
|
|
17
|
+
let code = 0;
|
|
18
|
+
let k = 0;
|
|
19
|
+
for (let len = 1; len <= 16; len++) {
|
|
20
|
+
valptr[len] = k;
|
|
21
|
+
mincode[len] = code;
|
|
22
|
+
const n = counts[len - 1];
|
|
23
|
+
for (let i = 0; i < n; i++, k++, code++) {
|
|
24
|
+
if (len <= LOOKUP_BITS) {
|
|
25
|
+
const shift = LOOKUP_BITS - len;
|
|
26
|
+
const base = code << shift;
|
|
27
|
+
for (let f = 0; f < 1 << shift; f++)
|
|
28
|
+
lookup[base + f] = (len << 8) | values[k];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
maxcode[len] = n ? code - 1 : -1;
|
|
32
|
+
code <<= 1;
|
|
33
|
+
}
|
|
34
|
+
maxcode[17] = 0x7fffffff;
|
|
35
|
+
return { lookup, maxcode, valptr, mincode, values };
|
|
36
|
+
}
|
|
37
|
+
/** Bits of the entropy-coded data; stops at a marker. */
|
|
38
|
+
class BitReader {
|
|
39
|
+
data;
|
|
40
|
+
pos;
|
|
41
|
+
buf = 0;
|
|
42
|
+
bits = 0;
|
|
43
|
+
/** A marker (RSTn, EOI, …) was reached. */
|
|
44
|
+
marker = -1;
|
|
45
|
+
constructor(data, start) {
|
|
46
|
+
this.data = data;
|
|
47
|
+
this.pos = start;
|
|
48
|
+
}
|
|
49
|
+
get offset() {
|
|
50
|
+
return this.pos;
|
|
51
|
+
}
|
|
52
|
+
fill() {
|
|
53
|
+
while (this.bits <= 24) {
|
|
54
|
+
let byte = 0;
|
|
55
|
+
if (this.marker < 0 && this.pos < this.data.length) {
|
|
56
|
+
byte = this.data[this.pos];
|
|
57
|
+
if (byte === 0xff) {
|
|
58
|
+
const next = this.data[this.pos + 1] ?? 0;
|
|
59
|
+
if (next === 0)
|
|
60
|
+
this.pos += 2; // stuffed zero
|
|
61
|
+
else {
|
|
62
|
+
this.marker = next; // leave the marker for the caller; feed zeros
|
|
63
|
+
byte = 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
this.pos++;
|
|
68
|
+
}
|
|
69
|
+
this.buf = (this.buf << 8) | byte;
|
|
70
|
+
this.bits += 8;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
peek(n) {
|
|
74
|
+
if (this.bits < n)
|
|
75
|
+
this.fill();
|
|
76
|
+
return (this.buf >>> (this.bits - n)) & ((1 << n) - 1);
|
|
77
|
+
}
|
|
78
|
+
skip(n) {
|
|
79
|
+
this.bits -= n;
|
|
80
|
+
this.buf &= (1 << this.bits) - 1;
|
|
81
|
+
}
|
|
82
|
+
read(n) {
|
|
83
|
+
if (n === 0)
|
|
84
|
+
return 0;
|
|
85
|
+
const v = this.peek(n);
|
|
86
|
+
this.skip(n);
|
|
87
|
+
return v;
|
|
88
|
+
}
|
|
89
|
+
bit() {
|
|
90
|
+
return this.read(1);
|
|
91
|
+
}
|
|
92
|
+
/** After a restart marker: byte-align and continue after it. */
|
|
93
|
+
restart() {
|
|
94
|
+
this.buf = 0;
|
|
95
|
+
this.bits = 0;
|
|
96
|
+
if (this.marker >= 0xd0 && this.marker <= 0xd7) {
|
|
97
|
+
this.pos += 2;
|
|
98
|
+
this.marker = -1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// no marker seen yet: find the next RST
|
|
102
|
+
while (this.pos + 1 < this.data.length) {
|
|
103
|
+
if (this.data[this.pos] === 0xff && this.data[this.pos + 1] >= 0xd0 && this.data[this.pos + 1] <= 0xd7) {
|
|
104
|
+
this.pos += 2;
|
|
105
|
+
this.marker = -1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.pos++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Where the scan data ends: the position of the marker that ended it. */
|
|
112
|
+
end() {
|
|
113
|
+
let p = this.pos;
|
|
114
|
+
while (p + 1 < this.data.length && !(this.data[p] === 0xff && this.data[p + 1] !== 0 && !(this.data[p + 1] >= 0xd0 && this.data[p + 1] <= 0xd7)))
|
|
115
|
+
p++;
|
|
116
|
+
return p;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function decodeHuffman(r, h) {
|
|
120
|
+
const look = h.lookup[r.peek(LOOKUP_BITS)];
|
|
121
|
+
if (look) {
|
|
122
|
+
r.skip(look >> 8);
|
|
123
|
+
return look & 0xff;
|
|
124
|
+
}
|
|
125
|
+
let code = r.read(LOOKUP_BITS);
|
|
126
|
+
let len = LOOKUP_BITS;
|
|
127
|
+
while (code > h.maxcode[len]) {
|
|
128
|
+
code = (code << 1) | r.bit();
|
|
129
|
+
len++;
|
|
130
|
+
if (len > 16)
|
|
131
|
+
throw new JpegError("invalid Huffman code");
|
|
132
|
+
}
|
|
133
|
+
return h.values[h.valptr[len] + code - h.mincode[len]];
|
|
134
|
+
}
|
|
135
|
+
function extend(v, s) {
|
|
136
|
+
return v < 1 << (s - 1) ? v - (1 << s) + 1 : v;
|
|
137
|
+
}
|
|
138
|
+
function decodeScan(data, start, frame, scan, restartInterval) {
|
|
139
|
+
const r = new BitReader(data, start);
|
|
140
|
+
const { ss, se, ah, al } = scan;
|
|
141
|
+
const single = scan.components.length === 1;
|
|
142
|
+
let eobrun = 0;
|
|
143
|
+
for (const c of scan.components)
|
|
144
|
+
c.pred = 0;
|
|
145
|
+
const p1 = 1 << al;
|
|
146
|
+
const m1 = -1 << al;
|
|
147
|
+
const decodeBlock = (c, off) => {
|
|
148
|
+
const coefs = c.coefs;
|
|
149
|
+
if (!frame.progressive) {
|
|
150
|
+
const t = decodeHuffman(r, c.dcTable);
|
|
151
|
+
c.pred += t === 0 ? 0 : extend(r.read(t), t);
|
|
152
|
+
coefs[off] = c.pred;
|
|
153
|
+
for (let k = 1; k < 64;) {
|
|
154
|
+
const rs = decodeHuffman(r, c.acTable);
|
|
155
|
+
const s = rs & 15;
|
|
156
|
+
const run = rs >> 4;
|
|
157
|
+
if (s === 0) {
|
|
158
|
+
if (run < 15)
|
|
159
|
+
break;
|
|
160
|
+
k += 16;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
k += run;
|
|
164
|
+
if (k > 63)
|
|
165
|
+
break;
|
|
166
|
+
coefs[off + ZIGZAG[k]] = extend(r.read(s), s);
|
|
167
|
+
k++;
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (ss === 0) {
|
|
172
|
+
// DC scans
|
|
173
|
+
if (ah === 0) {
|
|
174
|
+
const t = decodeHuffman(r, c.dcTable);
|
|
175
|
+
c.pred += t === 0 ? 0 : extend(r.read(t), t);
|
|
176
|
+
coefs[off] = c.pred * (1 << al);
|
|
177
|
+
}
|
|
178
|
+
else if (r.bit())
|
|
179
|
+
coefs[off] |= p1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (ah === 0) {
|
|
183
|
+
// AC first pass
|
|
184
|
+
if (eobrun > 0) {
|
|
185
|
+
eobrun--;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
for (let k = ss; k <= se;) {
|
|
189
|
+
const rs = decodeHuffman(r, c.acTable);
|
|
190
|
+
const s = rs & 15;
|
|
191
|
+
const run = rs >> 4;
|
|
192
|
+
if (s === 0) {
|
|
193
|
+
if (run < 15) {
|
|
194
|
+
eobrun = (1 << run) - 1;
|
|
195
|
+
if (run)
|
|
196
|
+
eobrun += r.read(run);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
k += 16;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
k += run;
|
|
203
|
+
if (k > 63)
|
|
204
|
+
break;
|
|
205
|
+
coefs[off + ZIGZAG[k]] = extend(r.read(s), s) * (1 << al);
|
|
206
|
+
k++;
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// AC refinement (libjpeg decode_mcu_AC_refine)
|
|
211
|
+
let k = ss;
|
|
212
|
+
const refine = (z) => {
|
|
213
|
+
if (r.bit() && (coefs[off + z] & p1) === 0)
|
|
214
|
+
coefs[off + z] = coefs[off + z] + (coefs[off + z] >= 0 ? p1 : m1);
|
|
215
|
+
};
|
|
216
|
+
if (eobrun === 0) {
|
|
217
|
+
for (; k <= se; k++) {
|
|
218
|
+
const rs = decodeHuffman(r, c.acTable);
|
|
219
|
+
let run = rs >> 4;
|
|
220
|
+
let s = rs & 15;
|
|
221
|
+
if (s)
|
|
222
|
+
s = r.bit() ? p1 : m1;
|
|
223
|
+
else if (run !== 15) {
|
|
224
|
+
eobrun = 1 << run;
|
|
225
|
+
if (run)
|
|
226
|
+
eobrun += r.read(run);
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
do {
|
|
230
|
+
const z = ZIGZAG[k];
|
|
231
|
+
if (coefs[off + z] !== 0)
|
|
232
|
+
refine(z);
|
|
233
|
+
else if (--run < 0)
|
|
234
|
+
break;
|
|
235
|
+
k++;
|
|
236
|
+
} while (k <= se);
|
|
237
|
+
if (s && k <= se)
|
|
238
|
+
coefs[off + ZIGZAG[k]] = s;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (eobrun > 0) {
|
|
242
|
+
for (; k <= se; k++) {
|
|
243
|
+
const z = ZIGZAG[k];
|
|
244
|
+
if (coefs[off + z] !== 0)
|
|
245
|
+
refine(z);
|
|
246
|
+
}
|
|
247
|
+
eobrun--;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const blockOffset = (c, row, col) => 64 * (row * c.stride + col);
|
|
251
|
+
let mcus;
|
|
252
|
+
let next;
|
|
253
|
+
if (single) {
|
|
254
|
+
// A single component: its own blocks in raster order, no MCU padding.
|
|
255
|
+
const c = scan.components[0];
|
|
256
|
+
mcus = c.blocksPerLine * c.blocksPerColumn;
|
|
257
|
+
next = (i) => decodeBlock(c, blockOffset(c, Math.floor(i / c.blocksPerLine), i % c.blocksPerLine));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
mcus = frame.mcusPerLine * frame.mcusPerColumn;
|
|
261
|
+
next = (i) => {
|
|
262
|
+
const mrow = Math.floor(i / frame.mcusPerLine);
|
|
263
|
+
const mcol = i % frame.mcusPerLine;
|
|
264
|
+
for (const c of scan.components)
|
|
265
|
+
for (let v = 0; v < c.v; v++)
|
|
266
|
+
for (let h = 0; h < c.h; h++)
|
|
267
|
+
decodeBlock(c, blockOffset(c, mrow * c.v + v, mcol * c.h + h));
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
for (let i = 0; i < mcus; i++) {
|
|
271
|
+
if (restartInterval && i > 0 && i % restartInterval === 0) {
|
|
272
|
+
r.restart();
|
|
273
|
+
eobrun = 0;
|
|
274
|
+
for (const c of scan.components)
|
|
275
|
+
c.pred = 0;
|
|
276
|
+
}
|
|
277
|
+
next(i);
|
|
278
|
+
}
|
|
279
|
+
return r.end();
|
|
280
|
+
}
|
|
281
|
+
/** Separable float inverse DCT of one dequantized block → 8×8 samples. */
|
|
282
|
+
const COS = (() => {
|
|
283
|
+
const t = new Float64Array(64);
|
|
284
|
+
for (let x = 0; x < 8; x++)
|
|
285
|
+
for (let u = 0; u < 8; u++)
|
|
286
|
+
t[x * 8 + u] = (u === 0 ? Math.SQRT1_2 : 1) * Math.cos(((2 * x + 1) * u * Math.PI) / 16) / 2;
|
|
287
|
+
return t;
|
|
288
|
+
})();
|
|
289
|
+
function idct(block, tmp, out, outOff, outStride) {
|
|
290
|
+
// rows: tmp[y][x] = Σu C(u) F(y,u) cos(...)
|
|
291
|
+
for (let y = 0; y < 8; y++) {
|
|
292
|
+
const r = y * 8;
|
|
293
|
+
let zero = true;
|
|
294
|
+
for (let u = 1; u < 8; u++)
|
|
295
|
+
if (block[r + u] !== 0)
|
|
296
|
+
zero = false;
|
|
297
|
+
if (zero) {
|
|
298
|
+
const dc = block[r] * COS[0];
|
|
299
|
+
for (let x = 0; x < 8; x++)
|
|
300
|
+
tmp[r + x] = dc;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
for (let x = 0; x < 8; x++) {
|
|
304
|
+
let s = 0;
|
|
305
|
+
for (let u = 0; u < 8; u++)
|
|
306
|
+
s += COS[x * 8 + u] * block[r + u];
|
|
307
|
+
tmp[r + x] = s;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (let x = 0; x < 8; x++)
|
|
311
|
+
for (let y = 0; y < 8; y++) {
|
|
312
|
+
let s = 0;
|
|
313
|
+
for (let v = 0; v < 8; v++)
|
|
314
|
+
s += COS[y * 8 + v] * tmp[v * 8 + x];
|
|
315
|
+
const val = Math.round(s + 128);
|
|
316
|
+
out[outOff + y * outStride + x] = val < 0 ? 0 : val > 255 ? 255 : val;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** Samples of one component, at its own resolution (padded to whole blocks). */
|
|
320
|
+
function componentSamples(c, quant) {
|
|
321
|
+
const width = c.stride * 8;
|
|
322
|
+
const rows = (c.coefs.length / 64 / c.stride) * 8;
|
|
323
|
+
const out = new Uint8Array(width * rows);
|
|
324
|
+
const block = new Float64Array(64);
|
|
325
|
+
const tmp = new Float64Array(64);
|
|
326
|
+
const blocks = c.coefs.length / 64;
|
|
327
|
+
for (let b = 0; b < blocks; b++) {
|
|
328
|
+
const off = b * 64;
|
|
329
|
+
for (let i = 0; i < 64; i++)
|
|
330
|
+
block[i] = c.coefs[off + i] * quant[i];
|
|
331
|
+
const row = Math.floor(b / c.stride);
|
|
332
|
+
const col = b % c.stride;
|
|
333
|
+
idct(block, tmp, out, row * 8 * width + col * 8, width);
|
|
334
|
+
}
|
|
335
|
+
return { data: out, width };
|
|
336
|
+
}
|
|
337
|
+
const clamp = (v) => (v < 0 ? 0 : v > 255 ? 255 : Math.round(v));
|
|
338
|
+
export function decodeJpeg(bytes) {
|
|
339
|
+
if (bytes[0] !== 0xff || bytes[1] !== 0xd8)
|
|
340
|
+
throw new JpegError("not a JPEG file");
|
|
341
|
+
const quant = [];
|
|
342
|
+
const dcTables = [];
|
|
343
|
+
const acTables = [];
|
|
344
|
+
let frame;
|
|
345
|
+
let restartInterval = 0;
|
|
346
|
+
let adobe;
|
|
347
|
+
let jfif = false;
|
|
348
|
+
let pos = 2;
|
|
349
|
+
const u16 = (p) => (bytes[p] << 8) | bytes[p + 1];
|
|
350
|
+
while (pos < bytes.length) {
|
|
351
|
+
if (bytes[pos] !== 0xff) {
|
|
352
|
+
pos++;
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const marker = bytes[pos + 1];
|
|
356
|
+
pos += 2;
|
|
357
|
+
if (marker === 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker === 0x01 || marker === 0xff) {
|
|
358
|
+
if (marker === 0xff)
|
|
359
|
+
pos--;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (marker === 0xd9)
|
|
363
|
+
break; // EOI
|
|
364
|
+
const len = u16(pos);
|
|
365
|
+
const seg = pos + 2;
|
|
366
|
+
const end = pos + len;
|
|
367
|
+
switch (marker) {
|
|
368
|
+
case 0xe0: // APP0
|
|
369
|
+
if (bytes[seg] === 0x4a && bytes[seg + 1] === 0x46 && bytes[seg + 2] === 0x49 && bytes[seg + 3] === 0x46)
|
|
370
|
+
jfif = true;
|
|
371
|
+
break;
|
|
372
|
+
case 0xee: // APP14 Adobe
|
|
373
|
+
if (bytes[seg] === 0x41 && bytes[seg + 1] === 0x64 && bytes[seg + 2] === 0x6f && bytes[seg + 3] === 0x62 && bytes[seg + 4] === 0x65)
|
|
374
|
+
adobe = bytes[seg + 11];
|
|
375
|
+
break;
|
|
376
|
+
case 0xdb: {
|
|
377
|
+
// DQT
|
|
378
|
+
let p = seg;
|
|
379
|
+
while (p < end) {
|
|
380
|
+
const pq = bytes[p] >> 4;
|
|
381
|
+
const tq = bytes[p] & 15;
|
|
382
|
+
p++;
|
|
383
|
+
const t = new Int32Array(64);
|
|
384
|
+
for (let i = 0; i < 64; i++) {
|
|
385
|
+
t[ZIGZAG[i]] = pq ? u16(p + i * 2) : bytes[p + i];
|
|
386
|
+
}
|
|
387
|
+
p += pq ? 128 : 64;
|
|
388
|
+
quant[tq] = t;
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case 0xc0:
|
|
393
|
+
case 0xc1:
|
|
394
|
+
case 0xc2: {
|
|
395
|
+
// SOF0 baseline, SOF1 extended, SOF2 progressive (Huffman)
|
|
396
|
+
if (bytes[seg] !== 8)
|
|
397
|
+
throw new JpegError(`${bytes[seg]}-bit JPEG is not supported`);
|
|
398
|
+
const height = u16(seg + 1);
|
|
399
|
+
const width = u16(seg + 3);
|
|
400
|
+
const n = bytes[seg + 5];
|
|
401
|
+
if (!width || !height)
|
|
402
|
+
throw new JpegError("JPEG without dimensions (DNL) is not supported");
|
|
403
|
+
const comps = [];
|
|
404
|
+
for (let i = 0; i < n; i++) {
|
|
405
|
+
const p = seg + 6 + i * 3;
|
|
406
|
+
comps.push({ id: bytes[p], h: bytes[p + 1] >> 4, v: bytes[p + 1] & 15, tq: bytes[p + 2], blocksPerLine: 0, blocksPerColumn: 0, stride: 0, coefs: new Int16Array(0), pred: 0 });
|
|
407
|
+
}
|
|
408
|
+
const hmax = Math.max(...comps.map((c) => c.h));
|
|
409
|
+
const vmax = Math.max(...comps.map((c) => c.v));
|
|
410
|
+
const mcusPerLine = Math.ceil(width / (8 * hmax));
|
|
411
|
+
const mcusPerColumn = Math.ceil(height / (8 * vmax));
|
|
412
|
+
for (const c of comps) {
|
|
413
|
+
c.blocksPerLine = Math.ceil(Math.ceil((width * c.h) / hmax) / 8);
|
|
414
|
+
c.blocksPerColumn = Math.ceil(Math.ceil((height * c.v) / vmax) / 8);
|
|
415
|
+
c.stride = mcusPerLine * c.h;
|
|
416
|
+
c.coefs = new Int16Array(64 * c.stride * mcusPerColumn * c.v);
|
|
417
|
+
}
|
|
418
|
+
frame = { progressive: marker === 0xc2, width, height, hmax, vmax, mcusPerLine, mcusPerColumn, components: comps };
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case 0xc3:
|
|
422
|
+
case 0xc5:
|
|
423
|
+
case 0xc6:
|
|
424
|
+
case 0xc7:
|
|
425
|
+
case 0xc9:
|
|
426
|
+
case 0xca:
|
|
427
|
+
case 0xcb:
|
|
428
|
+
case 0xcd:
|
|
429
|
+
case 0xce:
|
|
430
|
+
case 0xcf:
|
|
431
|
+
throw new JpegError("lossless, hierarchical and arithmetic-coded JPEG are not supported");
|
|
432
|
+
case 0xc4: {
|
|
433
|
+
// DHT
|
|
434
|
+
let p = seg;
|
|
435
|
+
while (p < end) {
|
|
436
|
+
const tc = bytes[p] >> 4;
|
|
437
|
+
const th = bytes[p] & 15;
|
|
438
|
+
const counts = bytes.subarray(p + 1, p + 17);
|
|
439
|
+
const total = counts.reduce((a, b) => a + b, 0);
|
|
440
|
+
const values = bytes.slice(p + 17, p + 17 + total);
|
|
441
|
+
(tc === 0 ? dcTables : acTables)[th] = buildHuffman(counts, values);
|
|
442
|
+
p += 17 + total;
|
|
443
|
+
}
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
case 0xdd:
|
|
447
|
+
restartInterval = u16(seg);
|
|
448
|
+
break;
|
|
449
|
+
case 0xda: {
|
|
450
|
+
// SOS
|
|
451
|
+
if (!frame)
|
|
452
|
+
throw new JpegError("scan before frame");
|
|
453
|
+
const n = bytes[seg];
|
|
454
|
+
const comps = [];
|
|
455
|
+
for (let i = 0; i < n; i++) {
|
|
456
|
+
const id = bytes[seg + 1 + i * 2];
|
|
457
|
+
const tables = bytes[seg + 2 + i * 2];
|
|
458
|
+
const c = frame.components.find((x) => x.id === id);
|
|
459
|
+
if (!c)
|
|
460
|
+
throw new JpegError(`scan names an unknown component ${id}`);
|
|
461
|
+
c.dcTable = dcTables[tables >> 4];
|
|
462
|
+
c.acTable = acTables[tables & 15];
|
|
463
|
+
comps.push(c);
|
|
464
|
+
}
|
|
465
|
+
const p = seg + 1 + n * 2;
|
|
466
|
+
const scan = { components: comps, ss: bytes[p], se: bytes[p + 1], ah: bytes[p + 2] >> 4, al: bytes[p + 2] & 15 };
|
|
467
|
+
pos = decodeScan(bytes, end, frame, scan, restartInterval);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
default:
|
|
471
|
+
break; // APPn, COM and others are skipped
|
|
472
|
+
}
|
|
473
|
+
pos = end;
|
|
474
|
+
}
|
|
475
|
+
if (!frame)
|
|
476
|
+
throw new JpegError("no image in the JPEG file");
|
|
477
|
+
// Samples per component, then upsampling and colour conversion.
|
|
478
|
+
const { width, height, hmax, vmax, components } = frame;
|
|
479
|
+
const planes = components.map((c) => {
|
|
480
|
+
const q = quant[c.tq];
|
|
481
|
+
if (!q)
|
|
482
|
+
throw new JpegError(`missing quantization table ${c.tq}`);
|
|
483
|
+
return { c, ...componentSamples(c, q) };
|
|
484
|
+
});
|
|
485
|
+
// Subsampled components are interpolated between sample centres (libjpeg's
|
|
486
|
+
// "fancy" upsampling), not repeated: no colour blocks along strokes.
|
|
487
|
+
const compW = planes.map((pl) => Math.ceil((width * pl.c.h) / hmax));
|
|
488
|
+
const compH = planes.map((pl) => Math.ceil((height * pl.c.v) / vmax));
|
|
489
|
+
const sample = (i, x, y) => {
|
|
490
|
+
const pl = planes[i];
|
|
491
|
+
if (pl.c.h === hmax && pl.c.v === vmax)
|
|
492
|
+
return pl.data[y * pl.width + x];
|
|
493
|
+
const fx = Math.max(0, Math.min(compW[i] - 1, ((x + 0.5) * pl.c.h) / hmax - 0.5));
|
|
494
|
+
const fy = Math.max(0, Math.min(compH[i] - 1, ((y + 0.5) * pl.c.v) / vmax - 0.5));
|
|
495
|
+
const x0 = Math.floor(fx);
|
|
496
|
+
const y0 = Math.floor(fy);
|
|
497
|
+
const x1 = Math.min(compW[i] - 1, x0 + 1);
|
|
498
|
+
const y1 = Math.min(compH[i] - 1, y0 + 1);
|
|
499
|
+
const tx = fx - x0;
|
|
500
|
+
const ty = fy - y0;
|
|
501
|
+
const d = pl.data;
|
|
502
|
+
const w = pl.width;
|
|
503
|
+
return (d[y0 * w + x0] * (1 - tx) + d[y0 * w + x1] * tx) * (1 - ty) + (d[y1 * w + x0] * (1 - tx) + d[y1 * w + x1] * tx) * ty;
|
|
504
|
+
};
|
|
505
|
+
if (components.length === 1) {
|
|
506
|
+
const out = new Uint8Array(width * height);
|
|
507
|
+
for (let y = 0; y < height; y++)
|
|
508
|
+
out.set(planes[0].data.subarray(y * planes[0].width, y * planes[0].width + width), y * width);
|
|
509
|
+
return { width, height, channels: 1, data: out };
|
|
510
|
+
}
|
|
511
|
+
const out = new Uint8Array(width * height * 3);
|
|
512
|
+
const transform = adobe !== undefined ? adobe !== 0 : jfif || components.length === 3;
|
|
513
|
+
for (let y = 0; y < height; y++)
|
|
514
|
+
for (let x = 0; x < width; x++) {
|
|
515
|
+
const o = (y * width + x) * 3;
|
|
516
|
+
let a = sample(0, x, y);
|
|
517
|
+
let b = sample(1, x, y);
|
|
518
|
+
let c = sample(2, x, y);
|
|
519
|
+
if (components.length === 4) {
|
|
520
|
+
// CMYK, or YCCK (Adobe transform 2). Adobe stores ink inverted (255 = none).
|
|
521
|
+
const k = sample(3, x, y);
|
|
522
|
+
if (adobe === 2) {
|
|
523
|
+
const y0 = a;
|
|
524
|
+
a = clamp(y0 + 1.402 * (c - 128));
|
|
525
|
+
b = clamp(y0 - 0.344136 * (b - 128) - 0.714136 * (c - 128));
|
|
526
|
+
c = clamp(y0 + 1.772 * (sample(1, x, y) - 128));
|
|
527
|
+
}
|
|
528
|
+
if (adobe !== undefined) {
|
|
529
|
+
out[o] = clamp((a * k) / 255);
|
|
530
|
+
out[o + 1] = clamp((b * k) / 255);
|
|
531
|
+
out[o + 2] = clamp((c * k) / 255);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
out[o] = clamp(((255 - a) * (255 - k)) / 255);
|
|
535
|
+
out[o + 1] = clamp(((255 - b) * (255 - k)) / 255);
|
|
536
|
+
out[o + 2] = clamp(((255 - c) * (255 - k)) / 255);
|
|
537
|
+
}
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
if (transform) {
|
|
541
|
+
out[o] = clamp(a + 1.402 * (c - 128));
|
|
542
|
+
out[o + 1] = clamp(a - 0.344136 * (b - 128) - 0.714136 * (c - 128));
|
|
543
|
+
out[o + 2] = clamp(a + 1.772 * (b - 128));
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
out[o] = a;
|
|
547
|
+
out[o + 1] = b;
|
|
548
|
+
out[o + 2] = c;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return { width, height, channels: 3, data: out };
|
|
552
|
+
}
|