@poe-platform/safe-bash 0.1.838 → 0.1.840
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/dist/mp4-ast/types.d.ts +4 -2
- package/dist/pdf-ast/cos/security.d.ts +10 -0
- package/dist/pdf-ast/edit/retained-attachment-edits.d.ts +4 -0
- package/dist/pdf-ast/edit/retained-graph.d.ts +1 -1
- package/dist/pdf-ast/image.js +1 -1
- package/dist/pdf-ast/index.d.ts +2 -1
- package/dist/pdf-ast/index.js +7 -1
- package/dist/pdf-ast/vendor/pdfjs-fonts.d.mts +8 -0
- package/dist/pdf-ast/vendor/pdfjs-fonts.mjs +28 -0
- package/dist/safe-bash/command-chunks/{chunk-SG2KOOD4.js → chunk-3SAYZFMD.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-FH5VMAPS.js → chunk-45ZH6CNF.js} +20 -4
- package/dist/safe-bash/command-chunks/{chunk-T6OXG7QQ.js → chunk-AAOPVEPF.js} +66 -1
- package/dist/safe-bash/command-chunks/{chunk-46GA576V.js → chunk-CPKVUB7S.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-MBXNG6C3.js → chunk-DLLN3C3Q.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-ZSQCROFE.js → chunk-DM5T5D2K.js} +5 -1
- package/dist/safe-bash/command-chunks/{chunk-3EAZRKI4.js → chunk-G7HQYFIU.js} +10 -1
- package/dist/safe-bash/command-chunks/{chunk-GYKQ3IOV.js → chunk-LMYQMJYY.js} +94 -18
- package/dist/safe-bash/command-chunks/{chunk-DX7KU6YU.js → chunk-N52AHO6S.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-4BSD2RGK.js → chunk-QPKL5GWH.js} +102 -42
- package/dist/safe-bash/command-chunks/{chunk-Z2T7E4ID.js → chunk-R7QLP5RH.js} +1212 -1119
- package/dist/safe-bash/command-chunks/{chunk-RSFKQE2U.js → chunk-T5WFJ44Z.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-77ROD2TR.js → chunk-X3WNWRVT.js} +11 -5
- package/dist/safe-bash/command-chunks/{chunk-TCXM7BSZ.js → chunk-YFMNXPLP.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-DQVOBGI2.js → chunk-YVFQ5I36.js} +7335 -7162
- package/dist/safe-bash-command-exiftool/index.js +25 -1
- package/dist/safe-bash-command-ffmpeg/index.js +174 -41
- package/dist/safe-bash-command-ffprobe/index.js +2 -2
- package/dist/safe-bash-command-ffprobe/media.js +2 -2
- package/dist/safe-bash-command-imagemagick/index.js +504 -111
- package/dist/safe-bash-command-mdq/index.js +2 -0
- package/dist/safe-bash-command-pandoc/citeproc-filters.js +7 -7
- package/dist/safe-bash-command-pandoc/command.js +8 -8
- package/dist/safe-bash-command-pandoc/index.js +51 -51
- package/dist/safe-bash-command-qpdf/index.js +1037 -654
- package/dist/safe-bash-command-qpdf/page-range.d.ts +2 -0
- package/dist/safe-bash-command-soffice/index.js +28 -8
- package/dist/safe-bash-command-sqlite3/btree.d.ts +2 -1
- package/dist/safe-bash-command-sqlite3/engine.d.ts +15 -1
- package/dist/safe-bash-command-sqlite3/index.js +294 -68
- package/dist/safe-bash-command-tar/create.js +2 -2
- package/dist/safe-bash-command-tar/extract.js +2 -2
- package/dist/safe-bash-command-tar/format.js +1 -1
- package/dist/safe-bash-command-tar/index.js +4 -4
- package/dist/safe-bash-command-tar/modes.js +4 -4
- package/dist/safe-bash-command-unzip/index.js +1 -1
- package/dist/safe-bash-command-unzip/unzip.js +1 -1
- package/dist/safe-bash-markdown-engine/commonmark-blocks.d.ts +17 -1
- package/dist/safe-bash-markdown-engine/commonmark-blocks.js +3 -3
- package/dist/safe-bash-markdown-engine/commonmark-inlines.d.ts +1 -1
- package/dist/safe-bash-markdown-engine/commonmark-inlines.js +3 -3
- package/dist/safe-bash-markdown-engine/commonmark-syntax.d.ts +5 -0
- package/dist/safe-bash-markdown-engine/commonmark-syntax.js +5 -1
- package/dist/safe-bash-markdown-engine/gfm-syntax.js +2 -2
- package/dist/safe-bash-markdown-engine/index.js +4 -4
- package/dist/safe-bash-zip-engine/index.js +1 -1
- package/dist/safe-bash-zip-engine/zip/repair.js +1 -1
- package/dist/safe-bash-zip-engine/zip/sfx.js +1 -1
- package/dist/safe-bash-zip-engine/zip-format.d.ts +2 -0
- package/dist/safe-bash-zip-engine/zip-format.js +1 -1
- package/package.json +2 -2
- package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +2 -0
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import "../safe-bash/command-chunks/chunk-65DS4IOB.js";
|
|
2
2
|
|
|
3
|
+
// packages/safe-bash-command-imagemagick/src/quantize.ts
|
|
4
|
+
function* quantizePixelSteps(length, requested) {
|
|
5
|
+
if (!Number.isSafeInteger(requested) || requested < 1) throw new Error("colors must be a positive integer");
|
|
6
|
+
let bins = /* @__PURE__ */ new Map(), mask = 4294967295, precision = 8;
|
|
7
|
+
const key = (data, i) => (data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]) & mask;
|
|
8
|
+
for (let position = 0; position < length; position += 4096) {
|
|
9
|
+
yield;
|
|
10
|
+
const size = Math.min(4096, length - position), data = yield { kind: "read", position, length: size };
|
|
11
|
+
if (!data || data.length !== size) throw new Error("Truncated quantization pixels");
|
|
12
|
+
for (let i = 0; i < size; i += 4) {
|
|
13
|
+
const id = key(data, i);
|
|
14
|
+
let bin = bins.get(id);
|
|
15
|
+
if (!bin) {
|
|
16
|
+
bin = { count: 0, sums: [0, 0, 0, 0], color: [] };
|
|
17
|
+
bins.set(id, bin);
|
|
18
|
+
}
|
|
19
|
+
bin.count++;
|
|
20
|
+
for (let c = 0; c < 4; c++) bin.sums[c] += data[i + c];
|
|
21
|
+
}
|
|
22
|
+
if (bins.size > 65536) {
|
|
23
|
+
const channelMask = 255 << 8 - --precision & 255;
|
|
24
|
+
mask = channelMask * 16843009;
|
|
25
|
+
const reduced = /* @__PURE__ */ new Map();
|
|
26
|
+
let work = 0;
|
|
27
|
+
for (const [id, bin] of bins) {
|
|
28
|
+
if (++work % 1024 === 0) yield;
|
|
29
|
+
const next = id & mask, existing = reduced.get(next);
|
|
30
|
+
if (!existing) reduced.set(next, bin);
|
|
31
|
+
else {
|
|
32
|
+
existing.count += bin.count;
|
|
33
|
+
for (let c = 0; c < 4; c++) existing.sums[c] += bin.sums[c];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
bins = reduced;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const pending = [{ colors: [...bins.values()], budget: requested }];
|
|
40
|
+
while (pending.length) {
|
|
41
|
+
yield;
|
|
42
|
+
const { colors, budget } = pending.pop();
|
|
43
|
+
if (!colors.length) continue;
|
|
44
|
+
const sums = [0, 0, 0, 0], squares = [0, 0, 0, 0];
|
|
45
|
+
let count = 0;
|
|
46
|
+
for (let i = 0; i < colors.length; i++) {
|
|
47
|
+
if (i % 1024 === 0) yield;
|
|
48
|
+
const bin = colors[i];
|
|
49
|
+
count += bin.count;
|
|
50
|
+
for (let c = 0; c < 4; c++) {
|
|
51
|
+
sums[c] += bin.sums[c];
|
|
52
|
+
squares[c] += bin.sums[c] ** 2 / bin.count;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (colors.length <= budget) {
|
|
56
|
+
for (let i = 0; i < colors.length; i++) {
|
|
57
|
+
if (i % 1024 === 0) yield;
|
|
58
|
+
colors[i].color = colors[i].sums.map((value) => Math.round(value / colors[i].count));
|
|
59
|
+
}
|
|
60
|
+
} else if (budget === 1) {
|
|
61
|
+
const average = sums.map((value) => Math.round(value / count));
|
|
62
|
+
for (let i = 0; i < colors.length; i++) {
|
|
63
|
+
if (i % 1024 === 0) yield;
|
|
64
|
+
colors[i].color = average;
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
let axis = 0, variance = -1;
|
|
68
|
+
for (let c = 0; c < 4; c++) {
|
|
69
|
+
const next = squares[c] - sums[c] ** 2 / count;
|
|
70
|
+
if (next >= variance) {
|
|
71
|
+
variance = next;
|
|
72
|
+
axis = c;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
colors.sort((a, b) => a.sums[axis] / a.count - b.sums[axis] / b.count);
|
|
76
|
+
let population = 0, split = 0;
|
|
77
|
+
do {
|
|
78
|
+
population += colors[split++].count;
|
|
79
|
+
} while (population < count / 2 && split < colors.length - 1);
|
|
80
|
+
const leftBudget = Math.min(split, Math.max(1, Math.floor(budget / 2)));
|
|
81
|
+
pending.push({ colors: colors.slice(0, split), budget: leftBudget }, { colors: colors.slice(split), budget: budget - leftBudget });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (let position = 0; position < length; position += 4096) {
|
|
85
|
+
yield;
|
|
86
|
+
const size = Math.min(4096, length - position), source = yield { kind: "read", position, length: size };
|
|
87
|
+
if (!source || source.length !== size) throw new Error("Truncated quantization pixels");
|
|
88
|
+
const data = new Uint8Array(size);
|
|
89
|
+
for (let i = 0; i < size; i += 4) data.set(bins.get(key(source, i)).color, i);
|
|
90
|
+
yield { kind: "write", position, data };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
3
94
|
// packages/safe-bash-command-imagemagick/src/connected-components.ts
|
|
4
95
|
import { yieldTurn as yieldTurn2 } from "../safe-bash-contracts/yield.js";
|
|
5
96
|
|
|
@@ -875,9 +966,10 @@ function* warpPixelSteps(source, plan, background) {
|
|
|
875
966
|
}
|
|
876
967
|
|
|
877
968
|
// packages/safe-bash-command-imagemagick/src/morphology-kernel.ts
|
|
878
|
-
function* morphologyPixelSteps(image,
|
|
969
|
+
function* morphologyPixelSteps(image, rows, mode) {
|
|
879
970
|
const { width, height } = image, histogram = new Float64Array(4 * 256);
|
|
880
|
-
const
|
|
971
|
+
const ry = Math.floor(rows.length / 2);
|
|
972
|
+
const rank = Math.floor(rows.reduce((total, radius) => total + radius * 2 + 1, 0) / 2);
|
|
881
973
|
let work = 0;
|
|
882
974
|
for (let y = 0; y < height; y++) {
|
|
883
975
|
yield;
|
|
@@ -887,14 +979,18 @@ function* morphologyPixelSteps(image, rx, ry, mode) {
|
|
|
887
979
|
for (let local = 0; local < count; local++) {
|
|
888
980
|
const x = left + local;
|
|
889
981
|
for (let phase = 0; phase < (x === 0 ? 1 : 2); phase++) {
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
982
|
+
const delta = x === 0 || phase === 1 ? 1 : -1;
|
|
983
|
+
for (let dy = -ry; dy <= ry; dy++) {
|
|
984
|
+
const rx = rows[dy + ry];
|
|
985
|
+
const first = x === 0 ? -rx : phase === 0 ? x - rx - 1 : x + rx;
|
|
986
|
+
const last = x === 0 ? rx : first;
|
|
987
|
+
for (let column = first; column <= last; column++) {
|
|
988
|
+
if (++work % 4096 === 0) yield;
|
|
989
|
+
const sx = Math.max(0, Math.min(width - 1, column)), sy = Math.max(0, Math.min(height - 1, y + dy));
|
|
990
|
+
const bytes = yield { kind: "read", position: (sy * width + sx) * 4, length: 4 };
|
|
991
|
+
if (!bytes || bytes.length !== 4) throw new Error("Truncated morphology pixels");
|
|
992
|
+
for (let c = 0; c < 4; c++) histogram[c * 256 + bytes[c]] += delta;
|
|
993
|
+
}
|
|
898
994
|
}
|
|
899
995
|
}
|
|
900
996
|
for (let c = 0; c < 4; c++) {
|
|
@@ -1332,6 +1428,7 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1332
1428
|
const height = Math.max(imgA.height, imgB.height);
|
|
1333
1429
|
const totalPixels = Math.max(1, width * height);
|
|
1334
1430
|
let aeCount = 0;
|
|
1431
|
+
let aeMagnitude = 0;
|
|
1335
1432
|
let sumAbs = 0;
|
|
1336
1433
|
let sumSq = 0;
|
|
1337
1434
|
let maxAbs = 0;
|
|
@@ -1341,6 +1438,15 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1341
1438
|
let sumLumSqB = 0;
|
|
1342
1439
|
let sumLumAB = 0;
|
|
1343
1440
|
const hasAlpha = imgA.hasAlpha || imgB.hasAlpha;
|
|
1441
|
+
const nccChannels = metric === "ncc" ? Array.from({ length: imgA.hasAlpha && imgB.hasAlpha ? 4 : 3 }, () => ({
|
|
1442
|
+
rawA: 0,
|
|
1443
|
+
rawB: 0,
|
|
1444
|
+
sumA: 0,
|
|
1445
|
+
sumB: 0,
|
|
1446
|
+
squareA: 0,
|
|
1447
|
+
squareB: 0,
|
|
1448
|
+
product: 0
|
|
1449
|
+
})) : [];
|
|
1344
1450
|
for (let y = 0; y < height; y++) {
|
|
1345
1451
|
if ((y & 511) === 511)
|
|
1346
1452
|
yield;
|
|
@@ -1353,6 +1459,7 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1353
1459
|
dataA = yield { kind: "read", image: 0, position: (y * imgA.width + left) * 4, length };
|
|
1354
1460
|
if (!(dataA instanceof Uint8Array) || dataA.length !== length)
|
|
1355
1461
|
throw new Error("Truncated comparison pixels");
|
|
1462
|
+
dataA = dataA.slice();
|
|
1356
1463
|
}
|
|
1357
1464
|
if (y < imgB.height && left < imgB.width) {
|
|
1358
1465
|
const length = Math.min(count, imgB.width - left) * 4;
|
|
@@ -1375,6 +1482,20 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1375
1482
|
const gB = offB >= 0 ? dataB[offB + 1] : 0;
|
|
1376
1483
|
const bB = offB >= 0 ? dataB[offB + 2] : 0;
|
|
1377
1484
|
const aB = offB >= 0 ? dataB[offB + 3] : 0;
|
|
1485
|
+
for (let channel = 0; channel < nccChannels.length; channel++) {
|
|
1486
|
+
const stats = nccChannels[channel];
|
|
1487
|
+
const rawA = offA >= 0 ? dataA[offA + channel] : 0;
|
|
1488
|
+
const rawB = offB >= 0 ? dataB[offB + channel] : 0;
|
|
1489
|
+
const a = channel === 3 ? rawA : rawA * aA / 255;
|
|
1490
|
+
const b = channel === 3 ? rawB : rawB * aB / 255;
|
|
1491
|
+
stats.rawA += rawA;
|
|
1492
|
+
stats.rawB += rawB;
|
|
1493
|
+
stats.sumA += a;
|
|
1494
|
+
stats.sumB += b;
|
|
1495
|
+
stats.squareA += a * a;
|
|
1496
|
+
stats.squareB += b * b;
|
|
1497
|
+
stats.product += a * b;
|
|
1498
|
+
}
|
|
1378
1499
|
const dr = Math.abs(rA - rB);
|
|
1379
1500
|
const dg = Math.abs(gA - gB);
|
|
1380
1501
|
const db = Math.abs(bA - bB);
|
|
@@ -1384,6 +1505,8 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1384
1505
|
const pdr = Math.abs(rA * alphaA - rB * alphaB);
|
|
1385
1506
|
const pdg = Math.abs(gA * alphaA - gB * alphaB);
|
|
1386
1507
|
const pdb = Math.abs(bA * alphaA - bB * alphaB);
|
|
1508
|
+
const aeR = hasAlpha ? pdr : dr, aeG = hasAlpha ? pdg : dg, aeB = hasAlpha ? pdb : db;
|
|
1509
|
+
aeMagnitude += (aeR > fuzz ? aeR : 0) + (aeG > fuzz ? aeG : 0) + (aeB > fuzz ? aeB : 0) + (hasAlpha && da > fuzz ? da : 0);
|
|
1387
1510
|
const maxDelta = hasAlpha ? Math.max(pdr, pdg, pdb, da) : Math.max(dr, dg, db);
|
|
1388
1511
|
if (!inBoundsA || !inBoundsB || maxDelta > fuzz) {
|
|
1389
1512
|
aeCount++;
|
|
@@ -1445,11 +1568,10 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1445
1568
|
const c1 = 1e-4;
|
|
1446
1569
|
const c2 = 9e-4;
|
|
1447
1570
|
const ssim = (2 * muA * muB + c1) * (2 * covAB + c2) / ((muA * muA + muB * muB + c1) * (varA + varB + c2));
|
|
1448
|
-
const ncc = varA === 0 && varB === 0 ? 1 : covAB / (Math.sqrt(varA * varB) || 1);
|
|
1449
1571
|
let metricStr;
|
|
1450
1572
|
switch (metric) {
|
|
1451
1573
|
case "ae":
|
|
1452
|
-
metricStr =
|
|
1574
|
+
metricStr = `${formatSignificantMetric(aeMagnitude / (numCh * 255))} (${formatSignificantMetric(aeMagnitude / (totalPixels * numCh * 255))})`;
|
|
1453
1575
|
break;
|
|
1454
1576
|
case "mae":
|
|
1455
1577
|
metricStr = `${formatMetricNum(maeNorm * 65535)} (${formatMetricNum(maeNorm)})`;
|
|
@@ -1461,23 +1583,37 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1461
1583
|
metricStr = `${formatMetricNum(paeNorm * 65535)} (${formatMetricNum(paeNorm)})`;
|
|
1462
1584
|
break;
|
|
1463
1585
|
case "psnr":
|
|
1464
|
-
metricStr = mseNorm === 0 ? "
|
|
1586
|
+
metricStr = mseNorm === 0 ? "120 (1)" : formatMetricNum(10 * Math.log10(1 / mseNorm));
|
|
1465
1587
|
break;
|
|
1466
|
-
case "ssim":
|
|
1467
|
-
|
|
1588
|
+
case "ssim": {
|
|
1589
|
+
const distortion = yield* compareSsimSteps(imgA, imgB);
|
|
1590
|
+
metricStr = `${formatSignificantMetric(distortion * 65535)} (${formatSignificantMetric(distortion)})`;
|
|
1468
1591
|
break;
|
|
1592
|
+
}
|
|
1469
1593
|
case "dssim":
|
|
1470
1594
|
metricStr = formatMetricNum((1 - ssim) / 2);
|
|
1471
1595
|
break;
|
|
1472
|
-
case "ncc":
|
|
1473
|
-
|
|
1596
|
+
case "ncc": {
|
|
1597
|
+
let varianceA = 0, varianceB = 0, covariance = 0;
|
|
1598
|
+
for (const stats of nccChannels) {
|
|
1599
|
+
const meanA = stats.rawA / (imgA.width * imgA.height);
|
|
1600
|
+
const meanB = stats.rawB / (imgB.width * imgB.height);
|
|
1601
|
+
varianceA += Math.max(0, stats.squareA - 2 * meanA * stats.sumA + totalPixels * meanA * meanA);
|
|
1602
|
+
varianceB += Math.max(0, stats.squareB - 2 * meanB * stats.sumB + totalPixels * meanB * meanB);
|
|
1603
|
+
covariance += stats.product - meanA * stats.sumB - meanB * stats.sumA + totalPixels * meanA * meanB;
|
|
1604
|
+
}
|
|
1605
|
+
const denominator = Math.sqrt(varianceA) * Math.sqrt(varianceB);
|
|
1606
|
+
const similarity = denominator === 0 ? 0 : covariance / denominator;
|
|
1607
|
+
const distortion = Math.abs(1 - similarity) < 2e-12 ? 0 : (1 - similarity) / 2;
|
|
1608
|
+
metricStr = `${formatSignificantMetric(distortion * 65535)} (${formatSignificantMetric(distortion)})`;
|
|
1474
1609
|
break;
|
|
1610
|
+
}
|
|
1475
1611
|
case "rmse":
|
|
1476
1612
|
default:
|
|
1477
1613
|
metricStr = `${formatMetricNum(rmseNorm * 65535)} (${formatMetricNum(rmseNorm)})`;
|
|
1478
1614
|
break;
|
|
1479
1615
|
}
|
|
1480
|
-
const exitCode = dissimilarityThreshold !== void 0 ? rmseNorm > dissimilarityThreshold ? 1 : 0 : aeCount > 0 ? 1 : 0;
|
|
1616
|
+
const exitCode = metric === "psnr" && mseNorm === 0 ? 1 : dissimilarityThreshold !== void 0 ? rmseNorm > dissimilarityThreshold ? 1 : 0 : aeCount > 0 ? 1 : 0;
|
|
1481
1617
|
return { width, height, metric: metricStr, exitCode };
|
|
1482
1618
|
}
|
|
1483
1619
|
function formatMetricNum(n) {
|
|
@@ -1488,6 +1624,65 @@ function formatMetricNum(n) {
|
|
|
1488
1624
|
const fixed = n.toFixed(6).replace(/\.?0+$/, "");
|
|
1489
1625
|
return fixed === "-0" ? "0" : fixed;
|
|
1490
1626
|
}
|
|
1627
|
+
function formatSignificantMetric(n) {
|
|
1628
|
+
const rounded = Number(n.toPrecision(6));
|
|
1629
|
+
if (rounded === 0 || rounded >= 1e-4 && rounded < 1e6)
|
|
1630
|
+
return String(rounded);
|
|
1631
|
+
const [mantissa, exponent] = rounded.toExponential().split("e");
|
|
1632
|
+
return `${mantissa}e${exponent[0]}${exponent.slice(1).padStart(2, "0")}`;
|
|
1633
|
+
}
|
|
1634
|
+
function* compareSsimSteps(imgA, imgB) {
|
|
1635
|
+
const width = Math.max(imgA.width, imgB.width), height = Math.max(imgA.height, imgB.height);
|
|
1636
|
+
const channels = imgA.hasAlpha && imgB.hasAlpha ? 4 : 3;
|
|
1637
|
+
const weights = Array.from({ length: 11 }, (_, i) => Math.exp(-((i - 5) ** 2) / (2 * 1.5 ** 2)));
|
|
1638
|
+
const weightSum = weights.reduce((sum, value) => sum + value, 0);
|
|
1639
|
+
for (let i = 0; i < weights.length; i++)
|
|
1640
|
+
weights[i] = weights[i] / weightSum;
|
|
1641
|
+
let similarity = 0;
|
|
1642
|
+
for (let y = 0; y < height; y++) {
|
|
1643
|
+
for (let left = 0; left < width; left += 1014) {
|
|
1644
|
+
yield;
|
|
1645
|
+
const count = Math.min(1014, width - left);
|
|
1646
|
+
const rows = [[], []];
|
|
1647
|
+
const starts = [];
|
|
1648
|
+
for (const image of [0, 1]) {
|
|
1649
|
+
const info = image === 0 ? imgA : imgB;
|
|
1650
|
+
const start = Math.min(info.width - 1, Math.max(0, left - 5));
|
|
1651
|
+
const end = Math.min(info.width, left + count + 5);
|
|
1652
|
+
starts[image] = start;
|
|
1653
|
+
for (let v = -5; v <= 5; v++) {
|
|
1654
|
+
const row = Math.max(0, Math.min(info.height - 1, y + v));
|
|
1655
|
+
const length = (end - start) * 4;
|
|
1656
|
+
const data = yield { kind: "read", image, position: (row * info.width + start) * 4, length };
|
|
1657
|
+
if (!(data instanceof Uint8Array) || data.length !== length)
|
|
1658
|
+
throw new Error("Truncated comparison pixels");
|
|
1659
|
+
rows[image].push(data.slice());
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
for (let x = left; x < left + count; x++) {
|
|
1663
|
+
for (let channel = 0; channel < channels; channel++) {
|
|
1664
|
+
let muA = 0, muB = 0, squareA = 0, squareB = 0, product = 0;
|
|
1665
|
+
for (let v = 0; v < 11; v++) {
|
|
1666
|
+
for (let u = 0; u < 11; u++) {
|
|
1667
|
+
const offsetA = (Math.max(0, Math.min(imgA.width - 1, x + u - 5)) - starts[0]) * 4 + channel;
|
|
1668
|
+
const offsetB = (Math.max(0, Math.min(imgB.width - 1, x + u - 5)) - starts[1]) * 4 + channel;
|
|
1669
|
+
const a = rows[0][v][offsetA] / 255, b = rows[1][v][offsetB] / 255;
|
|
1670
|
+
const weight = weights[u] * weights[v];
|
|
1671
|
+
muA += weight * a;
|
|
1672
|
+
muB += weight * b;
|
|
1673
|
+
squareA += weight * a * a;
|
|
1674
|
+
squareB += weight * b * b;
|
|
1675
|
+
product += weight * a * b;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
similarity += (2 * muA * muB + 1e-4) * (2 * (product - muA * muB) + 9e-4) / ((muA * muA + muB * muB + 1e-4) * (squareA - muA * muA + squareB - muB * muB + 9e-4));
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
const distortion = (1 - similarity / (width * height * channels)) / 2;
|
|
1684
|
+
return Math.abs(distortion) < 1e-12 ? 0 : distortion;
|
|
1685
|
+
}
|
|
1491
1686
|
|
|
1492
1687
|
// packages/safe-bash-command-imagemagick/src/identify-file.ts
|
|
1493
1688
|
import { readImageMetadataFromSource as readImageMetadataFromSource2, tryPdfMetadata as tryPdfMetadata2, computeStoredImageStats, readImageMetadata as readImageMetadata2, decodeImage as decodeImage2, computeImageStatsSteps, UnsupportedStoredResource as UnsupportedStoredResource3, UnsupportedImageFormat as UnsupportedImageFormat3 } from "../image-ast/portable.js";
|
|
@@ -1584,7 +1779,7 @@ import {
|
|
|
1584
1779
|
} from "../safe-bash-contracts/command.js";
|
|
1585
1780
|
import { readBytes as readBytes2, writeBytes as writeBytes2 } from "../safe-bash-contracts/io.js";
|
|
1586
1781
|
import { createOutputOperation } from "../safe-bash-contracts/output.js";
|
|
1587
|
-
import { readImageMetadataFromSource as readImageMetadataFromSource3, decodeImageToStorage as decodeImageToStorage2, transformStoredImage, UnsupportedStoredResource as UnsupportedStoredResource4, decodeImage as decodeImage3, decodePngToCanvas, FONT_5X7, encodeImage, readImageMetadata as readImageMetadata3, parseColor as baseParseColor, applyExifOrientationSteps, blurImageSteps, compositeImageSteps, computeImageStatsSteps as computeImageStatsSteps2, ensureAlphaImageSteps, extendImageSteps, extractChannelImageSteps, extractImageSteps, flattenImageSteps, flipImageSteps, flopImageSteps,
|
|
1782
|
+
import { readImageMetadataFromSource as readImageMetadataFromSource3, decodeImageToStorage as decodeImageToStorage2, transformStoredImage, UnsupportedStoredResource as UnsupportedStoredResource4, decodeImage as decodeImage3, decodePngToCanvas, FONT_5X7, encodeImage, readImageMetadata as readImageMetadata3, parseColor as baseParseColor, applyExifOrientationSteps, blurImageSteps, compositeImageSteps, computeImageStatsSteps as computeImageStatsSteps2, ensureAlphaImageSteps, extendImageSteps, extractChannelImageSteps, extractImageSteps, flattenImageSteps, flipImageSteps, flopImageSteps, grayscaleImageSteps, linearImageSteps, medianImageSteps, negateImageSteps, removeAlphaImageSteps, resizeImageSteps, rotateImageSteps, sharpenImageSteps, thresholdImageSteps, trimImageSteps } from "../image-ast/portable.js";
|
|
1588
1783
|
var X11_NAMED_COLORS = {
|
|
1589
1784
|
aliceblue: [240, 248, 255, 255],
|
|
1590
1785
|
antiquewhite: [250, 235, 215, 255],
|
|
@@ -2001,6 +2196,10 @@ function magickMorphologyPlan(methodRaw, kernelSpec) {
|
|
|
2001
2196
|
rx = Math.max(1, Math.round(Number(sizePart)));
|
|
2002
2197
|
ry = rx;
|
|
2003
2198
|
}
|
|
2199
|
+
const disk = (colonIdx < 0 ? kernelSpec : kernelSpec.slice(0, colonIdx)).toLowerCase() === "disk";
|
|
2200
|
+
const radius = Number(sizePart) || 4.5;
|
|
2201
|
+
if (disk) ry = Math.max(0, Math.floor(radius));
|
|
2202
|
+
const rows = Array.from({ length: ry * 2 + 1 }, (_, row) => disk ? Math.floor(Math.sqrt(radius * radius - (row - ry) ** 2)) : rx);
|
|
2004
2203
|
const filter = (source, mode) => ({ source, mode });
|
|
2005
2204
|
const diff = (a, b) => ({ mode: "diff", a, b });
|
|
2006
2205
|
let steps;
|
|
@@ -2037,7 +2236,7 @@ function magickMorphologyPlan(methodRaw, kernelSpec) {
|
|
|
2037
2236
|
default:
|
|
2038
2237
|
steps = [filter(0, "max")];
|
|
2039
2238
|
}
|
|
2040
|
-
return {
|
|
2239
|
+
return { rows, steps };
|
|
2041
2240
|
}
|
|
2042
2241
|
function* subtractMagickPixelSteps(a, b) {
|
|
2043
2242
|
const result = new Uint8Array(a.length);
|
|
@@ -2055,7 +2254,7 @@ function* applyMagickMorphology4ChSteps(img, methodRaw, kernelSpec, signal) {
|
|
|
2055
2254
|
images.push({ ...a, data: yield* subtractMagickPixelSteps(a.data, b.data) });
|
|
2056
2255
|
} else {
|
|
2057
2256
|
const source = images[step.source];
|
|
2058
|
-
images.push(yield* applyMagickRasterSteps(source, morphologyPixelSteps(source, plan.
|
|
2257
|
+
images.push(yield* applyMagickRasterSteps(source, morphologyPixelSteps(source, plan.rows, step.mode), signal));
|
|
2059
2258
|
}
|
|
2060
2259
|
}
|
|
2061
2260
|
return images.at(-1);
|
|
@@ -2168,7 +2367,9 @@ function* applyMagickEvaluateSequenceSteps(stack, opRaw, signal) {
|
|
|
2168
2367
|
}
|
|
2169
2368
|
function parseMagickConvolveKernel(spec) {
|
|
2170
2369
|
const afterColon = spec.includes(":") ? spec.slice(spec.indexOf(":") + 1) : spec;
|
|
2171
|
-
|
|
2370
|
+
const kernel = afterColon.trim().split(/[\s,]+/).filter((s) => s.length > 0).map(Number).filter(Number.isFinite);
|
|
2371
|
+
const sum = kernel.reduce((total, weight) => total + weight, 0);
|
|
2372
|
+
return sum === 0 ? kernel : kernel.map((weight) => weight / sum);
|
|
2172
2373
|
}
|
|
2173
2374
|
function* applyMagickCustomConvolveSteps(img, spec, signal) {
|
|
2174
2375
|
const kernel = parseMagickConvolveKernel(spec);
|
|
@@ -2804,7 +3005,9 @@ function* applyMagickOpaqueSteps(img, target, replacement, fuzz, invert, signal)
|
|
|
2804
3005
|
const dr = Math.abs(out[i] - target.r);
|
|
2805
3006
|
const dg = Math.abs(out[i + 1] - target.g);
|
|
2806
3007
|
const db = Math.abs(out[i + 2] - target.b);
|
|
2807
|
-
const
|
|
3008
|
+
const alpha = out[i + 3];
|
|
3009
|
+
const distance = (alpha - target.a) ** 2 + (dr ** 2 + dg ** 2 + db ** 2) * (alpha / 255) * (target.a / 255) / 3;
|
|
3010
|
+
const matched = distance <= fuzz ** 2;
|
|
2808
3011
|
if (invert ? !matched : matched) {
|
|
2809
3012
|
out[i] = replacement.r;
|
|
2810
3013
|
out[i + 1] = replacement.g;
|
|
@@ -2823,7 +3026,9 @@ function* applyMagickTransparentSteps(img, target, fuzz, invert, signal) {
|
|
|
2823
3026
|
const dr = Math.abs(out[i] - target.r);
|
|
2824
3027
|
const dg = Math.abs(out[i + 1] - target.g);
|
|
2825
3028
|
const db = Math.abs(out[i + 2] - target.b);
|
|
2826
|
-
const
|
|
3029
|
+
const alpha = out[i + 3];
|
|
3030
|
+
const distance = (alpha - target.a) ** 2 + (dr ** 2 + dg ** 2 + db ** 2) * (alpha / 255) * (target.a / 255) / 3;
|
|
3031
|
+
const matched = distance <= fuzz ** 2;
|
|
2827
3032
|
if (invert ? !matched : matched) {
|
|
2828
3033
|
out[i + 3] = 0;
|
|
2829
3034
|
}
|
|
@@ -2835,7 +3040,7 @@ function* applyMagickEvaluateSteps(img, opRaw, valStr, channels, signal) {
|
|
|
2835
3040
|
const op = opRaw.toLowerCase().replace(/[-_]/g, "");
|
|
2836
3041
|
const isPct = valStr.trim().endsWith("%");
|
|
2837
3042
|
const rawNum = parseFloat(valStr);
|
|
2838
|
-
const vByte = isPct ? rawNum / 100 * 255 : rawNum
|
|
3043
|
+
const vByte = isPct ? rawNum / 100 * 255 : rawNum / 257;
|
|
2839
3044
|
const vFactor = isPct ? rawNum / 100 : rawNum;
|
|
2840
3045
|
const out = new Uint8Array(img.data);
|
|
2841
3046
|
const mask = [channels.r, channels.g, channels.b, channels.a];
|
|
@@ -3592,14 +3797,66 @@ function* applyMagickPosterizeSteps(img, levelsRaw, signal) {
|
|
|
3592
3797
|
const levels = Math.max(2, Math.min(256, Math.round(levelsRaw)));
|
|
3593
3798
|
const step = 255 / (levels - 1);
|
|
3594
3799
|
const out = new Uint8Array(img.data);
|
|
3800
|
+
let palette;
|
|
3595
3801
|
for (let i = 0; i < out.length; i += 4) {
|
|
3596
3802
|
if (++cooperativeWork % 65536 === 0)
|
|
3597
3803
|
yield;
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3804
|
+
if (img.hasAlpha && out[i + 3] < 255 && levels <= 16) {
|
|
3805
|
+
if (!palette) {
|
|
3806
|
+
palette = { children: [] };
|
|
3807
|
+
for (let a = 0; a < levels; a++) for (let b = 0; b < levels; b++) for (let g = 0; g < levels; g++) for (let r = 0; r < levels; r++) {
|
|
3808
|
+
if (++cooperativeWork % 1024 === 0) {
|
|
3809
|
+
signal?.throwIfAborted();
|
|
3810
|
+
yield;
|
|
3811
|
+
}
|
|
3812
|
+
const color = a === 0 ? [0, 0, 0, 0] : [r * step, g * step, b * step, a * step];
|
|
3813
|
+
const bytes2 = color.map((value, channel) => Math.round(channel === 3 ? value : value * color[3] / 255));
|
|
3814
|
+
let node2 = palette;
|
|
3815
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
3816
|
+
const id = bytes2[0] >> bit & 1 | (bytes2[1] >> bit & 1) << 1 | (bytes2[2] >> bit & 1) << 2 | (bytes2[3] >> bit & 1) << 3;
|
|
3817
|
+
node2 = node2.children[id] ??= { children: [] };
|
|
3818
|
+
}
|
|
3819
|
+
node2.color = color;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
const alpha = out[i + 3] / 255;
|
|
3823
|
+
const target = [out[i] * alpha, out[i + 1] * alpha, out[i + 2] * alpha, out[i + 3]];
|
|
3824
|
+
const bytes = target.map(Math.round);
|
|
3825
|
+
let node = palette, parent = palette;
|
|
3826
|
+
for (let bit = 7; bit > 0; bit--) {
|
|
3827
|
+
const id = bytes[0] >> bit & 1 | (bytes[1] >> bit & 1) << 1 | (bytes[2] >> bit & 1) << 2 | (bytes[3] >> bit & 1) << 3;
|
|
3828
|
+
const child = node.children[id];
|
|
3829
|
+
if (!child) break;
|
|
3830
|
+
parent = node;
|
|
3831
|
+
node = child;
|
|
3832
|
+
}
|
|
3833
|
+
let distance = Infinity, closest = [0, 0, 0, 0];
|
|
3834
|
+
const pending = [parent];
|
|
3835
|
+
while (pending.length) {
|
|
3836
|
+
if (++cooperativeWork % 1024 === 0) {
|
|
3837
|
+
signal?.throwIfAborted();
|
|
3838
|
+
yield;
|
|
3839
|
+
}
|
|
3840
|
+
const candidate = pending.pop();
|
|
3841
|
+
for (let child = candidate.children.length - 1; child >= 0; child--) {
|
|
3842
|
+
if (candidate.children[child]) pending.push(candidate.children[child]);
|
|
3843
|
+
}
|
|
3844
|
+
if (!candidate.color) continue;
|
|
3845
|
+
const color = candidate.color, candidateAlpha = color[3] / 255;
|
|
3846
|
+
let error = (color[3] - target[3]) ** 2;
|
|
3847
|
+
for (let channel = 0; channel < 3; channel++) error += (color[channel] * candidateAlpha - target[channel] * alpha) ** 2;
|
|
3848
|
+
if (error <= distance) {
|
|
3849
|
+
distance = error;
|
|
3850
|
+
closest = color;
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
for (let channel = 0; channel < 4; channel++) out[i + channel] = clampByteVal(closest[channel]);
|
|
3854
|
+
} else {
|
|
3855
|
+
for (let channel = 0; channel < 4; channel++) out[i + channel] = clampByteVal(Math.round(out[i + channel] / 255 * (levels - 1)) * step);
|
|
3856
|
+
}
|
|
3601
3857
|
}
|
|
3602
|
-
|
|
3858
|
+
const { data16: ignored, ...metadata } = img;
|
|
3859
|
+
return { ...metadata, data: out };
|
|
3603
3860
|
}
|
|
3604
3861
|
function* applyMagickRasterSteps(img, steps, signal, dimensions = img, sources = [img]) {
|
|
3605
3862
|
const data = new Uint8Array(dimensions.width * dimensions.height * 4);
|
|
@@ -3755,7 +4012,7 @@ function parseCompose(raw) {
|
|
|
3755
4012
|
function magickCompositeMode(mode) {
|
|
3756
4013
|
let name = "";
|
|
3757
4014
|
for (const character of mode.toLowerCase()) if (character !== "-" && character !== "_" && character.trim()) name += character;
|
|
3758
|
-
return { name, delegated: ["over", "srcover", "dstover", "destover", "plus", "add", "lineardodge", "colordodge", "colorburn", "
|
|
4015
|
+
return { name, delegated: ["over", "srcover", "dstover", "destover", "plus", "add", "lineardodge", "colordodge", "colorburn", "saturate"].includes(name) };
|
|
3759
4016
|
}
|
|
3760
4017
|
function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeArgs, signal) {
|
|
3761
4018
|
let cooperativeWork = 0;
|
|
@@ -3823,7 +4080,7 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3823
4080
|
out[dIdx] = sr;
|
|
3824
4081
|
out[dIdx + 1] = sg;
|
|
3825
4082
|
out[dIdx + 2] = sb;
|
|
3826
|
-
out[dIdx + 3] = saByte;
|
|
4083
|
+
out[dIdx + 3] = dst.hasAlpha ? saByte : daByte;
|
|
3827
4084
|
continue;
|
|
3828
4085
|
}
|
|
3829
4086
|
if (norm === "dst" || norm === "dest") {
|
|
@@ -3852,11 +4109,24 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3852
4109
|
out[dIdx + 2] = sb;
|
|
3853
4110
|
continue;
|
|
3854
4111
|
}
|
|
4112
|
+
if (norm === "softlight") {
|
|
4113
|
+
const alpha = sa + da - sa * da;
|
|
4114
|
+
for (let c = 0; c < 3; c++) {
|
|
4115
|
+
const sc = src.data[sIdx + c] / 255 * sa;
|
|
4116
|
+
const dc = dst.data[dIdx + c] / 255 * da;
|
|
4117
|
+
const blend = sc <= 0.5 ? dc - (1 - 2 * sc) * dc * (1 - dc) : dc + (2 * sc - 1) * ((dc > 0.25 ? Math.sqrt(dc) : ((16 * dc - 12) * dc + 4) * dc) - dc);
|
|
4118
|
+
out[dIdx + c] = alpha === 0 ? 0 : clampByteVal(255 * (dc * sa + da * blend + sc * (1 - da) + dc * (1 - sa)) / alpha);
|
|
4119
|
+
}
|
|
4120
|
+
out[dIdx + 3] = clampByteVal(alpha * 255);
|
|
4121
|
+
continue;
|
|
4122
|
+
}
|
|
3855
4123
|
if (norm === "difference") {
|
|
4124
|
+
if (saByte === 0)
|
|
4125
|
+
continue;
|
|
3856
4126
|
out[dIdx] = Math.abs(sr - dr);
|
|
3857
4127
|
out[dIdx + 1] = Math.abs(sg - dg);
|
|
3858
4128
|
out[dIdx + 2] = Math.abs(sb - db);
|
|
3859
|
-
out[dIdx + 3] = Math.abs(saByte - daByte);
|
|
4129
|
+
out[dIdx + 3] = dst.hasAlpha ? Math.abs(saByte - daByte) : daByte;
|
|
3860
4130
|
continue;
|
|
3861
4131
|
}
|
|
3862
4132
|
if (norm === "in" || norm === "srcin") {
|
|
@@ -3910,10 +4180,10 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3910
4180
|
out[dIdx] = clampByteVal(sr * sa * (1 - da) + dr * da * (1 - sa));
|
|
3911
4181
|
out[dIdx + 1] = clampByteVal(sg * sa * (1 - da) + dg * da * (1 - sa));
|
|
3912
4182
|
out[dIdx + 2] = clampByteVal(sb * sa * (1 - da) + db * da * (1 - sa));
|
|
3913
|
-
out[dIdx + 3] = clampByteVal(outA * 255);
|
|
4183
|
+
out[dIdx + 3] = dst.hasAlpha ? clampByteVal(outA * 255) : daByte;
|
|
3914
4184
|
continue;
|
|
3915
4185
|
}
|
|
3916
|
-
if (norm === "multiply" || norm === "screen" || norm === "overlay" || norm === "darken" || norm === "lighten" || norm === "exclusion") {
|
|
4186
|
+
if (norm === "multiply" || norm === "screen" || norm === "overlay" || norm === "hardlight" || norm === "darken" || norm === "lighten" || norm === "exclusion") {
|
|
3917
4187
|
const gamma = sa + da - sa * da;
|
|
3918
4188
|
const sChan = [sr / 255, sg / 255, sb / 255];
|
|
3919
4189
|
const dChan = [dr / 255, dg / 255, db / 255];
|
|
@@ -3937,10 +4207,12 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3937
4207
|
f = sc * dc;
|
|
3938
4208
|
else if (norm === "screen")
|
|
3939
4209
|
f = sc + dc - sc * dc;
|
|
4210
|
+
else if (norm === "hardlight")
|
|
4211
|
+
f = sc <= 0.5 ? 2 * sc * dc : 1 - 2 * (1 - sc) * (1 - dc);
|
|
3940
4212
|
else
|
|
3941
4213
|
f = dc <= 0.5 ? 2 * sc * dc : 1 - 2 * (1 - sc) * (1 - dc);
|
|
3942
4214
|
const comp = f * sa * da + sc * sa * (1 - da) + dc * da * (1 - sa);
|
|
3943
|
-
const unpremul = gamma > 1e-6 && norm === "overlay" ? comp / gamma : comp;
|
|
4215
|
+
const unpremul = gamma > 1e-6 && (norm === "overlay" || norm === "hardlight") ? comp / gamma : comp;
|
|
3944
4216
|
out[dIdx + c] = clampByteVal(unpremul * 255);
|
|
3945
4217
|
}
|
|
3946
4218
|
}
|
|
@@ -3964,7 +4236,7 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3964
4236
|
const clipped = yield* extractImageSteps(src, { left: sx0, top: sy0, width: sx1 - sx0, height: sy1 - sy0 });
|
|
3965
4237
|
return yield* compositeImageSteps(dst, [rgbaToCompositeLayer(clipped, Math.max(0, ox), Math.max(0, oy), parseCompose(modeRaw))]);
|
|
3966
4238
|
}
|
|
3967
|
-
return { ...dst, data: out, hasAlpha: true };
|
|
4239
|
+
return { ...dst, data: out, hasAlpha: ["difference", "src", "source", "copy", "xor"].includes(norm) ? dst.hasAlpha : true };
|
|
3968
4240
|
}
|
|
3969
4241
|
function rgbaToCss(c) {
|
|
3970
4242
|
if (c.a === 0) return "none";
|
|
@@ -4540,21 +4812,24 @@ function* appendStackImagesSteps(stack, vertical, state) {
|
|
|
4540
4812
|
const totalW = vertical ? Math.max(...stack.map((im) => im.width)) : stack.reduce((acc, im) => acc + im.width, 0);
|
|
4541
4813
|
const totalH = vertical ? stack.reduce((acc, im) => acc + im.height, 0) : Math.max(...stack.map((im) => im.height));
|
|
4542
4814
|
const canvas = yield* createSolidRgbaImageSteps(totalW, totalH, state.background);
|
|
4815
|
+
let hasAlpha = canvas.hasAlpha;
|
|
4543
4816
|
let cursor = 0;
|
|
4544
4817
|
for (const im of stack) {
|
|
4545
4818
|
if (++work % 16384 === 0)
|
|
4546
4819
|
yield;
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4820
|
+
const off = resolveGravityOffset(vertical ? totalW - im.width : 0, vertical ? 0 : totalH - im.height, state.gravity);
|
|
4821
|
+
const left = vertical ? off.left : cursor, top = vertical ? cursor : off.top;
|
|
4822
|
+
for (let y = 0; y < im.height; y++) {
|
|
4823
|
+
for (let x = 0; x < im.width; x += 4096) {
|
|
4824
|
+
yield;
|
|
4825
|
+
const start = (y * im.width + x) * 4;
|
|
4826
|
+
canvas.data.set(im.data.subarray(start, start + Math.min(4096, im.width - x) * 4), ((top + y) * totalW + left + x) * 4);
|
|
4827
|
+
}
|
|
4555
4828
|
}
|
|
4829
|
+
hasAlpha ||= im.hasAlpha;
|
|
4830
|
+
cursor += vertical ? im.height : im.width;
|
|
4556
4831
|
}
|
|
4557
|
-
return canvas;
|
|
4832
|
+
return { ...canvas, hasAlpha };
|
|
4558
4833
|
}
|
|
4559
4834
|
function inferOutputFormat(spec, fallback = "png") {
|
|
4560
4835
|
const prefixMatch = /^([a-zA-Z0-9]+):(.*)$/.exec(spec);
|
|
@@ -5092,6 +5367,7 @@ function* accumulateMagickHistogram(data, histogram) {
|
|
|
5092
5367
|
for (let i = 0; i < data.length; i += 4) {
|
|
5093
5368
|
if (i && i % 65536 === 0) yield;
|
|
5094
5369
|
for (let channel = 0; channel < 4; channel++) histogram[channel][data[i + channel]]++;
|
|
5370
|
+
if (histogram[4]) histogram[4][Math.round(257 * (0.212656 * data[i] + 0.715158 * data[i + 1] + 0.072186 * data[i + 2]))]++;
|
|
5095
5371
|
}
|
|
5096
5372
|
}
|
|
5097
5373
|
function* applyMagickLookup(image, lookup) {
|
|
@@ -5113,9 +5389,9 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5113
5389
|
const token = tokens[start];
|
|
5114
5390
|
if (!["-auto-level", "-auto-gamma", "-normalize", "-contrast-stretch", "-linear-stretch", "-equalize"].includes(token ?? "")) return;
|
|
5115
5391
|
let end = start;
|
|
5116
|
-
const argument = token === "-
|
|
5392
|
+
const argument = token === "-contrast-stretch" || token === "-linear-stretch" ? tokens[++end] ?? "0%x0%" : "";
|
|
5117
5393
|
const independent = state.channelExplicit, channels = independent ? state.channels : { r: true, g: true, b: true, a: false };
|
|
5118
|
-
return { end, lookup(histogram, image) {
|
|
5394
|
+
return { end, luminance: token === "-linear-stretch" || (token === "-normalize" || token === "-contrast-stretch" || token === "-equalize") && !independent, lookup(histogram, image) {
|
|
5119
5395
|
const lookup = Array.from({ length: 4 }, () => Uint8Array.from({ length: 256 }, (_, value) => value));
|
|
5120
5396
|
const count = Math.max(1, image.width * image.height);
|
|
5121
5397
|
if (token === "-auto-gamma") {
|
|
@@ -5142,36 +5418,59 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5142
5418
|
const range = maxs[channel] - mins[channel];
|
|
5143
5419
|
if (active[channel] && range > 0) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value - mins[channel]) / range * 255);
|
|
5144
5420
|
}
|
|
5145
|
-
} else if (token === "-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5421
|
+
} else if (token === "-linear-stretch") {
|
|
5422
|
+
const [black, white] = argument.split("x"), percentage = argument.includes("%");
|
|
5423
|
+
const lowTarget = (parseFloat(black || "0") || 0) * (percentage ? count / 100 : 1);
|
|
5424
|
+
const highTarget = white === void 0 ? count - lowTarget : (parseFloat(white) || 0) * (percentage ? count / 100 : 1);
|
|
5425
|
+
const bins = histogram[4];
|
|
5426
|
+
let low = 0, high = bins.length - 1, sum = 0;
|
|
5427
|
+
for (; low < bins.length - 1; low++) {
|
|
5428
|
+
sum += bins[low];
|
|
5429
|
+
if (sum >= lowTarget) break;
|
|
5430
|
+
}
|
|
5431
|
+
sum = 0;
|
|
5432
|
+
for (; high > 0; high--) {
|
|
5433
|
+
sum += bins[high];
|
|
5434
|
+
if (sum >= highTarget) break;
|
|
5435
|
+
}
|
|
5436
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5437
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5438
|
+
if (!active[channel]) continue;
|
|
5439
|
+
for (let value = 0; value < 256; value++) lookup[channel][value] = high === low ? value * 257 > low ? 255 : 0 : clampByteVal((value * 257 - low) / (high - low) * 255);
|
|
5440
|
+
}
|
|
5441
|
+
} else if (token === "-normalize" || token === "-contrast-stretch") {
|
|
5442
|
+
const [black, white] = argument.split("x"), percentage = argument.includes("%");
|
|
5443
|
+
const blackValue = parseFloat(black || "0") || 0, whiteValue = white === void 0 ? blackValue : parseFloat(white) || 0;
|
|
5444
|
+
const lowTarget = token === "-normalize" ? 0.02 * count : percentage ? blackValue / 100 * count : blackValue;
|
|
5445
|
+
const highTarget = token === "-normalize" ? 0.01 * count : percentage ? whiteValue / 100 * count : whiteValue;
|
|
5446
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5447
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5448
|
+
if (!active[channel]) continue;
|
|
5449
|
+
const bins = histogram[4] ?? histogram[channel], scale = histogram[4] ? 257 : 1;
|
|
5450
|
+
let low = 0, high = bins.length - 1, sum = 0;
|
|
5451
|
+
for (; low < bins.length - 1; low++) {
|
|
5452
|
+
sum += bins[low];
|
|
5453
|
+
if (sum > lowTarget) break;
|
|
5165
5454
|
}
|
|
5166
5455
|
sum = 0;
|
|
5456
|
+
for (; high > 0; high--) {
|
|
5457
|
+
sum += bins[high];
|
|
5458
|
+
if (sum > highTarget) break;
|
|
5459
|
+
}
|
|
5460
|
+
if (high > low) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value * scale - low) / (high - low) * 255);
|
|
5461
|
+
}
|
|
5462
|
+
} else if (token === "-equalize") {
|
|
5463
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5464
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5465
|
+
if (!active[channel]) continue;
|
|
5466
|
+
const bins = histogram[4] ?? histogram[channel], scale = histogram[4] ? 257 : 1;
|
|
5467
|
+
const black = bins[0];
|
|
5468
|
+
if (count === black) continue;
|
|
5469
|
+
let cumulative = 0, bin = 0;
|
|
5167
5470
|
for (let value = 0; value < 256; value++) {
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
high = value;
|
|
5171
|
-
break;
|
|
5172
|
-
}
|
|
5471
|
+
for (; bin <= value * scale; bin++) cumulative += bins[bin];
|
|
5472
|
+
lookup[channel][value] = clampByteVal((cumulative - black) / (count - black) * 255);
|
|
5173
5473
|
}
|
|
5174
|
-
if (high > low) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value - low) / (high - low) * 255);
|
|
5175
5474
|
}
|
|
5176
5475
|
}
|
|
5177
5476
|
return lookup;
|
|
@@ -5180,7 +5479,25 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5180
5479
|
function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
5181
5480
|
const state = { ...settings }, token = tokens[start];
|
|
5182
5481
|
let i = start, apply;
|
|
5183
|
-
if (token === "-
|
|
5482
|
+
if (token === "-colorspace" || token === "-grayscale") {
|
|
5483
|
+
const space = (tokens[++i] ?? "gray").toLowerCase();
|
|
5484
|
+
apply = function* (image) {
|
|
5485
|
+
if (!space.includes("gray") && !space.includes("grey") && space !== "rec709luma" && space !== "rec601luma") return image;
|
|
5486
|
+
const out = new Uint8Array(image.data);
|
|
5487
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5488
|
+
if (idx % 65536 === 0) yield;
|
|
5489
|
+
const gray = clampByteVal(space === "rec601luma" ? 0.298839 * out[idx] + 0.586811 * out[idx + 1] + 0.11435 * out[idx + 2] : 0.212656 * out[idx] + 0.715158 * out[idx + 1] + 0.072186 * out[idx + 2]);
|
|
5490
|
+
out[idx] = out[idx + 1] = out[idx + 2] = gray;
|
|
5491
|
+
}
|
|
5492
|
+
const { data16: ignored, ...metadata } = image;
|
|
5493
|
+
return { ...metadata, data: out, space: "b-w", channels: image.hasAlpha ? 2 : 1 };
|
|
5494
|
+
};
|
|
5495
|
+
} else if (token === "-gamma") {
|
|
5496
|
+
const gamma = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
5497
|
+
const channels = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: true };
|
|
5498
|
+
const lookup = [channels.r, channels.g, channels.b, channels.a].map((active) => Uint8Array.from({ length: 256 }, (_, value) => active ? clampByteVal(255 * Math.pow(value / 255, 1 / gamma)) : value));
|
|
5499
|
+
apply = (image) => applyMagickLookup(image, lookup);
|
|
5500
|
+
} else if (token === "-alpha") {
|
|
5184
5501
|
const mode = (tokens[++i] ?? "on").toLowerCase();
|
|
5185
5502
|
if (mode === "remove") {
|
|
5186
5503
|
apply = function* (im) {
|
|
@@ -5244,10 +5561,10 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5244
5561
|
apply = (image) => applyMagickColorMatrixSteps(image, matrix, signal);
|
|
5245
5562
|
} else if (token === "-opaque" || token === "+opaque") {
|
|
5246
5563
|
const target = parseColor(tokens[++i] ?? "#000000");
|
|
5247
|
-
apply = (image) => applyMagickOpaqueSteps(image, target, state.fill, state.
|
|
5564
|
+
apply = (image) => applyMagickOpaqueSteps(image, target, state.fill, state.alphaFuzz, token === "+opaque", signal);
|
|
5248
5565
|
} else if (token === "-transparent" || token === "+transparent") {
|
|
5249
5566
|
const target = parseColor(tokens[++i] ?? "#ffffff");
|
|
5250
|
-
apply = (image) => applyMagickTransparentSteps(image, target, state.
|
|
5567
|
+
apply = (image) => applyMagickTransparentSteps(image, target, state.alphaFuzz, token === "+transparent", signal);
|
|
5251
5568
|
} else if (token === "-evaluate") {
|
|
5252
5569
|
const operation = tokens[++i] ?? "Add", value = tokens[++i] ?? "0";
|
|
5253
5570
|
apply = (image) => applyMagickEvaluateSteps(image, operation, value, state.channels, signal);
|
|
@@ -5257,7 +5574,7 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5257
5574
|
} else if (token === "-sepia-tone" || token === "-solarize") {
|
|
5258
5575
|
const value = tokens[++i] ?? (token === "-sepia-tone" ? "80%" : "50%");
|
|
5259
5576
|
apply = (image) => token === "-sepia-tone" ? applyMagickSepiaToneSteps(image, value, signal) : applyMagickSolarizeSteps(image, value, signal);
|
|
5260
|
-
} else if (token === "-posterize"
|
|
5577
|
+
} else if (token === "-posterize") {
|
|
5261
5578
|
const levels = Number(tokens[++i] ?? 8);
|
|
5262
5579
|
apply = (image) => applyMagickPosterizeSteps(image, levels, signal);
|
|
5263
5580
|
} else if (token === "-monochrome") {
|
|
@@ -5266,17 +5583,83 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5266
5583
|
};
|
|
5267
5584
|
} else if (token === "-modulate") {
|
|
5268
5585
|
const parts = (tokens[++i] ?? "100,100,100").split(",").map(Number);
|
|
5269
|
-
const
|
|
5270
|
-
apply = (image)
|
|
5586
|
+
const brightness = (parts[0] ?? 100) / 100, saturation = (parts[1] ?? 100) / 100, hueShift = ((parts[2] ?? 100) - 100) * 3 / 100;
|
|
5587
|
+
apply = function* (image) {
|
|
5588
|
+
const out = new Uint8Array(image.data);
|
|
5589
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5590
|
+
if (idx % 65536 === 0) {
|
|
5591
|
+
signal?.throwIfAborted();
|
|
5592
|
+
yield;
|
|
5593
|
+
}
|
|
5594
|
+
const r = out[idx] / 255, g = out[idx + 1] / 255, b = out[idx + 2] / 255;
|
|
5595
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min;
|
|
5596
|
+
const lightness = (max + min) / 2;
|
|
5597
|
+
const sat = delta === 0 ? 0 : delta / (lightness <= 0.5 ? max + min : 2 - max - min);
|
|
5598
|
+
let hue = delta === 0 ? 0 : max === r ? (g - b) / delta : max === g ? 2 + (b - r) / delta : 4 + (r - g) / delta;
|
|
5599
|
+
hue = ((hue + hueShift) % 6 + 6) % 6;
|
|
5600
|
+
const light = lightness * brightness;
|
|
5601
|
+
const chroma = (1 - Math.abs(2 * light - 1)) * sat * saturation;
|
|
5602
|
+
const secondary = chroma * (1 - Math.abs(hue % 2 - 1));
|
|
5603
|
+
const offset = light - chroma / 2;
|
|
5604
|
+
const red = hue < 1 || hue >= 5 ? chroma : hue < 2 || hue >= 4 ? secondary : 0;
|
|
5605
|
+
const green = hue < 1 ? secondary : hue < 3 ? chroma : hue < 4 ? secondary : 0;
|
|
5606
|
+
const blue = hue < 2 ? 0 : hue < 3 ? secondary : hue < 5 ? chroma : secondary;
|
|
5607
|
+
out[idx] = clampByteVal((red + offset) * 255);
|
|
5608
|
+
out[idx + 1] = clampByteVal((green + offset) * 255);
|
|
5609
|
+
out[idx + 2] = clampByteVal((blue + offset) * 255);
|
|
5610
|
+
}
|
|
5611
|
+
const { data16: ignored, ...metadata } = image;
|
|
5612
|
+
return { ...metadata, data: out };
|
|
5613
|
+
};
|
|
5271
5614
|
} else if (token === "-brightness-contrast") {
|
|
5272
|
-
const
|
|
5273
|
-
|
|
5615
|
+
const [brightness = 0, contrast = 0] = (tokens[++i] ?? "0x0").split("x").map((value) => parseFloat(value) || 0);
|
|
5616
|
+
const slope = contrast < 0 ? 1 + contrast / 100 : 100 / Math.max(1e-12, 100 - contrast);
|
|
5617
|
+
const offset = (brightness / 100 * 255 - 127.5) * slope + 127.5;
|
|
5618
|
+
const channels = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: true };
|
|
5619
|
+
apply = function* (image) {
|
|
5620
|
+
const out = new Uint8Array(image.data);
|
|
5621
|
+
const mask = [channels.r, channels.g, channels.b, channels.a && image.hasAlpha];
|
|
5622
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5623
|
+
if (idx % 65536 === 0) yield;
|
|
5624
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5625
|
+
if (mask[channel]) out[idx + channel] = clampByteVal(out[idx + channel] * slope + offset);
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
return { ...image, data: out };
|
|
5629
|
+
};
|
|
5274
5630
|
} else if (token === "-threshold") {
|
|
5275
5631
|
const raw = tokens[++i] ?? "50%", value = Math.round(raw.endsWith("%") ? parseFloat(raw) / 100 * 255 : parseFloat(raw));
|
|
5276
5632
|
apply = (image) => thresholdImageSteps(image, value, true);
|
|
5277
|
-
} else if (token === "-
|
|
5278
|
-
i
|
|
5279
|
-
|
|
5633
|
+
} else if (token === "-colorize") {
|
|
5634
|
+
const strength = parseFloat(tokens[++i] ?? "100") / 100;
|
|
5635
|
+
const fill = [state.fill.r, state.fill.g, state.fill.b];
|
|
5636
|
+
apply = function* (image) {
|
|
5637
|
+
const out = new Uint8Array(image.data);
|
|
5638
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5639
|
+
if (idx % 65536 === 0) yield;
|
|
5640
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
5641
|
+
out[idx + channel] = clampByteVal(out[idx + channel] * (1 - strength) + fill[channel] * strength);
|
|
5642
|
+
}
|
|
5643
|
+
}
|
|
5644
|
+
return { ...image, data: out };
|
|
5645
|
+
};
|
|
5646
|
+
} else if (token === "-tint") {
|
|
5647
|
+
const strength = parseFloat(tokens[++i] ?? "100") / 100;
|
|
5648
|
+
const fill = [state.fill.r, state.fill.g, state.fill.b];
|
|
5649
|
+
const intensity = 0.212656 * state.fill.r + 0.715158 * state.fill.g + 0.072186 * state.fill.b;
|
|
5650
|
+
const vector = fill.map((channel) => channel * strength - intensity);
|
|
5651
|
+
apply = function* (image) {
|
|
5652
|
+
const out = new Uint8Array(image.data);
|
|
5653
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5654
|
+
if (idx % 65536 === 0) yield;
|
|
5655
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
5656
|
+
const value = out[idx + channel];
|
|
5657
|
+
const weight = value / 255 - 0.5;
|
|
5658
|
+
out[idx + channel] = clampByteVal(value + vector[channel] * (1 - 4 * weight * weight));
|
|
5659
|
+
}
|
|
5660
|
+
}
|
|
5661
|
+
return { ...image, data: out };
|
|
5662
|
+
};
|
|
5280
5663
|
} else if (token === "-negate" || token === "+negate") {
|
|
5281
5664
|
const onlyGray = token === "+negate";
|
|
5282
5665
|
const ch = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: false };
|
|
@@ -5529,10 +5912,16 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5529
5912
|
} else if (analysisOperation) {
|
|
5530
5913
|
i = analysisOperation.end;
|
|
5531
5914
|
stack = yield* mapSteps(stack, function* (image) {
|
|
5532
|
-
const histogram = Array.from({ length: 4 }, () => new Float64Array(256));
|
|
5915
|
+
const histogram = Array.from({ length: analysisOperation.luminance ? 5 : 4 }, (_, channel) => new Float64Array(channel === 4 ? 65536 : 256));
|
|
5533
5916
|
yield* accumulateMagickHistogram(image.data, histogram);
|
|
5534
5917
|
return yield* applyMagickLookup(image, analysisOperation.lookup(histogram, image));
|
|
5535
5918
|
});
|
|
5919
|
+
} else if (t === "-colors") {
|
|
5920
|
+
const colors = Number(tokens[++i]);
|
|
5921
|
+
stack = yield* mapSteps(stack, (image) => {
|
|
5922
|
+
const { data16: ignored, ...pixels } = image;
|
|
5923
|
+
return applyMagickRasterSteps(pixels, quantizePixelSteps(image.data.length, colors), signal);
|
|
5924
|
+
});
|
|
5536
5925
|
} else if (pixelOperation) {
|
|
5537
5926
|
i = pixelOperation.end;
|
|
5538
5927
|
stack = yield* mapSteps(stack, pixelOperation.apply);
|
|
@@ -5756,18 +6145,6 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5756
6145
|
stack = yield* mapSteps(stack, function* (im) {
|
|
5757
6146
|
return yield* applyExifOrientationSteps(im);
|
|
5758
6147
|
});
|
|
5759
|
-
} else if (t === "-colorspace" || t === "-grayscale") {
|
|
5760
|
-
const cs = (tokens[++i] ?? "gray").toLowerCase();
|
|
5761
|
-
if (cs.includes("gray") || cs.includes("grey") || cs === "rec709luma" || cs === "rec601luma") {
|
|
5762
|
-
stack = yield* mapSteps(stack, function* (im) {
|
|
5763
|
-
return yield* grayscaleImageSteps(im);
|
|
5764
|
-
});
|
|
5765
|
-
}
|
|
5766
|
-
} else if (t === "-gamma") {
|
|
5767
|
-
const gammaVal = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
5768
|
-
stack = yield* mapSteps(stack, function* (im) {
|
|
5769
|
-
return yield* gammaImageSteps(im, gammaVal, gammaVal);
|
|
5770
|
-
});
|
|
5771
6148
|
} else if (t === "-raise" || t === "+raise") {
|
|
5772
6149
|
const g = parseMagickGeometry(tokens[++i] ?? "4");
|
|
5773
6150
|
const bw = Math.max(1, Math.round(g.width ?? 4));
|
|
@@ -5970,14 +6347,19 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5970
6347
|
}
|
|
5971
6348
|
stack = [appended];
|
|
5972
6349
|
} else if (t === "-flatten" || t === "-mosaic" || t === "-layers" && ["flatten", "merge", "mosaic"].includes((tokens[i + 1] ?? "").toLowerCase())) {
|
|
6350
|
+
const flatten = t === "-flatten" || t === "-layers" && tokens[i + 1]?.toLowerCase() === "flatten";
|
|
5973
6351
|
if (t === "-layers")
|
|
5974
6352
|
i++;
|
|
5975
6353
|
if (stack.length > 0) {
|
|
5976
6354
|
const oldStack = stack;
|
|
5977
|
-
const maxW = Math.max(...oldStack.map((im) => im.width));
|
|
5978
|
-
const maxH = Math.max(...oldStack.map((im) => im.height));
|
|
6355
|
+
const maxW = flatten ? oldStack[0].width : Math.max(...oldStack.map((im) => im.width));
|
|
6356
|
+
const maxH = flatten ? oldStack[0].height : Math.max(...oldStack.map((im) => im.height));
|
|
5979
6357
|
const canvas = yield* createSolidRgbaImageSteps(maxW, maxH, state.background);
|
|
5980
|
-
const layers =
|
|
6358
|
+
const layers = [];
|
|
6359
|
+
for (const im of oldStack) {
|
|
6360
|
+
const clipped = im.width > maxW || im.height > maxH ? yield* extractImageSteps(im, { left: 0, top: 0, width: Math.min(im.width, maxW), height: Math.min(im.height, maxH) }) : im;
|
|
6361
|
+
layers.push(rgbaToCompositeLayer(clipped, 0, 0, state.compose));
|
|
6362
|
+
}
|
|
5981
6363
|
const flattened = yield* compositeImageSteps(canvas, layers);
|
|
5982
6364
|
if (canvas.data.buffer !== flattened.data.buffer) detachRgbaBuffer(canvas.data);
|
|
5983
6365
|
for (const im of oldStack) {
|
|
@@ -6227,7 +6609,7 @@ async function morphStoredMagickImage(image, backend, plan, signal) {
|
|
|
6227
6609
|
images.push({ ...image, position });
|
|
6228
6610
|
} else {
|
|
6229
6611
|
const source = images[step.source];
|
|
6230
|
-
images.push(await transformStoredMagickRaster(source, backend, morphologyPixelSteps(source, plan.
|
|
6612
|
+
images.push(await transformStoredMagickRaster(source, backend, morphologyPixelSteps(source, plan.rows, step.mode), signal));
|
|
6231
6613
|
}
|
|
6232
6614
|
}
|
|
6233
6615
|
return images.at(-1);
|
|
@@ -6276,7 +6658,8 @@ async function transformStoredMagickCoordinates(image, backend, token, geometry,
|
|
|
6276
6658
|
return { ...image, position, width, height, ...token === "-splice" ? { hasAlpha: true } : {} };
|
|
6277
6659
|
}
|
|
6278
6660
|
async function compositeStoredMagick(base, overlay, backend, signal, left, top, mode, args, generic = false, inPlace = false, montageRounding = false) {
|
|
6279
|
-
|
|
6661
|
+
const compositeMode = magickCompositeMode(mode);
|
|
6662
|
+
let hasAlpha = base.hasAlpha || !generic && !compositeMode.delegated && !["difference", "src", "source", "copy", "xor"].includes(compositeMode.name);
|
|
6280
6663
|
const position = inPlace ? base.position : backend.storage.allocate(base.width * base.height * 4);
|
|
6281
6664
|
for (let start = 0; !inPlace && start < base.width * base.height * 4; start += 16384) {
|
|
6282
6665
|
await yieldTurn9(signal);
|
|
@@ -6477,6 +6860,11 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6477
6860
|
});
|
|
6478
6861
|
continue;
|
|
6479
6862
|
}
|
|
6863
|
+
if (!operandsOnly && token === "-colors") {
|
|
6864
|
+
const colors = Number(tokens[++i]);
|
|
6865
|
+
steps.push(async (image, backend) => image ? transformStoredMagickRaster(image, backend, quantizePixelSteps(image.width * image.height * 4, colors), signal) : void 0);
|
|
6866
|
+
continue;
|
|
6867
|
+
}
|
|
6480
6868
|
const pixelOperation = operandsOnly ? void 0 : parseMagickPixelOperation(tokens, state, i, signal);
|
|
6481
6869
|
if (pixelOperation) {
|
|
6482
6870
|
i = pixelOperation.end;
|
|
@@ -6499,7 +6887,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6499
6887
|
i = analysisOperation.end;
|
|
6500
6888
|
steps.push(async (image, backend) => {
|
|
6501
6889
|
if (!image) return;
|
|
6502
|
-
const histogram = Array.from({ length: 4 }, () => new Float64Array(256)), size = image.width * image.height * 4;
|
|
6890
|
+
const histogram = Array.from({ length: analysisOperation.luminance ? 5 : 4 }, (_, channel) => new Float64Array(channel === 4 ? 65536 : 256)), size = image.width * image.height * 4;
|
|
6503
6891
|
for (let offset = 0; offset < size; offset += 16384) {
|
|
6504
6892
|
if (offset % 1048576 === 0) await yieldTurn9(signal);
|
|
6505
6893
|
await drainSteps(accumulateMagickHistogram(await backend.storage.read(image.position + offset, Math.min(16384, size - offset)), histogram), signal);
|
|
@@ -6510,6 +6898,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6510
6898
|
continue;
|
|
6511
6899
|
}
|
|
6512
6900
|
if (!operandsOnly && (["-append", "+append", "-flatten", "-mosaic", "-composite"].includes(token) || token === "-layers" && ["flatten", "merge", "mosaic"].includes((tokens[i + 1] ?? "").toLowerCase()))) {
|
|
6901
|
+
const flatten = token === "-flatten" || token === "-layers" && tokens[i + 1]?.toLowerCase() === "flatten";
|
|
6513
6902
|
if (token === "-layers") i++;
|
|
6514
6903
|
const settings = { ...state };
|
|
6515
6904
|
stackStep(async (stack, backend) => {
|
|
@@ -6534,7 +6923,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6534
6923
|
const append = token.endsWith("append"), vertical = token === "-append";
|
|
6535
6924
|
if (append && stack.length === 1) return stack;
|
|
6536
6925
|
let width = 0, height = 0;
|
|
6537
|
-
for (let n = 0; n < stack.length; n++) {
|
|
6926
|
+
for (let n = 0; n < (flatten ? 1 : stack.length); n++) {
|
|
6538
6927
|
await yieldTurn9(signal);
|
|
6539
6928
|
const frame = await stack.get(n);
|
|
6540
6929
|
width = append && !vertical ? width + frame.width : Math.max(width, frame.width);
|
|
@@ -6544,7 +6933,17 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6544
6933
|
let cursor = 0;
|
|
6545
6934
|
for (let n = 0; n < stack.length; n++) {
|
|
6546
6935
|
const frame = await stack.get(n), offset = resolveGravityOffset(vertical ? width - frame.width : 0, vertical ? 0 : height - frame.height, settings.gravity);
|
|
6547
|
-
|
|
6936
|
+
if (append) {
|
|
6937
|
+
const left = vertical ? offset.left : cursor, top = vertical ? cursor : offset.top;
|
|
6938
|
+
for (let y = 0; y < frame.height; y++) {
|
|
6939
|
+
for (let x = 0; x < frame.width; x += 4096) {
|
|
6940
|
+
await yieldTurn9(signal);
|
|
6941
|
+
const pixels = await backend.storage.read(frame.position + (y * frame.width + x) * 4, Math.min(4096, frame.width - x) * 4);
|
|
6942
|
+
await backend.storage.write(result.position + ((top + y) * width + left + x) * 4, pixels);
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
result = { ...result, hasAlpha: result.hasAlpha || frame.hasAlpha };
|
|
6946
|
+
} else result = await compositeStoredMagick(result, frame, backend, signal, 0, 0, settings.compose, void 0, true, true);
|
|
6548
6947
|
cursor += vertical ? frame.height : frame.width;
|
|
6549
6948
|
}
|
|
6550
6949
|
stack.length = 0;
|
|
@@ -6760,12 +7159,6 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6760
7159
|
} else if (!operandsOnly && token === "-rotate") {
|
|
6761
7160
|
const angle = Number(tokens[++i] ?? 0), background = state.background;
|
|
6762
7161
|
transform(() => ({ kind: "rotate", angle, background }));
|
|
6763
|
-
} else if (!operandsOnly && token === "-gamma") {
|
|
6764
|
-
const gamma = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
6765
|
-
transform(() => ({ kind: "gamma", gamma, gammaOut: gamma }));
|
|
6766
|
-
} else if (!operandsOnly && ["-colorspace", "-grayscale"].includes(token)) {
|
|
6767
|
-
const space = (tokens[++i] ?? "gray").toLowerCase();
|
|
6768
|
-
if (space.includes("gray") || space.includes("grey") || space === "rec709luma" || space === "rec601luma") transform(() => ({ kind: "grayscale" }));
|
|
6769
7162
|
} else if (!operandsOnly && ["-blur", "-gaussian-blur", "-sharpen", "-unsharp"].includes(token)) {
|
|
6770
7163
|
const geometry = parseMagickGeometry(tokens[++i] ?? "0x1"), sigma = Math.max(0.3, geometry.height ?? geometry.width ?? 1);
|
|
6771
7164
|
const kind = token === "-blur" || token === "-gaussian-blur" ? "blur" : "sharpen";
|