@poe-platform/safe-bash 0.1.839 → 0.1.841
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/audio-ast/binary.d.ts +19 -0
- package/dist/audio-ast/index.d.ts +1 -0
- package/dist/audio-ast/index.js +92 -91
- package/dist/audio-ast/mpeg.d.ts +23 -0
- package/dist/mp4-ast/containers/adapters.d.ts +1 -0
- package/dist/mp4-ast/types.d.ts +8 -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/audio-ast.browser.js +2 -1
- package/dist/safe-bash/audio-ast.js +1 -1
- package/dist/safe-bash/command-chunks/{chunk-SG2KOOD4.js → chunk-3SAYZFMD.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-T6OXG7QQ.js → chunk-AAOPVEPF.js} +66 -1
- package/dist/safe-bash/command-chunks/{chunk-FH5VMAPS.js → chunk-BZW25LNV.js} +420 -131
- package/dist/safe-bash/command-chunks/{chunk-DQVOBGI2.js → chunk-CIV3GER2.js} +6770 -6523
- 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-77ROD2TR.js → chunk-X3WNWRVT.js} +11 -5
- package/dist/safe-bash/command-chunks/{chunk-RSFKQE2U.js → chunk-YAXP4EHP.js} +19 -10
- package/dist/safe-bash/command-chunks/{chunk-TCXM7BSZ.js → chunk-YFMNXPLP.js} +4 -4
- package/dist/safe-bash-command-exiftool/index.js +25 -1
- package/dist/safe-bash-command-ffmpeg/index.js +191 -49
- 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 +498 -101
- 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 +52 -52
- 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
|
|
|
@@ -1337,6 +1428,7 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1337
1428
|
const height = Math.max(imgA.height, imgB.height);
|
|
1338
1429
|
const totalPixels = Math.max(1, width * height);
|
|
1339
1430
|
let aeCount = 0;
|
|
1431
|
+
let aeMagnitude = 0;
|
|
1340
1432
|
let sumAbs = 0;
|
|
1341
1433
|
let sumSq = 0;
|
|
1342
1434
|
let maxAbs = 0;
|
|
@@ -1346,6 +1438,15 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1346
1438
|
let sumLumSqB = 0;
|
|
1347
1439
|
let sumLumAB = 0;
|
|
1348
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
|
+
})) : [];
|
|
1349
1450
|
for (let y = 0; y < height; y++) {
|
|
1350
1451
|
if ((y & 511) === 511)
|
|
1351
1452
|
yield;
|
|
@@ -1358,6 +1459,7 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1358
1459
|
dataA = yield { kind: "read", image: 0, position: (y * imgA.width + left) * 4, length };
|
|
1359
1460
|
if (!(dataA instanceof Uint8Array) || dataA.length !== length)
|
|
1360
1461
|
throw new Error("Truncated comparison pixels");
|
|
1462
|
+
dataA = dataA.slice();
|
|
1361
1463
|
}
|
|
1362
1464
|
if (y < imgB.height && left < imgB.width) {
|
|
1363
1465
|
const length = Math.min(count, imgB.width - left) * 4;
|
|
@@ -1380,6 +1482,20 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1380
1482
|
const gB = offB >= 0 ? dataB[offB + 1] : 0;
|
|
1381
1483
|
const bB = offB >= 0 ? dataB[offB + 2] : 0;
|
|
1382
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
|
+
}
|
|
1383
1499
|
const dr = Math.abs(rA - rB);
|
|
1384
1500
|
const dg = Math.abs(gA - gB);
|
|
1385
1501
|
const db = Math.abs(bA - bB);
|
|
@@ -1389,6 +1505,8 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1389
1505
|
const pdr = Math.abs(rA * alphaA - rB * alphaB);
|
|
1390
1506
|
const pdg = Math.abs(gA * alphaA - gB * alphaB);
|
|
1391
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);
|
|
1392
1510
|
const maxDelta = hasAlpha ? Math.max(pdr, pdg, pdb, da) : Math.max(dr, dg, db);
|
|
1393
1511
|
if (!inBoundsA || !inBoundsB || maxDelta > fuzz) {
|
|
1394
1512
|
aeCount++;
|
|
@@ -1450,11 +1568,10 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1450
1568
|
const c1 = 1e-4;
|
|
1451
1569
|
const c2 = 9e-4;
|
|
1452
1570
|
const ssim = (2 * muA * muB + c1) * (2 * covAB + c2) / ((muA * muA + muB * muB + c1) * (varA + varB + c2));
|
|
1453
|
-
const ncc = varA === 0 && varB === 0 ? 1 : covAB / (Math.sqrt(varA * varB) || 1);
|
|
1454
1571
|
let metricStr;
|
|
1455
1572
|
switch (metric) {
|
|
1456
1573
|
case "ae":
|
|
1457
|
-
metricStr =
|
|
1574
|
+
metricStr = `${formatSignificantMetric(aeMagnitude / (numCh * 255))} (${formatSignificantMetric(aeMagnitude / (totalPixels * numCh * 255))})`;
|
|
1458
1575
|
break;
|
|
1459
1576
|
case "mae":
|
|
1460
1577
|
metricStr = `${formatMetricNum(maeNorm * 65535)} (${formatMetricNum(maeNorm)})`;
|
|
@@ -1466,23 +1583,37 @@ function* compareImageSteps(imgA, imgB, options) {
|
|
|
1466
1583
|
metricStr = `${formatMetricNum(paeNorm * 65535)} (${formatMetricNum(paeNorm)})`;
|
|
1467
1584
|
break;
|
|
1468
1585
|
case "psnr":
|
|
1469
|
-
metricStr = mseNorm === 0 ? "
|
|
1586
|
+
metricStr = mseNorm === 0 ? "120 (1)" : formatMetricNum(10 * Math.log10(1 / mseNorm));
|
|
1470
1587
|
break;
|
|
1471
|
-
case "ssim":
|
|
1472
|
-
|
|
1588
|
+
case "ssim": {
|
|
1589
|
+
const distortion = yield* compareSsimSteps(imgA, imgB);
|
|
1590
|
+
metricStr = `${formatSignificantMetric(distortion * 65535)} (${formatSignificantMetric(distortion)})`;
|
|
1473
1591
|
break;
|
|
1592
|
+
}
|
|
1474
1593
|
case "dssim":
|
|
1475
1594
|
metricStr = formatMetricNum((1 - ssim) / 2);
|
|
1476
1595
|
break;
|
|
1477
|
-
case "ncc":
|
|
1478
|
-
|
|
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)})`;
|
|
1479
1609
|
break;
|
|
1610
|
+
}
|
|
1480
1611
|
case "rmse":
|
|
1481
1612
|
default:
|
|
1482
1613
|
metricStr = `${formatMetricNum(rmseNorm * 65535)} (${formatMetricNum(rmseNorm)})`;
|
|
1483
1614
|
break;
|
|
1484
1615
|
}
|
|
1485
|
-
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;
|
|
1486
1617
|
return { width, height, metric: metricStr, exitCode };
|
|
1487
1618
|
}
|
|
1488
1619
|
function formatMetricNum(n) {
|
|
@@ -1493,6 +1624,65 @@ function formatMetricNum(n) {
|
|
|
1493
1624
|
const fixed = n.toFixed(6).replace(/\.?0+$/, "");
|
|
1494
1625
|
return fixed === "-0" ? "0" : fixed;
|
|
1495
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
|
+
}
|
|
1496
1686
|
|
|
1497
1687
|
// packages/safe-bash-command-imagemagick/src/identify-file.ts
|
|
1498
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";
|
|
@@ -1589,7 +1779,7 @@ import {
|
|
|
1589
1779
|
} from "../safe-bash-contracts/command.js";
|
|
1590
1780
|
import { readBytes as readBytes2, writeBytes as writeBytes2 } from "../safe-bash-contracts/io.js";
|
|
1591
1781
|
import { createOutputOperation } from "../safe-bash-contracts/output.js";
|
|
1592
|
-
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";
|
|
1593
1783
|
var X11_NAMED_COLORS = {
|
|
1594
1784
|
aliceblue: [240, 248, 255, 255],
|
|
1595
1785
|
antiquewhite: [250, 235, 215, 255],
|
|
@@ -2006,10 +2196,21 @@ function magickMorphologyPlan(methodRaw, kernelSpec) {
|
|
|
2006
2196
|
rx = Math.max(1, Math.round(Number(sizePart)));
|
|
2007
2197
|
ry = rx;
|
|
2008
2198
|
}
|
|
2009
|
-
const
|
|
2199
|
+
const shape = (colonIdx < 0 ? kernelSpec : kernelSpec.slice(0, colonIdx)).toLowerCase();
|
|
2200
|
+
const disk = shape === "disk";
|
|
2010
2201
|
const radius = Number(sizePart) || 4.5;
|
|
2011
2202
|
if (disk) ry = Math.max(0, Math.floor(radius));
|
|
2012
|
-
|
|
2203
|
+
if (shape === "octagon" || shape === "diamond" || shape === "plus") {
|
|
2204
|
+
rx = ry = Math.max(1, Math.floor(Number(sizePart) || (shape === "diamond" ? 1 : 2)));
|
|
2205
|
+
}
|
|
2206
|
+
const rows = Array.from({ length: ry * 2 + 1 }, (_, row) => {
|
|
2207
|
+
const dy = Math.abs(row - ry);
|
|
2208
|
+
if (disk) return Math.floor(Math.sqrt(radius * radius - dy * dy));
|
|
2209
|
+
if (shape === "diamond") return rx - dy;
|
|
2210
|
+
if (shape === "plus") return dy === 0 ? rx : 0;
|
|
2211
|
+
if (shape === "octagon") return Math.min(rx, rx + Math.floor(rx / 2) - dy);
|
|
2212
|
+
return rx;
|
|
2213
|
+
});
|
|
2013
2214
|
const filter = (source, mode) => ({ source, mode });
|
|
2014
2215
|
const diff = (a, b) => ({ mode: "diff", a, b });
|
|
2015
2216
|
let steps;
|
|
@@ -2177,7 +2378,9 @@ function* applyMagickEvaluateSequenceSteps(stack, opRaw, signal) {
|
|
|
2177
2378
|
}
|
|
2178
2379
|
function parseMagickConvolveKernel(spec) {
|
|
2179
2380
|
const afterColon = spec.includes(":") ? spec.slice(spec.indexOf(":") + 1) : spec;
|
|
2180
|
-
|
|
2381
|
+
const kernel = afterColon.trim().split(/[\s,]+/).filter((s) => s.length > 0).map(Number).filter(Number.isFinite);
|
|
2382
|
+
const sum = kernel.reduce((total, weight) => total + weight, 0);
|
|
2383
|
+
return sum === 0 ? kernel : kernel.map((weight) => weight / sum);
|
|
2181
2384
|
}
|
|
2182
2385
|
function* applyMagickCustomConvolveSteps(img, spec, signal) {
|
|
2183
2386
|
const kernel = parseMagickConvolveKernel(spec);
|
|
@@ -2813,7 +3016,9 @@ function* applyMagickOpaqueSteps(img, target, replacement, fuzz, invert, signal)
|
|
|
2813
3016
|
const dr = Math.abs(out[i] - target.r);
|
|
2814
3017
|
const dg = Math.abs(out[i + 1] - target.g);
|
|
2815
3018
|
const db = Math.abs(out[i + 2] - target.b);
|
|
2816
|
-
const
|
|
3019
|
+
const alpha = out[i + 3];
|
|
3020
|
+
const distance = (alpha - target.a) ** 2 + (dr ** 2 + dg ** 2 + db ** 2) * (alpha / 255) * (target.a / 255) / 3;
|
|
3021
|
+
const matched = distance <= fuzz ** 2;
|
|
2817
3022
|
if (invert ? !matched : matched) {
|
|
2818
3023
|
out[i] = replacement.r;
|
|
2819
3024
|
out[i + 1] = replacement.g;
|
|
@@ -2832,7 +3037,9 @@ function* applyMagickTransparentSteps(img, target, fuzz, invert, signal) {
|
|
|
2832
3037
|
const dr = Math.abs(out[i] - target.r);
|
|
2833
3038
|
const dg = Math.abs(out[i + 1] - target.g);
|
|
2834
3039
|
const db = Math.abs(out[i + 2] - target.b);
|
|
2835
|
-
const
|
|
3040
|
+
const alpha = out[i + 3];
|
|
3041
|
+
const distance = (alpha - target.a) ** 2 + (dr ** 2 + dg ** 2 + db ** 2) * (alpha / 255) * (target.a / 255) / 3;
|
|
3042
|
+
const matched = distance <= fuzz ** 2;
|
|
2836
3043
|
if (invert ? !matched : matched) {
|
|
2837
3044
|
out[i + 3] = 0;
|
|
2838
3045
|
}
|
|
@@ -2844,7 +3051,7 @@ function* applyMagickEvaluateSteps(img, opRaw, valStr, channels, signal) {
|
|
|
2844
3051
|
const op = opRaw.toLowerCase().replace(/[-_]/g, "");
|
|
2845
3052
|
const isPct = valStr.trim().endsWith("%");
|
|
2846
3053
|
const rawNum = parseFloat(valStr);
|
|
2847
|
-
const vByte = isPct ? rawNum / 100 * 255 : rawNum
|
|
3054
|
+
const vByte = isPct ? rawNum / 100 * 255 : rawNum / 257;
|
|
2848
3055
|
const vFactor = isPct ? rawNum / 100 : rawNum;
|
|
2849
3056
|
const out = new Uint8Array(img.data);
|
|
2850
3057
|
const mask = [channels.r, channels.g, channels.b, channels.a];
|
|
@@ -3601,14 +3808,66 @@ function* applyMagickPosterizeSteps(img, levelsRaw, signal) {
|
|
|
3601
3808
|
const levels = Math.max(2, Math.min(256, Math.round(levelsRaw)));
|
|
3602
3809
|
const step = 255 / (levels - 1);
|
|
3603
3810
|
const out = new Uint8Array(img.data);
|
|
3811
|
+
let palette;
|
|
3604
3812
|
for (let i = 0; i < out.length; i += 4) {
|
|
3605
3813
|
if (++cooperativeWork % 65536 === 0)
|
|
3606
3814
|
yield;
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3815
|
+
if (img.hasAlpha && out[i + 3] < 255 && levels <= 16) {
|
|
3816
|
+
if (!palette) {
|
|
3817
|
+
palette = { children: [] };
|
|
3818
|
+
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++) {
|
|
3819
|
+
if (++cooperativeWork % 1024 === 0) {
|
|
3820
|
+
signal?.throwIfAborted();
|
|
3821
|
+
yield;
|
|
3822
|
+
}
|
|
3823
|
+
const color = a === 0 ? [0, 0, 0, 0] : [r * step, g * step, b * step, a * step];
|
|
3824
|
+
const bytes2 = color.map((value, channel) => Math.round(channel === 3 ? value : value * color[3] / 255));
|
|
3825
|
+
let node2 = palette;
|
|
3826
|
+
for (let bit = 7; bit >= 0; bit--) {
|
|
3827
|
+
const id = bytes2[0] >> bit & 1 | (bytes2[1] >> bit & 1) << 1 | (bytes2[2] >> bit & 1) << 2 | (bytes2[3] >> bit & 1) << 3;
|
|
3828
|
+
node2 = node2.children[id] ??= { children: [] };
|
|
3829
|
+
}
|
|
3830
|
+
node2.color = color;
|
|
3831
|
+
}
|
|
3832
|
+
}
|
|
3833
|
+
const alpha = out[i + 3] / 255;
|
|
3834
|
+
const target = [out[i] * alpha, out[i + 1] * alpha, out[i + 2] * alpha, out[i + 3]];
|
|
3835
|
+
const bytes = target.map(Math.round);
|
|
3836
|
+
let node = palette, parent = palette;
|
|
3837
|
+
for (let bit = 7; bit > 0; bit--) {
|
|
3838
|
+
const id = bytes[0] >> bit & 1 | (bytes[1] >> bit & 1) << 1 | (bytes[2] >> bit & 1) << 2 | (bytes[3] >> bit & 1) << 3;
|
|
3839
|
+
const child = node.children[id];
|
|
3840
|
+
if (!child) break;
|
|
3841
|
+
parent = node;
|
|
3842
|
+
node = child;
|
|
3843
|
+
}
|
|
3844
|
+
let distance = Infinity, closest = [0, 0, 0, 0];
|
|
3845
|
+
const pending = [parent];
|
|
3846
|
+
while (pending.length) {
|
|
3847
|
+
if (++cooperativeWork % 1024 === 0) {
|
|
3848
|
+
signal?.throwIfAborted();
|
|
3849
|
+
yield;
|
|
3850
|
+
}
|
|
3851
|
+
const candidate = pending.pop();
|
|
3852
|
+
for (let child = candidate.children.length - 1; child >= 0; child--) {
|
|
3853
|
+
if (candidate.children[child]) pending.push(candidate.children[child]);
|
|
3854
|
+
}
|
|
3855
|
+
if (!candidate.color) continue;
|
|
3856
|
+
const color = candidate.color, candidateAlpha = color[3] / 255;
|
|
3857
|
+
let error = (color[3] - target[3]) ** 2;
|
|
3858
|
+
for (let channel = 0; channel < 3; channel++) error += (color[channel] * candidateAlpha - target[channel] * alpha) ** 2;
|
|
3859
|
+
if (error <= distance) {
|
|
3860
|
+
distance = error;
|
|
3861
|
+
closest = color;
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
for (let channel = 0; channel < 4; channel++) out[i + channel] = clampByteVal(closest[channel]);
|
|
3865
|
+
} else {
|
|
3866
|
+
for (let channel = 0; channel < 4; channel++) out[i + channel] = clampByteVal(Math.round(out[i + channel] / 255 * (levels - 1)) * step);
|
|
3867
|
+
}
|
|
3610
3868
|
}
|
|
3611
|
-
|
|
3869
|
+
const { data16: ignored, ...metadata } = img;
|
|
3870
|
+
return { ...metadata, data: out };
|
|
3612
3871
|
}
|
|
3613
3872
|
function* applyMagickRasterSteps(img, steps, signal, dimensions = img, sources = [img]) {
|
|
3614
3873
|
const data = new Uint8Array(dimensions.width * dimensions.height * 4);
|
|
@@ -3764,7 +4023,7 @@ function parseCompose(raw) {
|
|
|
3764
4023
|
function magickCompositeMode(mode) {
|
|
3765
4024
|
let name = "";
|
|
3766
4025
|
for (const character of mode.toLowerCase()) if (character !== "-" && character !== "_" && character.trim()) name += character;
|
|
3767
|
-
return { name, delegated: ["over", "srcover", "dstover", "destover", "plus", "add", "lineardodge", "colordodge", "colorburn", "
|
|
4026
|
+
return { name, delegated: ["over", "srcover", "dstover", "destover", "plus", "add", "lineardodge", "colordodge", "colorburn", "saturate"].includes(name) };
|
|
3768
4027
|
}
|
|
3769
4028
|
function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeArgs, signal) {
|
|
3770
4029
|
let cooperativeWork = 0;
|
|
@@ -3832,7 +4091,7 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3832
4091
|
out[dIdx] = sr;
|
|
3833
4092
|
out[dIdx + 1] = sg;
|
|
3834
4093
|
out[dIdx + 2] = sb;
|
|
3835
|
-
out[dIdx + 3] = saByte;
|
|
4094
|
+
out[dIdx + 3] = dst.hasAlpha ? saByte : daByte;
|
|
3836
4095
|
continue;
|
|
3837
4096
|
}
|
|
3838
4097
|
if (norm === "dst" || norm === "dest") {
|
|
@@ -3861,11 +4120,24 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3861
4120
|
out[dIdx + 2] = sb;
|
|
3862
4121
|
continue;
|
|
3863
4122
|
}
|
|
4123
|
+
if (norm === "softlight") {
|
|
4124
|
+
const alpha = sa + da - sa * da;
|
|
4125
|
+
for (let c = 0; c < 3; c++) {
|
|
4126
|
+
const sc = src.data[sIdx + c] / 255 * sa;
|
|
4127
|
+
const dc = dst.data[dIdx + c] / 255 * da;
|
|
4128
|
+
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);
|
|
4129
|
+
out[dIdx + c] = alpha === 0 ? 0 : clampByteVal(255 * (dc * sa + da * blend + sc * (1 - da) + dc * (1 - sa)) / alpha);
|
|
4130
|
+
}
|
|
4131
|
+
out[dIdx + 3] = clampByteVal(alpha * 255);
|
|
4132
|
+
continue;
|
|
4133
|
+
}
|
|
3864
4134
|
if (norm === "difference") {
|
|
4135
|
+
if (saByte === 0)
|
|
4136
|
+
continue;
|
|
3865
4137
|
out[dIdx] = Math.abs(sr - dr);
|
|
3866
4138
|
out[dIdx + 1] = Math.abs(sg - dg);
|
|
3867
4139
|
out[dIdx + 2] = Math.abs(sb - db);
|
|
3868
|
-
out[dIdx + 3] = Math.abs(saByte - daByte);
|
|
4140
|
+
out[dIdx + 3] = dst.hasAlpha ? Math.abs(saByte - daByte) : daByte;
|
|
3869
4141
|
continue;
|
|
3870
4142
|
}
|
|
3871
4143
|
if (norm === "in" || norm === "srcin") {
|
|
@@ -3919,10 +4191,10 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3919
4191
|
out[dIdx] = clampByteVal(sr * sa * (1 - da) + dr * da * (1 - sa));
|
|
3920
4192
|
out[dIdx + 1] = clampByteVal(sg * sa * (1 - da) + dg * da * (1 - sa));
|
|
3921
4193
|
out[dIdx + 2] = clampByteVal(sb * sa * (1 - da) + db * da * (1 - sa));
|
|
3922
|
-
out[dIdx + 3] = clampByteVal(outA * 255);
|
|
4194
|
+
out[dIdx + 3] = dst.hasAlpha ? clampByteVal(outA * 255) : daByte;
|
|
3923
4195
|
continue;
|
|
3924
4196
|
}
|
|
3925
|
-
if (norm === "multiply" || norm === "screen" || norm === "overlay" || norm === "darken" || norm === "lighten" || norm === "exclusion") {
|
|
4197
|
+
if (norm === "multiply" || norm === "screen" || norm === "overlay" || norm === "hardlight" || norm === "darken" || norm === "lighten" || norm === "exclusion") {
|
|
3926
4198
|
const gamma = sa + da - sa * da;
|
|
3927
4199
|
const sChan = [sr / 255, sg / 255, sb / 255];
|
|
3928
4200
|
const dChan = [dr / 255, dg / 255, db / 255];
|
|
@@ -3946,10 +4218,12 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3946
4218
|
f = sc * dc;
|
|
3947
4219
|
else if (norm === "screen")
|
|
3948
4220
|
f = sc + dc - sc * dc;
|
|
4221
|
+
else if (norm === "hardlight")
|
|
4222
|
+
f = sc <= 0.5 ? 2 * sc * dc : 1 - 2 * (1 - sc) * (1 - dc);
|
|
3949
4223
|
else
|
|
3950
4224
|
f = dc <= 0.5 ? 2 * sc * dc : 1 - 2 * (1 - sc) * (1 - dc);
|
|
3951
4225
|
const comp = f * sa * da + sc * sa * (1 - da) + dc * da * (1 - sa);
|
|
3952
|
-
const unpremul = gamma > 1e-6 && norm === "overlay" ? comp / gamma : comp;
|
|
4226
|
+
const unpremul = gamma > 1e-6 && (norm === "overlay" || norm === "hardlight") ? comp / gamma : comp;
|
|
3953
4227
|
out[dIdx + c] = clampByteVal(unpremul * 255);
|
|
3954
4228
|
}
|
|
3955
4229
|
}
|
|
@@ -3973,7 +4247,7 @@ function* applyMagickCompositeLayerSteps(dst, src, modeRaw, left, top, composeAr
|
|
|
3973
4247
|
const clipped = yield* extractImageSteps(src, { left: sx0, top: sy0, width: sx1 - sx0, height: sy1 - sy0 });
|
|
3974
4248
|
return yield* compositeImageSteps(dst, [rgbaToCompositeLayer(clipped, Math.max(0, ox), Math.max(0, oy), parseCompose(modeRaw))]);
|
|
3975
4249
|
}
|
|
3976
|
-
return { ...dst, data: out, hasAlpha: true };
|
|
4250
|
+
return { ...dst, data: out, hasAlpha: ["difference", "src", "source", "copy", "xor"].includes(norm) ? dst.hasAlpha : true };
|
|
3977
4251
|
}
|
|
3978
4252
|
function rgbaToCss(c) {
|
|
3979
4253
|
if (c.a === 0) return "none";
|
|
@@ -4549,27 +4823,31 @@ function* appendStackImagesSteps(stack, vertical, state) {
|
|
|
4549
4823
|
const totalW = vertical ? Math.max(...stack.map((im) => im.width)) : stack.reduce((acc, im) => acc + im.width, 0);
|
|
4550
4824
|
const totalH = vertical ? stack.reduce((acc, im) => acc + im.height, 0) : Math.max(...stack.map((im) => im.height));
|
|
4551
4825
|
const canvas = yield* createSolidRgbaImageSteps(totalW, totalH, state.background);
|
|
4826
|
+
let hasAlpha = canvas.hasAlpha;
|
|
4552
4827
|
let cursor = 0;
|
|
4553
4828
|
for (const im of stack) {
|
|
4554
4829
|
if (++work % 16384 === 0)
|
|
4555
4830
|
yield;
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4831
|
+
const off = resolveGravityOffset(vertical ? totalW - im.width : 0, vertical ? 0 : totalH - im.height, state.gravity);
|
|
4832
|
+
const left = vertical ? off.left : cursor, top = vertical ? cursor : off.top;
|
|
4833
|
+
for (let y = 0; y < im.height; y++) {
|
|
4834
|
+
for (let x = 0; x < im.width; x += 4096) {
|
|
4835
|
+
yield;
|
|
4836
|
+
const start = (y * im.width + x) * 4;
|
|
4837
|
+
canvas.data.set(im.data.subarray(start, start + Math.min(4096, im.width - x) * 4), ((top + y) * totalW + left + x) * 4);
|
|
4838
|
+
}
|
|
4564
4839
|
}
|
|
4840
|
+
hasAlpha ||= im.hasAlpha;
|
|
4841
|
+
cursor += vertical ? im.height : im.width;
|
|
4565
4842
|
}
|
|
4566
|
-
return canvas;
|
|
4843
|
+
return { ...canvas, hasAlpha };
|
|
4567
4844
|
}
|
|
4568
4845
|
function inferOutputFormat(spec, fallback = "png") {
|
|
4569
4846
|
const prefixMatch = /^([a-zA-Z0-9]+):(.*)$/.exec(spec);
|
|
4570
4847
|
if (prefixMatch) {
|
|
4571
4848
|
const prefix = prefixMatch[1].toLowerCase();
|
|
4572
4849
|
const rest = prefixMatch[2];
|
|
4850
|
+
if (prefix === "rgba") throw new Error("unsupported output coder 'rgba'");
|
|
4573
4851
|
const fmt = extToImageFormat(prefix);
|
|
4574
4852
|
if (fmt) {
|
|
4575
4853
|
return { format: fmt, path: rest };
|
|
@@ -5101,6 +5379,7 @@ function* accumulateMagickHistogram(data, histogram) {
|
|
|
5101
5379
|
for (let i = 0; i < data.length; i += 4) {
|
|
5102
5380
|
if (i && i % 65536 === 0) yield;
|
|
5103
5381
|
for (let channel = 0; channel < 4; channel++) histogram[channel][data[i + channel]]++;
|
|
5382
|
+
if (histogram[4]) histogram[4][Math.round(257 * (0.212656 * data[i] + 0.715158 * data[i + 1] + 0.072186 * data[i + 2]))]++;
|
|
5104
5383
|
}
|
|
5105
5384
|
}
|
|
5106
5385
|
function* applyMagickLookup(image, lookup) {
|
|
@@ -5122,9 +5401,9 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5122
5401
|
const token = tokens[start];
|
|
5123
5402
|
if (!["-auto-level", "-auto-gamma", "-normalize", "-contrast-stretch", "-linear-stretch", "-equalize"].includes(token ?? "")) return;
|
|
5124
5403
|
let end = start;
|
|
5125
|
-
const argument = token === "-
|
|
5404
|
+
const argument = token === "-contrast-stretch" || token === "-linear-stretch" ? tokens[++end] ?? "0%x0%" : "";
|
|
5126
5405
|
const independent = state.channelExplicit, channels = independent ? state.channels : { r: true, g: true, b: true, a: false };
|
|
5127
|
-
return { end, lookup(histogram, image) {
|
|
5406
|
+
return { end, luminance: token === "-linear-stretch" || (token === "-normalize" || token === "-contrast-stretch" || token === "-equalize") && !independent, lookup(histogram, image) {
|
|
5128
5407
|
const lookup = Array.from({ length: 4 }, () => Uint8Array.from({ length: 256 }, (_, value) => value));
|
|
5129
5408
|
const count = Math.max(1, image.width * image.height);
|
|
5130
5409
|
if (token === "-auto-gamma") {
|
|
@@ -5151,36 +5430,59 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5151
5430
|
const range = maxs[channel] - mins[channel];
|
|
5152
5431
|
if (active[channel] && range > 0) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value - mins[channel]) / range * 255);
|
|
5153
5432
|
}
|
|
5154
|
-
} else if (token === "-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5433
|
+
} else if (token === "-linear-stretch") {
|
|
5434
|
+
const [black, white] = argument.split("x"), percentage = argument.includes("%");
|
|
5435
|
+
const lowTarget = (parseFloat(black || "0") || 0) * (percentage ? count / 100 : 1);
|
|
5436
|
+
const highTarget = white === void 0 ? count - lowTarget : (parseFloat(white) || 0) * (percentage ? count / 100 : 1);
|
|
5437
|
+
const bins = histogram[4];
|
|
5438
|
+
let low = 0, high = bins.length - 1, sum = 0;
|
|
5439
|
+
for (; low < bins.length - 1; low++) {
|
|
5440
|
+
sum += bins[low];
|
|
5441
|
+
if (sum >= lowTarget) break;
|
|
5442
|
+
}
|
|
5443
|
+
sum = 0;
|
|
5444
|
+
for (; high > 0; high--) {
|
|
5445
|
+
sum += bins[high];
|
|
5446
|
+
if (sum >= highTarget) break;
|
|
5447
|
+
}
|
|
5448
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5449
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5450
|
+
if (!active[channel]) continue;
|
|
5451
|
+
for (let value = 0; value < 256; value++) lookup[channel][value] = high === low ? value * 257 > low ? 255 : 0 : clampByteVal((value * 257 - low) / (high - low) * 255);
|
|
5452
|
+
}
|
|
5453
|
+
} else if (token === "-normalize" || token === "-contrast-stretch") {
|
|
5454
|
+
const [black, white] = argument.split("x"), percentage = argument.includes("%");
|
|
5455
|
+
const blackValue = parseFloat(black || "0") || 0, whiteValue = white === void 0 ? blackValue : parseFloat(white) || 0;
|
|
5456
|
+
const lowTarget = token === "-normalize" ? 0.02 * count : percentage ? blackValue / 100 * count : blackValue;
|
|
5457
|
+
const highTarget = token === "-normalize" ? 0.01 * count : percentage ? whiteValue / 100 * count : whiteValue;
|
|
5458
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5459
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5460
|
+
if (!active[channel]) continue;
|
|
5461
|
+
const bins = histogram[4] ?? histogram[channel], scale = histogram[4] ? 257 : 1;
|
|
5462
|
+
let low = 0, high = bins.length - 1, sum = 0;
|
|
5463
|
+
for (; low < bins.length - 1; low++) {
|
|
5464
|
+
sum += bins[low];
|
|
5465
|
+
if (sum > lowTarget) break;
|
|
5174
5466
|
}
|
|
5175
5467
|
sum = 0;
|
|
5468
|
+
for (; high > 0; high--) {
|
|
5469
|
+
sum += bins[high];
|
|
5470
|
+
if (sum > highTarget) break;
|
|
5471
|
+
}
|
|
5472
|
+
if (high > low) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value * scale - low) / (high - low) * 255);
|
|
5473
|
+
}
|
|
5474
|
+
} else if (token === "-equalize") {
|
|
5475
|
+
const active = [channels.r, channels.g, channels.b, (!independent || channels.a) && image.hasAlpha];
|
|
5476
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5477
|
+
if (!active[channel]) continue;
|
|
5478
|
+
const bins = histogram[4] ?? histogram[channel], scale = histogram[4] ? 257 : 1;
|
|
5479
|
+
const black = bins[0];
|
|
5480
|
+
if (count === black) continue;
|
|
5481
|
+
let cumulative = 0, bin = 0;
|
|
5176
5482
|
for (let value = 0; value < 256; value++) {
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
high = value;
|
|
5180
|
-
break;
|
|
5181
|
-
}
|
|
5483
|
+
for (; bin <= value * scale; bin++) cumulative += bins[bin];
|
|
5484
|
+
lookup[channel][value] = clampByteVal((cumulative - black) / (count - black) * 255);
|
|
5182
5485
|
}
|
|
5183
|
-
if (high > low) for (let value = 0; value < 256; value++) lookup[channel][value] = clampByteVal((value - low) / (high - low) * 255);
|
|
5184
5486
|
}
|
|
5185
5487
|
}
|
|
5186
5488
|
return lookup;
|
|
@@ -5189,7 +5491,25 @@ function parseMagickAnalysisOperation(tokens, state, start) {
|
|
|
5189
5491
|
function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
5190
5492
|
const state = { ...settings }, token = tokens[start];
|
|
5191
5493
|
let i = start, apply;
|
|
5192
|
-
if (token === "-
|
|
5494
|
+
if (token === "-colorspace" || token === "-grayscale") {
|
|
5495
|
+
const space = (tokens[++i] ?? "gray").toLowerCase();
|
|
5496
|
+
apply = function* (image) {
|
|
5497
|
+
if (!space.includes("gray") && !space.includes("grey") && space !== "rec709luma" && space !== "rec601luma") return image;
|
|
5498
|
+
const out = new Uint8Array(image.data);
|
|
5499
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5500
|
+
if (idx % 65536 === 0) yield;
|
|
5501
|
+
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]);
|
|
5502
|
+
out[idx] = out[idx + 1] = out[idx + 2] = gray;
|
|
5503
|
+
}
|
|
5504
|
+
const { data16: ignored, ...metadata } = image;
|
|
5505
|
+
return { ...metadata, data: out, space: "b-w", channels: image.hasAlpha ? 2 : 1 };
|
|
5506
|
+
};
|
|
5507
|
+
} else if (token === "-gamma") {
|
|
5508
|
+
const gamma = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
5509
|
+
const channels = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: true };
|
|
5510
|
+
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));
|
|
5511
|
+
apply = (image) => applyMagickLookup(image, lookup);
|
|
5512
|
+
} else if (token === "-alpha") {
|
|
5193
5513
|
const mode = (tokens[++i] ?? "on").toLowerCase();
|
|
5194
5514
|
if (mode === "remove") {
|
|
5195
5515
|
apply = function* (im) {
|
|
@@ -5253,10 +5573,10 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5253
5573
|
apply = (image) => applyMagickColorMatrixSteps(image, matrix, signal);
|
|
5254
5574
|
} else if (token === "-opaque" || token === "+opaque") {
|
|
5255
5575
|
const target = parseColor(tokens[++i] ?? "#000000");
|
|
5256
|
-
apply = (image) => applyMagickOpaqueSteps(image, target, state.fill, state.
|
|
5576
|
+
apply = (image) => applyMagickOpaqueSteps(image, target, state.fill, state.alphaFuzz, token === "+opaque", signal);
|
|
5257
5577
|
} else if (token === "-transparent" || token === "+transparent") {
|
|
5258
5578
|
const target = parseColor(tokens[++i] ?? "#ffffff");
|
|
5259
|
-
apply = (image) => applyMagickTransparentSteps(image, target, state.
|
|
5579
|
+
apply = (image) => applyMagickTransparentSteps(image, target, state.alphaFuzz, token === "+transparent", signal);
|
|
5260
5580
|
} else if (token === "-evaluate") {
|
|
5261
5581
|
const operation = tokens[++i] ?? "Add", value = tokens[++i] ?? "0";
|
|
5262
5582
|
apply = (image) => applyMagickEvaluateSteps(image, operation, value, state.channels, signal);
|
|
@@ -5266,7 +5586,7 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5266
5586
|
} else if (token === "-sepia-tone" || token === "-solarize") {
|
|
5267
5587
|
const value = tokens[++i] ?? (token === "-sepia-tone" ? "80%" : "50%");
|
|
5268
5588
|
apply = (image) => token === "-sepia-tone" ? applyMagickSepiaToneSteps(image, value, signal) : applyMagickSolarizeSteps(image, value, signal);
|
|
5269
|
-
} else if (token === "-posterize"
|
|
5589
|
+
} else if (token === "-posterize") {
|
|
5270
5590
|
const levels = Number(tokens[++i] ?? 8);
|
|
5271
5591
|
apply = (image) => applyMagickPosterizeSteps(image, levels, signal);
|
|
5272
5592
|
} else if (token === "-monochrome") {
|
|
@@ -5275,17 +5595,83 @@ function parseMagickPixelOperation(tokens, settings, start, signal) {
|
|
|
5275
5595
|
};
|
|
5276
5596
|
} else if (token === "-modulate") {
|
|
5277
5597
|
const parts = (tokens[++i] ?? "100,100,100").split(",").map(Number);
|
|
5278
|
-
const
|
|
5279
|
-
apply = (image)
|
|
5598
|
+
const brightness = (parts[0] ?? 100) / 100, saturation = (parts[1] ?? 100) / 100, hueShift = ((parts[2] ?? 100) - 100) * 3 / 100;
|
|
5599
|
+
apply = function* (image) {
|
|
5600
|
+
const out = new Uint8Array(image.data);
|
|
5601
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5602
|
+
if (idx % 65536 === 0) {
|
|
5603
|
+
signal?.throwIfAborted();
|
|
5604
|
+
yield;
|
|
5605
|
+
}
|
|
5606
|
+
const r = out[idx] / 255, g = out[idx + 1] / 255, b = out[idx + 2] / 255;
|
|
5607
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b), delta = max - min;
|
|
5608
|
+
const lightness = (max + min) / 2;
|
|
5609
|
+
const sat = delta === 0 ? 0 : delta / (lightness <= 0.5 ? max + min : 2 - max - min);
|
|
5610
|
+
let hue = delta === 0 ? 0 : max === r ? (g - b) / delta : max === g ? 2 + (b - r) / delta : 4 + (r - g) / delta;
|
|
5611
|
+
hue = ((hue + hueShift) % 6 + 6) % 6;
|
|
5612
|
+
const light = lightness * brightness;
|
|
5613
|
+
const chroma = (1 - Math.abs(2 * light - 1)) * sat * saturation;
|
|
5614
|
+
const secondary = chroma * (1 - Math.abs(hue % 2 - 1));
|
|
5615
|
+
const offset = light - chroma / 2;
|
|
5616
|
+
const red = hue < 1 || hue >= 5 ? chroma : hue < 2 || hue >= 4 ? secondary : 0;
|
|
5617
|
+
const green = hue < 1 ? secondary : hue < 3 ? chroma : hue < 4 ? secondary : 0;
|
|
5618
|
+
const blue = hue < 2 ? 0 : hue < 3 ? secondary : hue < 5 ? chroma : secondary;
|
|
5619
|
+
out[idx] = clampByteVal((red + offset) * 255);
|
|
5620
|
+
out[idx + 1] = clampByteVal((green + offset) * 255);
|
|
5621
|
+
out[idx + 2] = clampByteVal((blue + offset) * 255);
|
|
5622
|
+
}
|
|
5623
|
+
const { data16: ignored, ...metadata } = image;
|
|
5624
|
+
return { ...metadata, data: out };
|
|
5625
|
+
};
|
|
5280
5626
|
} else if (token === "-brightness-contrast") {
|
|
5281
|
-
const
|
|
5282
|
-
|
|
5627
|
+
const [brightness = 0, contrast = 0] = (tokens[++i] ?? "0x0").split("x").map((value) => parseFloat(value) || 0);
|
|
5628
|
+
const slope = contrast < 0 ? 1 + contrast / 100 : 100 / Math.max(1e-12, 100 - contrast);
|
|
5629
|
+
const offset = (brightness / 100 * 255 - 127.5) * slope + 127.5;
|
|
5630
|
+
const channels = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: true };
|
|
5631
|
+
apply = function* (image) {
|
|
5632
|
+
const out = new Uint8Array(image.data);
|
|
5633
|
+
const mask = [channels.r, channels.g, channels.b, channels.a && image.hasAlpha];
|
|
5634
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5635
|
+
if (idx % 65536 === 0) yield;
|
|
5636
|
+
for (let channel = 0; channel < 4; channel++) {
|
|
5637
|
+
if (mask[channel]) out[idx + channel] = clampByteVal(out[idx + channel] * slope + offset);
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
return { ...image, data: out };
|
|
5641
|
+
};
|
|
5283
5642
|
} else if (token === "-threshold") {
|
|
5284
5643
|
const raw = tokens[++i] ?? "50%", value = Math.round(raw.endsWith("%") ? parseFloat(raw) / 100 * 255 : parseFloat(raw));
|
|
5285
5644
|
apply = (image) => thresholdImageSteps(image, value, true);
|
|
5286
|
-
} else if (token === "-
|
|
5287
|
-
i
|
|
5288
|
-
|
|
5645
|
+
} else if (token === "-colorize") {
|
|
5646
|
+
const strength = parseFloat(tokens[++i] ?? "100") / 100;
|
|
5647
|
+
const fill = [state.fill.r, state.fill.g, state.fill.b];
|
|
5648
|
+
apply = function* (image) {
|
|
5649
|
+
const out = new Uint8Array(image.data);
|
|
5650
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5651
|
+
if (idx % 65536 === 0) yield;
|
|
5652
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
5653
|
+
out[idx + channel] = clampByteVal(out[idx + channel] * (1 - strength) + fill[channel] * strength);
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
return { ...image, data: out };
|
|
5657
|
+
};
|
|
5658
|
+
} else if (token === "-tint") {
|
|
5659
|
+
const strength = parseFloat(tokens[++i] ?? "100") / 100;
|
|
5660
|
+
const fill = [state.fill.r, state.fill.g, state.fill.b];
|
|
5661
|
+
const intensity = 0.212656 * state.fill.r + 0.715158 * state.fill.g + 0.072186 * state.fill.b;
|
|
5662
|
+
const vector = fill.map((channel) => channel * strength - intensity);
|
|
5663
|
+
apply = function* (image) {
|
|
5664
|
+
const out = new Uint8Array(image.data);
|
|
5665
|
+
for (let idx = 0; idx < out.length; idx += 4) {
|
|
5666
|
+
if (idx % 65536 === 0) yield;
|
|
5667
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
5668
|
+
const value = out[idx + channel];
|
|
5669
|
+
const weight = value / 255 - 0.5;
|
|
5670
|
+
out[idx + channel] = clampByteVal(value + vector[channel] * (1 - 4 * weight * weight));
|
|
5671
|
+
}
|
|
5672
|
+
}
|
|
5673
|
+
return { ...image, data: out };
|
|
5674
|
+
};
|
|
5289
5675
|
} else if (token === "-negate" || token === "+negate") {
|
|
5290
5676
|
const onlyGray = token === "+negate";
|
|
5291
5677
|
const ch = state.channelExplicit ? state.channels : { r: true, g: true, b: true, a: false };
|
|
@@ -5538,10 +5924,16 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5538
5924
|
} else if (analysisOperation) {
|
|
5539
5925
|
i = analysisOperation.end;
|
|
5540
5926
|
stack = yield* mapSteps(stack, function* (image) {
|
|
5541
|
-
const histogram = Array.from({ length: 4 }, () => new Float64Array(256));
|
|
5927
|
+
const histogram = Array.from({ length: analysisOperation.luminance ? 5 : 4 }, (_, channel) => new Float64Array(channel === 4 ? 65536 : 256));
|
|
5542
5928
|
yield* accumulateMagickHistogram(image.data, histogram);
|
|
5543
5929
|
return yield* applyMagickLookup(image, analysisOperation.lookup(histogram, image));
|
|
5544
5930
|
});
|
|
5931
|
+
} else if (t === "-colors") {
|
|
5932
|
+
const colors = Number(tokens[++i]);
|
|
5933
|
+
stack = yield* mapSteps(stack, (image) => {
|
|
5934
|
+
const { data16: ignored, ...pixels } = image;
|
|
5935
|
+
return applyMagickRasterSteps(pixels, quantizePixelSteps(image.data.length, colors), signal);
|
|
5936
|
+
});
|
|
5545
5937
|
} else if (pixelOperation) {
|
|
5546
5938
|
i = pixelOperation.end;
|
|
5547
5939
|
stack = yield* mapSteps(stack, pixelOperation.apply);
|
|
@@ -5765,18 +6157,6 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5765
6157
|
stack = yield* mapSteps(stack, function* (im) {
|
|
5766
6158
|
return yield* applyExifOrientationSteps(im);
|
|
5767
6159
|
});
|
|
5768
|
-
} else if (t === "-colorspace" || t === "-grayscale") {
|
|
5769
|
-
const cs = (tokens[++i] ?? "gray").toLowerCase();
|
|
5770
|
-
if (cs.includes("gray") || cs.includes("grey") || cs === "rec709luma" || cs === "rec601luma") {
|
|
5771
|
-
stack = yield* mapSteps(stack, function* (im) {
|
|
5772
|
-
return yield* grayscaleImageSteps(im);
|
|
5773
|
-
});
|
|
5774
|
-
}
|
|
5775
|
-
} else if (t === "-gamma") {
|
|
5776
|
-
const gammaVal = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
5777
|
-
stack = yield* mapSteps(stack, function* (im) {
|
|
5778
|
-
return yield* gammaImageSteps(im, gammaVal, gammaVal);
|
|
5779
|
-
});
|
|
5780
6160
|
} else if (t === "-raise" || t === "+raise") {
|
|
5781
6161
|
const g = parseMagickGeometry(tokens[++i] ?? "4");
|
|
5782
6162
|
const bw = Math.max(1, Math.round(g.width ?? 4));
|
|
@@ -5979,14 +6359,19 @@ function* evaluatePipelineTokensSteps(tokens, files, state, parentStack = [], st
|
|
|
5979
6359
|
}
|
|
5980
6360
|
stack = [appended];
|
|
5981
6361
|
} else if (t === "-flatten" || t === "-mosaic" || t === "-layers" && ["flatten", "merge", "mosaic"].includes((tokens[i + 1] ?? "").toLowerCase())) {
|
|
6362
|
+
const flatten = t === "-flatten" || t === "-layers" && tokens[i + 1]?.toLowerCase() === "flatten";
|
|
5982
6363
|
if (t === "-layers")
|
|
5983
6364
|
i++;
|
|
5984
6365
|
if (stack.length > 0) {
|
|
5985
6366
|
const oldStack = stack;
|
|
5986
|
-
const maxW = Math.max(...oldStack.map((im) => im.width));
|
|
5987
|
-
const maxH = Math.max(...oldStack.map((im) => im.height));
|
|
6367
|
+
const maxW = flatten ? oldStack[0].width : Math.max(...oldStack.map((im) => im.width));
|
|
6368
|
+
const maxH = flatten ? oldStack[0].height : Math.max(...oldStack.map((im) => im.height));
|
|
5988
6369
|
const canvas = yield* createSolidRgbaImageSteps(maxW, maxH, state.background);
|
|
5989
|
-
const layers =
|
|
6370
|
+
const layers = [];
|
|
6371
|
+
for (const im of oldStack) {
|
|
6372
|
+
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;
|
|
6373
|
+
layers.push(rgbaToCompositeLayer(clipped, 0, 0, state.compose));
|
|
6374
|
+
}
|
|
5990
6375
|
const flattened = yield* compositeImageSteps(canvas, layers);
|
|
5991
6376
|
if (canvas.data.buffer !== flattened.data.buffer) detachRgbaBuffer(canvas.data);
|
|
5992
6377
|
for (const im of oldStack) {
|
|
@@ -6285,7 +6670,8 @@ async function transformStoredMagickCoordinates(image, backend, token, geometry,
|
|
|
6285
6670
|
return { ...image, position, width, height, ...token === "-splice" ? { hasAlpha: true } : {} };
|
|
6286
6671
|
}
|
|
6287
6672
|
async function compositeStoredMagick(base, overlay, backend, signal, left, top, mode, args, generic = false, inPlace = false, montageRounding = false) {
|
|
6288
|
-
|
|
6673
|
+
const compositeMode = magickCompositeMode(mode);
|
|
6674
|
+
let hasAlpha = base.hasAlpha || !generic && !compositeMode.delegated && !["difference", "src", "source", "copy", "xor"].includes(compositeMode.name);
|
|
6289
6675
|
const position = inPlace ? base.position : backend.storage.allocate(base.width * base.height * 4);
|
|
6290
6676
|
for (let start = 0; !inPlace && start < base.width * base.height * 4; start += 16384) {
|
|
6291
6677
|
await yieldTurn9(signal);
|
|
@@ -6330,7 +6716,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6330
6716
|
await probeImageArguments(argv, input, stdinBytes, signal);
|
|
6331
6717
|
return drainSteps(runConvertCliSteps(argv, /* @__PURE__ */ new Map(), stdinBytes, signal), signal);
|
|
6332
6718
|
}
|
|
6333
|
-
|
|
6719
|
+
let output;
|
|
6334
6720
|
await yieldTurn9(signal);
|
|
6335
6721
|
let state = createDefaultState();
|
|
6336
6722
|
const tokens = argv.slice(0, -1), scopes = [];
|
|
@@ -6347,6 +6733,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6347
6733
|
});
|
|
6348
6734
|
};
|
|
6349
6735
|
try {
|
|
6736
|
+
output = inferOutputFormat(outSpec, "png");
|
|
6350
6737
|
for (let i = 0; i < tokens.length; i++) {
|
|
6351
6738
|
if (i && i % 64 === 0) await yieldTurn9(signal);
|
|
6352
6739
|
const token = tokens[i];
|
|
@@ -6486,6 +6873,11 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6486
6873
|
});
|
|
6487
6874
|
continue;
|
|
6488
6875
|
}
|
|
6876
|
+
if (!operandsOnly && token === "-colors") {
|
|
6877
|
+
const colors = Number(tokens[++i]);
|
|
6878
|
+
steps.push(async (image, backend) => image ? transformStoredMagickRaster(image, backend, quantizePixelSteps(image.width * image.height * 4, colors), signal) : void 0);
|
|
6879
|
+
continue;
|
|
6880
|
+
}
|
|
6489
6881
|
const pixelOperation = operandsOnly ? void 0 : parseMagickPixelOperation(tokens, state, i, signal);
|
|
6490
6882
|
if (pixelOperation) {
|
|
6491
6883
|
i = pixelOperation.end;
|
|
@@ -6508,7 +6900,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6508
6900
|
i = analysisOperation.end;
|
|
6509
6901
|
steps.push(async (image, backend) => {
|
|
6510
6902
|
if (!image) return;
|
|
6511
|
-
const histogram = Array.from({ length: 4 }, () => new Float64Array(256)), size = image.width * image.height * 4;
|
|
6903
|
+
const histogram = Array.from({ length: analysisOperation.luminance ? 5 : 4 }, (_, channel) => new Float64Array(channel === 4 ? 65536 : 256)), size = image.width * image.height * 4;
|
|
6512
6904
|
for (let offset = 0; offset < size; offset += 16384) {
|
|
6513
6905
|
if (offset % 1048576 === 0) await yieldTurn9(signal);
|
|
6514
6906
|
await drainSteps(accumulateMagickHistogram(await backend.storage.read(image.position + offset, Math.min(16384, size - offset)), histogram), signal);
|
|
@@ -6519,6 +6911,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6519
6911
|
continue;
|
|
6520
6912
|
}
|
|
6521
6913
|
if (!operandsOnly && (["-append", "+append", "-flatten", "-mosaic", "-composite"].includes(token) || token === "-layers" && ["flatten", "merge", "mosaic"].includes((tokens[i + 1] ?? "").toLowerCase()))) {
|
|
6914
|
+
const flatten = token === "-flatten" || token === "-layers" && tokens[i + 1]?.toLowerCase() === "flatten";
|
|
6522
6915
|
if (token === "-layers") i++;
|
|
6523
6916
|
const settings = { ...state };
|
|
6524
6917
|
stackStep(async (stack, backend) => {
|
|
@@ -6543,7 +6936,7 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6543
6936
|
const append = token.endsWith("append"), vertical = token === "-append";
|
|
6544
6937
|
if (append && stack.length === 1) return stack;
|
|
6545
6938
|
let width = 0, height = 0;
|
|
6546
|
-
for (let n = 0; n < stack.length; n++) {
|
|
6939
|
+
for (let n = 0; n < (flatten ? 1 : stack.length); n++) {
|
|
6547
6940
|
await yieldTurn9(signal);
|
|
6548
6941
|
const frame = await stack.get(n);
|
|
6549
6942
|
width = append && !vertical ? width + frame.width : Math.max(width, frame.width);
|
|
@@ -6553,7 +6946,17 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6553
6946
|
let cursor = 0;
|
|
6554
6947
|
for (let n = 0; n < stack.length; n++) {
|
|
6555
6948
|
const frame = await stack.get(n), offset = resolveGravityOffset(vertical ? width - frame.width : 0, vertical ? 0 : height - frame.height, settings.gravity);
|
|
6556
|
-
|
|
6949
|
+
if (append) {
|
|
6950
|
+
const left = vertical ? offset.left : cursor, top = vertical ? cursor : offset.top;
|
|
6951
|
+
for (let y = 0; y < frame.height; y++) {
|
|
6952
|
+
for (let x = 0; x < frame.width; x += 4096) {
|
|
6953
|
+
await yieldTurn9(signal);
|
|
6954
|
+
const pixels = await backend.storage.read(frame.position + (y * frame.width + x) * 4, Math.min(4096, frame.width - x) * 4);
|
|
6955
|
+
await backend.storage.write(result.position + ((top + y) * width + left + x) * 4, pixels);
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
result = { ...result, hasAlpha: result.hasAlpha || frame.hasAlpha };
|
|
6959
|
+
} else result = await compositeStoredMagick(result, frame, backend, signal, 0, 0, settings.compose, void 0, true, true);
|
|
6557
6960
|
cursor += vertical ? frame.height : frame.width;
|
|
6558
6961
|
}
|
|
6559
6962
|
stack.length = 0;
|
|
@@ -6769,12 +7172,6 @@ async function tryConvertFilesResult(argv, input, stdinBytes, signal, analysis)
|
|
|
6769
7172
|
} else if (!operandsOnly && token === "-rotate") {
|
|
6770
7173
|
const angle = Number(tokens[++i] ?? 0), background = state.background;
|
|
6771
7174
|
transform(() => ({ kind: "rotate", angle, background }));
|
|
6772
|
-
} else if (!operandsOnly && token === "-gamma") {
|
|
6773
|
-
const gamma = Math.max(0.1, Number(tokens[++i] ?? 1));
|
|
6774
|
-
transform(() => ({ kind: "gamma", gamma, gammaOut: gamma }));
|
|
6775
|
-
} else if (!operandsOnly && ["-colorspace", "-grayscale"].includes(token)) {
|
|
6776
|
-
const space = (tokens[++i] ?? "gray").toLowerCase();
|
|
6777
|
-
if (space.includes("gray") || space.includes("grey") || space === "rec709luma" || space === "rec601luma") transform(() => ({ kind: "grayscale" }));
|
|
6778
7175
|
} else if (!operandsOnly && ["-blur", "-gaussian-blur", "-sharpen", "-unsharp"].includes(token)) {
|
|
6779
7176
|
const geometry = parseMagickGeometry(tokens[++i] ?? "0x1"), sigma = Math.max(0.3, geometry.height ?? geometry.width ?? 1);
|
|
6780
7177
|
const kind = token === "-blur" || token === "-gaussian-blur" ? "blur" : "sharpen";
|