ag-psd 32.0.0 → 34.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.
Files changed (117) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  25. package/CHANGELOG.md +100 -0
  26. package/LICENSE +5 -0
  27. package/README.md +4 -7
  28. package/README_PSD.md +261 -84
  29. package/TODO.md +13 -84
  30. package/dist/abr.d.ts +11 -4
  31. package/dist/abr.js +362 -230
  32. package/dist/abr.js.map +1 -1
  33. package/dist/additionalInfo.d.ts +56 -2
  34. package/dist/additionalInfo.js +828 -361
  35. package/dist/additionalInfo.js.map +1 -1
  36. package/dist/ase.d.ts +6 -1
  37. package/dist/ase.js +61 -61
  38. package/dist/ase.js.map +1 -1
  39. package/dist/bundle.js +3321 -1364
  40. package/dist/descriptor.d.ts +28 -13
  41. package/dist/descriptor.js +295 -241
  42. package/dist/descriptor.js.map +1 -1
  43. package/dist/helpers.d.ts +7 -1
  44. package/dist/helpers.js +246 -4
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/imageResources.d.ts +15 -3
  47. package/dist/imageResources.js +586 -144
  48. package/dist/imageResources.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/jpeg.js +8 -8
  53. package/dist/jpeg.js.map +1 -1
  54. package/dist/psd.d.ts +292 -19
  55. package/dist/psd.js.map +1 -1
  56. package/dist/psdReader.d.ts +10 -0
  57. package/dist/psdReader.js +424 -216
  58. package/dist/psdReader.js.map +1 -1
  59. package/dist/psdWriter.d.ts +2 -0
  60. package/dist/psdWriter.js +373 -113
  61. package/dist/psdWriter.js.map +1 -1
  62. package/dist/text.d.ts +32 -1
  63. package/dist/text.js +75 -6
  64. package/dist/text.js.map +1 -1
  65. package/dist/utf8.js +0 -3
  66. package/dist/utf8.js.map +1 -1
  67. package/dist-es/abr.d.ts +11 -4
  68. package/dist-es/abr.js +362 -230
  69. package/dist-es/abr.js.map +1 -1
  70. package/dist-es/additionalInfo.d.ts +56 -2
  71. package/dist-es/additionalInfo.js +828 -361
  72. package/dist-es/additionalInfo.js.map +1 -1
  73. package/dist-es/ase.d.ts +6 -1
  74. package/dist-es/ase.js +61 -61
  75. package/dist-es/ase.js.map +1 -1
  76. package/dist-es/descriptor.d.ts +28 -13
  77. package/dist-es/descriptor.js +295 -241
  78. package/dist-es/descriptor.js.map +1 -1
  79. package/dist-es/helpers.d.ts +7 -1
  80. package/dist-es/helpers.js +246 -4
  81. package/dist-es/helpers.js.map +1 -1
  82. package/dist-es/imageResources.d.ts +15 -3
  83. package/dist-es/imageResources.js +586 -144
  84. package/dist-es/imageResources.js.map +1 -1
  85. package/dist-es/index.d.ts +1 -0
  86. package/dist-es/index.js +1 -0
  87. package/dist-es/index.js.map +1 -1
  88. package/dist-es/jpeg.js +8 -8
  89. package/dist-es/jpeg.js.map +1 -1
  90. package/dist-es/psd.d.ts +292 -19
  91. package/dist-es/psd.js.map +1 -1
  92. package/dist-es/psdReader.d.ts +10 -0
  93. package/dist-es/psdReader.js +424 -216
  94. package/dist-es/psdReader.js.map +1 -1
  95. package/dist-es/psdWriter.d.ts +2 -0
  96. package/dist-es/psdWriter.js +373 -113
  97. package/dist-es/psdWriter.js.map +1 -1
  98. package/dist-es/text.d.ts +32 -1
  99. package/dist-es/text.js +75 -6
  100. package/dist-es/text.js.map +1 -1
  101. package/dist-es/utf8.js +0 -3
  102. package/dist-es/utf8.js.map +1 -1
  103. package/package.json +2 -2
  104. package/perf.js +312 -9
  105. package/src/abr.ts +382 -235
  106. package/src/additionalInfo.ts +1071 -482
  107. package/src/ase.ts +66 -61
  108. package/src/descriptor.ts +291 -242
  109. package/src/helpers.ts +266 -3
  110. package/src/imageResources.ts +736 -169
  111. package/src/index.ts +1 -0
  112. package/src/jpeg.ts +8 -8
  113. package/src/psd.ts +271 -32
  114. package/src/psdReader.ts +440 -221
  115. package/src/psdWriter.ts +402 -110
  116. package/src/text.ts +93 -7
  117. package/src/utf8.ts +0 -4
package/perf.js CHANGED
@@ -1,12 +1,315 @@
1
- require('ts-node').register({});
2
- const { readPsd } = require('./src/index.ts');
3
- require('./src/initializeCanvas.ts');
4
- const { readFileSync } = require('fs');
1
+ // Measures decoding speed of all PSD/PSB files in the repository and resources/psd-files
2
+ // and saves the summary to results/perf/perf-<version>[-<tag>].json
3
+ //
4
+ // usage: npx tsc -p tsconfig.json && node perf.js [options]
5
+ // --iterations <n> timed runs per file, after one warmup run (default 3)
6
+ // --filter <text> only measure files with path containing text
7
+ // --tag <name> appended to summary file name (perf-<version>-<name>.json)
8
+ // --compare <file> summary file (or version/tag, e.g. 33.0.0) to compare with,
9
+ // defaults to most recently modified other summary in results/perf
10
+ // --threshold <n> relative change reported as regression/improvement (default 0.1 = 10%)
5
11
 
6
- const buffer = readFileSync('test/read/ignore-stuck/src.psd');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const crypto = require('crypto');
15
+ const os = require('os');
16
+ const v8 = require('v8');
17
+ const vm = require('vm');
7
18
 
8
- const start = performance.now();
9
- const psd = readPsd(buffer);
10
- const end = performance.now();
19
+ const root = __dirname;
20
+ const perfDir = path.join(root, 'results', 'perf');
21
+ const excludedDirs = ['node_modules', 'dist', 'dist-es', 'results', 'resources', 'coverage', 'coverage-remapped', '.git'];
22
+ const readOptions = { useImageData: true, log: () => { } };
23
+ const minDiffMs = 2; // ignore smaller absolute changes as noise
11
24
 
12
- console.log('time', end - start, 'ms', psd.width, psd.height);
25
+ function parseArgs() {
26
+ const args = { iterations: 3, filter: '', tag: '', compare: '', threshold: 0.1 };
27
+ const argv = process.argv.slice(2);
28
+
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const key = argv[i].replace(/^--/, '');
31
+ if (!(key in args)) throw new Error(`Unknown option: ${argv[i]}`);
32
+ const value = argv[++i];
33
+ if (value === undefined) throw new Error(`Missing value for: ${argv[i - 1]}`);
34
+ args[key] = typeof args[key] === 'number' ? +value : value;
35
+ }
36
+
37
+ return args;
38
+ }
39
+
40
+ function findFiles(dir, result = []) {
41
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
42
+ const fullPath = path.join(dir, entry.name);
43
+
44
+ if (entry.isDirectory()) {
45
+ if (!excludedDirs.includes(entry.name)) findFiles(fullPath, result);
46
+ } else if (/\.ps[db]$/i.test(entry.name)) {
47
+ result.push(fullPath);
48
+ }
49
+ }
50
+
51
+ return result;
52
+ }
53
+
54
+ function collectFiles(filter) {
55
+ const repoFiles = findFiles(root).sort();
56
+ const resourcesDir = path.join(root, 'resources', 'psd-files');
57
+ const resourceFiles = fs.existsSync(resourcesDir) ? findFiles(resourcesDir).sort() : [];
58
+ const hashes = new Set();
59
+ const files = [];
60
+
61
+ for (const file of [...repoFiles, ...resourceFiles]) {
62
+ const relative = path.relative(root, file).replace(/\\/g, '/');
63
+ if (filter && !relative.includes(filter)) continue;
64
+
65
+ const hash = crypto.createHash('sha1').update(fs.readFileSync(file)).digest('hex');
66
+ if (hashes.has(hash)) continue; // same file exists in both locations
67
+
68
+ hashes.add(hash);
69
+ files.push({ path: relative, fullPath: file, hash });
70
+ }
71
+
72
+ return files;
73
+ }
74
+
75
+ function checkBuild() {
76
+ const index = path.join(root, 'dist', 'index.js');
77
+
78
+ if (!fs.existsSync(index)) {
79
+ console.error('dist/index.js not found, build first: npx tsc -p tsconfig.json');
80
+ process.exit(1);
81
+ }
82
+
83
+ const buildTime = fs.statSync(index).mtimeMs;
84
+ const stale = fs.readdirSync(path.join(root, 'src'))
85
+ .filter(f => f.endsWith('.ts') && fs.statSync(path.join(root, 'src', f)).mtimeMs > buildTime);
86
+
87
+ if (stale.length) console.warn(`WARNING: dist/ is older than source files (${stale.join(', ')}), rebuild first: npx tsc -p tsconfig.json\n`);
88
+ }
89
+
90
+ function getGC() {
91
+ v8.setFlagsFromString('--expose-gc');
92
+ return vm.runInNewContext('gc');
93
+ }
94
+
95
+ function median(values) {
96
+ const sorted = [...values].sort((a, b) => a - b);
97
+ const mid = sorted.length >> 1;
98
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
99
+ }
100
+
101
+ const round = (value, digits = 2) => +value.toFixed(digits);
102
+
103
+ function formatSize(bytes) {
104
+ if (bytes < 1024) return `${bytes} B`;
105
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
106
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
107
+ }
108
+
109
+ function measure(readPsd, file, iterations, gc) {
110
+ const buffer = fs.readFileSync(file.fullPath);
111
+ const result = { path: file.path, hash: file.hash, size: buffer.byteLength, status: 'ok' };
112
+ const times = [];
113
+
114
+ for (let i = 0; i <= iterations; i++) {
115
+ gc();
116
+ const start = performance.now();
117
+ let psd;
118
+
119
+ try {
120
+ psd = readPsd(buffer, readOptions);
121
+ } catch (e) {
122
+ result.status = 'error';
123
+ result.error = `${e.message || e}`.split('\n')[0].slice(0, 200);
124
+ }
125
+
126
+ const time = performance.now() - start;
127
+ if (i > 0) times.push(time); // first run is warmup
128
+
129
+ if (psd && i === 0) {
130
+ result.width = psd.width;
131
+ result.height = psd.height;
132
+ result.layers = countLayers(psd);
133
+ }
134
+
135
+ psd = undefined;
136
+ }
137
+
138
+ result.min = round(Math.min(...times), 3);
139
+ result.median = round(median(times), 3);
140
+ result.times = times.map(t => round(t, 3));
141
+ return result;
142
+ }
143
+
144
+ function countLayers(parent) {
145
+ let count = 0;
146
+
147
+ for (const child of parent.children || []) {
148
+ count += 1 + countLayers(child);
149
+ }
150
+
151
+ return count;
152
+ }
153
+
154
+ function summarize(results, elapsed) {
155
+ const ok = results.filter(r => r.status === 'ok');
156
+ const totalBytes = ok.reduce((sum, r) => sum + r.size, 0);
157
+ const totalMin = ok.reduce((sum, r) => sum + r.min, 0);
158
+ const totalMedian = ok.reduce((sum, r) => sum + r.median, 0);
159
+
160
+ return {
161
+ files: results.length,
162
+ ok: ok.length,
163
+ failed: results.length - ok.length,
164
+ totalBytes,
165
+ totalMinMs: round(totalMin),
166
+ totalMedianMs: round(totalMedian),
167
+ throughputMBps: round(totalBytes / (1024 * 1024) / (totalMin / 1000)),
168
+ failedTotalMinMs: round(results.filter(r => r.status !== 'ok').reduce((sum, r) => sum + r.min, 0)),
169
+ elapsedMs: round(elapsed),
170
+ };
171
+ }
172
+
173
+ function findCompareFile(compare, outputFile) {
174
+ if (compare) {
175
+ const candidates = [compare, path.join(perfDir, compare), path.join(perfDir, `perf-${compare}.json`)];
176
+ const found = candidates.find(f => fs.existsSync(f) && fs.statSync(f).isFile());
177
+ if (!found) throw new Error(`Summary to compare not found: ${compare}`);
178
+ return found;
179
+ }
180
+
181
+ if (!fs.existsSync(perfDir)) return undefined;
182
+
183
+ return fs.readdirSync(perfDir)
184
+ .filter(f => /^perf-.*\.json$/.test(f))
185
+ .map(f => path.join(perfDir, f))
186
+ .filter(f => path.resolve(f) !== path.resolve(outputFile))
187
+ .sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)[0];
188
+ }
189
+
190
+ function printTable(rows, columns) {
191
+ const widths = columns.map((c, i) => Math.max(c.length, ...rows.map(r => `${r[i]}`.length)));
192
+ const line = cells => cells.map((c, i) => i === 0 ? `${c}`.padEnd(widths[i]) : `${c}`.padStart(widths[i])).join(' ');
193
+ console.log(line(columns));
194
+ for (const row of rows) console.log(line(row));
195
+ }
196
+
197
+ function compareSummaries(current, previousFile, threshold) {
198
+ const previous = JSON.parse(fs.readFileSync(previousFile, 'utf8'));
199
+ const previousByHash = new Map(previous.results.map(r => [r.hash, r]));
200
+ const changes = [];
201
+ const statusChanges = [];
202
+ let currentTotal = 0, previousTotal = 0, logRatioSum = 0;
203
+
204
+ for (const r of current.results) {
205
+ const p = previousByHash.get(r.hash);
206
+ if (!p) continue;
207
+
208
+ if (p.status !== r.status) {
209
+ statusChanges.push(`${r.path}: ${p.status} -> ${r.status}${r.error ? ` (${r.error})` : ''}`);
210
+ continue;
211
+ }
212
+
213
+ if (r.status !== 'ok') continue;
214
+
215
+ currentTotal += r.min;
216
+ previousTotal += p.min;
217
+ logRatioSum += Math.log(Math.max(r.min, 0.001) / Math.max(p.min, 0.001));
218
+ changes.push({ path: r.path, before: p.min, after: r.min, ratio: r.min / Math.max(p.min, 0.001) });
219
+ }
220
+
221
+ const significant = c => Math.abs(c.after - c.before) >= minDiffMs && Math.abs(c.ratio - 1) >= threshold;
222
+ const regressions = changes.filter(c => c.ratio > 1 && significant(c)).sort((a, b) => (b.after - b.before) - (a.after - a.before));
223
+ const improvements = changes.filter(c => c.ratio < 1 && significant(c)).sort((a, b) => (a.after - a.before) - (b.after - b.before));
224
+ const percent = ratio => `${ratio >= 1 ? '+' : ''}${((ratio - 1) * 100).toFixed(1)}%`;
225
+ const toRows = list => list.slice(0, 20).map(c => [c.path, c.before.toFixed(2), c.after.toFixed(2), percent(c.ratio)]);
226
+
227
+ console.log(`\nComparison with ${path.relative(root, previousFile)} (version ${previous.version}${previous.tag ? `-${previous.tag}` : ''}, ${previous.date})`);
228
+ console.log(` common files: ${changes.length}`);
229
+ console.log(` total (min): ${previousTotal.toFixed(1)} ms -> ${currentTotal.toFixed(1)} ms (${percent(currentTotal / Math.max(previousTotal, 0.001))})`);
230
+ console.log(` geometric mean of per-file ratios: ${percent(Math.exp(logRatioSum / Math.max(changes.length, 1)))}`);
231
+
232
+ if (previous.node !== current.node || previous.cpu !== current.cpu) {
233
+ console.log(` NOTE: different environment (${previous.node}, ${previous.cpu})`);
234
+ }
235
+
236
+ if (statusChanges.length) {
237
+ console.log(`\nStatus changes (${statusChanges.length}):`);
238
+ for (const s of statusChanges) console.log(` ${s}`);
239
+ }
240
+
241
+ if (regressions.length) {
242
+ console.log(`\nRegressions (${regressions.length}, showing top 20 by ms):`);
243
+ printTable(toRows(regressions), ['file', 'before', 'after', 'change']);
244
+ }
245
+
246
+ if (improvements.length) {
247
+ console.log(`\nImprovements (${improvements.length}, showing top 20 by ms):`);
248
+ printTable(toRows(improvements), ['file', 'before', 'after', 'change']);
249
+ }
250
+
251
+ if (!regressions.length && !improvements.length && !statusChanges.length) {
252
+ console.log(`\nNo significant changes (threshold ${threshold * 100}%, ${minDiffMs} ms)`);
253
+ }
254
+ }
255
+
256
+ function main() {
257
+ const args = parseArgs();
258
+ checkBuild();
259
+
260
+ require('./dist/initializeCanvas');
261
+ const { readPsd } = require('./dist/index');
262
+ const { version } = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
263
+ const outputFile = path.join(perfDir, `perf-${version}${args.tag ? `-${args.tag}` : ''}${args.filter ? '-filtered' : ''}.json`);
264
+ const gc = getGC();
265
+ const files = collectFiles(args.filter);
266
+ const results = [];
267
+ const start = performance.now();
268
+
269
+ console.log(`ag-psd ${version}, ${files.length} files, ${args.iterations} iterations, node ${process.version}`);
270
+
271
+ for (let i = 0; i < files.length; i++) {
272
+ if (process.stdout.isTTY) {
273
+ process.stdout.write(`\r[${i + 1}/${files.length}] ${files[i].path}`.slice(0, process.stdout.columns - 1).padEnd(process.stdout.columns - 1));
274
+ }
275
+
276
+ results.push(measure(readPsd, files[i], args.iterations, gc));
277
+ }
278
+
279
+ if (process.stdout.isTTY) process.stdout.write('\r'.padEnd(process.stdout.columns - 1) + '\r');
280
+
281
+ const summary = {
282
+ version,
283
+ tag: args.tag || undefined,
284
+ filter: args.filter || undefined,
285
+ date: new Date().toISOString(),
286
+ node: process.version,
287
+ platform: `${os.platform()} ${os.release()} ${os.arch()}`,
288
+ cpu: os.cpus()[0]?.model.trim(),
289
+ iterations: args.iterations,
290
+ readOptions: { useImageData: true },
291
+ total: summarize(results, performance.now() - start),
292
+ results,
293
+ };
294
+
295
+ fs.mkdirSync(perfDir, { recursive: true });
296
+ fs.writeFileSync(outputFile, JSON.stringify(summary, null, 2), 'utf8');
297
+
298
+ const { total } = summary;
299
+ console.log(`\nfiles: ${total.files} (${total.ok} ok, ${total.failed} failed)`);
300
+ console.log(`total size: ${formatSize(total.totalBytes)}`);
301
+ console.log(`total time: ${total.totalMinMs.toFixed(1)} ms (min), ${total.totalMedianMs.toFixed(1)} ms (median)`);
302
+ console.log(`throughput: ${total.throughputMBps} MB/s`);
303
+ console.log(`elapsed: ${(total.elapsedMs / 1000).toFixed(1)} s`);
304
+
305
+ console.log('\nSlowest files:');
306
+ const slowest = results.filter(r => r.status === 'ok').sort((a, b) => b.min - a.min).slice(0, 10);
307
+ printTable(slowest.map(r => [r.path, formatSize(r.size), r.min.toFixed(2), r.median.toFixed(2)]), ['file', 'size', 'min ms', 'median ms']);
308
+
309
+ console.log(`\nSummary saved to ${path.relative(root, outputFile)}`);
310
+
311
+ const compareFile = findCompareFile(args.compare, outputFile);
312
+ if (compareFile) compareSummaries(summary, compareFile, args.threshold);
313
+ }
314
+
315
+ main();