emf-converter 4.8.2 → 4.8.4
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/README.md +1 -1
- package/dist/browser.js +206 -71
- package/dist/browser.mjs +206 -71
- package/dist/index.js +206 -71
- package/dist/index.mjs +206 -71
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ Everything below is verified against output painted by Windows itself; `src/gdi-
|
|
|
214
214
|
|
|
215
215
|
Everything is measured against output painted by Windows itself; `src/gdi-parity.fixture.test.ts` holds the exact per-fixture bounds. Open work items are tracked in [docs/outstanding-work.md](docs/outstanding-work.md).
|
|
216
216
|
|
|
217
|
-
- **Colour adjustment and image effects**: all eleven EMF+ image effects are supported. Complete native tables cover every legal ColorCurve intensity. Deferred draws retain their effects. JPEG, GIF and TIFF have bundled decoders for environments without Canvas. HSL, tint, red-eye
|
|
217
|
+
- **Colour adjustment and image effects**: all eleven EMF+ image effects are supported. Complete native tables cover every legal ColorCurve intensity. Deferred draws retain their effects. JPEG, GIF and TIFF have bundled decoders for environments without Canvas. Blur and sharpen are exact; HSL, tint, red-eye and GDI colour adjustment retain measured differences; see [the detailed limitations](docs/limitations.md) and the native fixture bounds.
|
|
218
218
|
- **Pen transforms**: EMF+ pen transforms (uniform, nonuniform and skewed) match GDI+ exactly on the 17 pen-transform fixtures, including dashes, which GDI+ lays out along the path in world space at the untransformed pen width. Native fixtures verify dashes under both uniform and non-uniform scales. GDI+ rejects singular pen transforms; the converter ignores such malformed transform data. Square, round, diamond and arrow anchor caps use their native shapes; the new centered-cap fixtures match every pixel with and without antialiasing.
|
|
219
219
|
- **Text**: ANSI, multi-string and small-text records now render; the Windows text fixtures retain a few glyph-edge differences and a `PolyTextOut` C1 control-glyph difference (under 0.1% of pixels; bounds in `src/emf-text-records.fixture.test.ts`). ANSI decoding uses the host's `TextDecoder` for common Windows code pages; Johab and OEM CP437 use bundled Windows mappings. Vertical `ETO_PDY` advances work with and without the `fonts` engine. Without `fonts`, SVG text measurements use deterministic estimates, so precise justification requires supplying the matching fonts.
|
|
220
220
|
- **Wide pens and paths**: flat-capped GDI pens 7 px and wider can differ by a few pixels at round joins, and dashed wide curves follow `WidenPath` (exact for round and flat caps at one logical unit per pixel; square caps differ slightly at path ends); at most 0.2% of pixels on the fixtures. `EMR_WIDENPATH` does not reproduce the extra inner join triangles GDI's own `WidenPath` emits (visible only when the widened outline is itself stroked). EMF+ 1-pixel antialiased lines can differ by one antialiasing sample at their ends, some closed widened outlines by one sample along an edge, and Inset or compound pens on closed figures are approximate.
|
package/dist/browser.js
CHANGED
|
@@ -16810,14 +16810,21 @@ function blurKernel(radius) {
|
|
|
16810
16810
|
k[0] = 1;
|
|
16811
16811
|
return k;
|
|
16812
16812
|
}
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
|
|
16819
|
-
|
|
16820
|
-
|
|
16813
|
+
const step = truncF32(truncF32(1.4) / Math.fround(radius));
|
|
16814
|
+
const w = new Float64Array(taps + 1);
|
|
16815
|
+
w[0] = 1;
|
|
16816
|
+
let offset = 0;
|
|
16817
|
+
let sum = 1;
|
|
16818
|
+
for (let i = 1; i <= taps; i++) {
|
|
16819
|
+
offset = truncF32(offset + step);
|
|
16820
|
+
w[i] = truncF32(Math.exp(-truncF32(offset * offset)));
|
|
16821
|
+
sum = truncF32(sum + truncF32(2 * w[i]));
|
|
16822
|
+
}
|
|
16823
|
+
const scale = truncF32(256 / sum);
|
|
16824
|
+
for (let i = 0; i <= taps; i++) {
|
|
16825
|
+
const weight = truncF32(w[i] * scale) / 256;
|
|
16826
|
+
k[taps + i] = weight;
|
|
16827
|
+
k[taps - i] = weight;
|
|
16821
16828
|
}
|
|
16822
16829
|
return k;
|
|
16823
16830
|
}
|
|
@@ -17039,13 +17046,17 @@ function expandedBlur(rgba, width, height, radius, x0, y0, x1, y1, cropped) {
|
|
|
17039
17046
|
const blurred = blurReduction(radius) > 1 ? effectBlur(buf, bw, bh, radius, cropped ? { x: r, y: r, w: x1 - x0, h: y1 - y0 } : void 0, true) : gdipBlur(buf, bw, bh, radius, cropped ? r : 0, cropped, Math.ceil((y1 - y0) / (x1 - x0)));
|
|
17040
17047
|
const w = x1 - x0;
|
|
17041
17048
|
const out = new Uint8ClampedArray(w * (y1 - y0) * 4);
|
|
17049
|
+
const halo = new Uint8ClampedArray((y1 - y0) * 4);
|
|
17042
17050
|
for (let y = 0; y < y1 - y0; y++) {
|
|
17043
17051
|
const from = ((y + r) * bw + r) * 4;
|
|
17044
17052
|
for (let i = 0; i < w * 4; i++) {
|
|
17045
17053
|
out[y * w * 4 + i] = Math.round(blurred[from + i]);
|
|
17046
17054
|
}
|
|
17055
|
+
for (let i = 0; i < 4; i++) {
|
|
17056
|
+
halo[y * 4 + i] = Math.round(blurred[from + w * 4 + i]);
|
|
17057
|
+
}
|
|
17047
17058
|
}
|
|
17048
|
-
return out;
|
|
17059
|
+
return { out, halo };
|
|
17049
17060
|
}
|
|
17050
17061
|
function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
17051
17062
|
if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
@@ -17078,7 +17089,12 @@ function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
|
17078
17089
|
};
|
|
17079
17090
|
if (src && effect.kind === "blur" && effect.expandEdge) {
|
|
17080
17091
|
const cropped = Math.ceil(src.x + src.w) < width && Math.ceil(src.y + src.h) < height;
|
|
17081
|
-
|
|
17092
|
+
const { out: out2, halo } = expandedBlur(rgba, width, height, effect.radius, x0, y0, x1, y1, cropped);
|
|
17093
|
+
const region2 = result(out2);
|
|
17094
|
+
if (shownWidth === w && shownHeight === h) {
|
|
17095
|
+
region2.haloRight = halo;
|
|
17096
|
+
}
|
|
17097
|
+
return region2;
|
|
17082
17098
|
}
|
|
17083
17099
|
let region = rgba;
|
|
17084
17100
|
if (w !== width || h !== height) {
|
|
@@ -20450,6 +20466,22 @@ function cubicIntegral(t, a) {
|
|
|
20450
20466
|
}
|
|
20451
20467
|
return Math.sign(t) * v;
|
|
20452
20468
|
}
|
|
20469
|
+
var FAR_FADE = [1, 1, 0.985, 0.95, 0.91, 0.85, 0.8, 0.745, 0.69, 0.63, 0.56, 0.5, 0.43, 0.35, 0.28, 0.22, 0.16, 0.11, 0.065, 0.03, 0];
|
|
20470
|
+
function fadeAt(d, bilinear) {
|
|
20471
|
+
if (d >= 1) {
|
|
20472
|
+
return 0;
|
|
20473
|
+
}
|
|
20474
|
+
if (bilinear) {
|
|
20475
|
+
return 1 - Math.max(0, d);
|
|
20476
|
+
}
|
|
20477
|
+
const x = Math.max(0, d) * 20;
|
|
20478
|
+
const i = Math.floor(x);
|
|
20479
|
+
return FAR_FADE[i] + (FAR_FADE[i + 1] - FAR_FADE[i]) * (x - i);
|
|
20480
|
+
}
|
|
20481
|
+
function nearSourceExtent(deviceLen, srcLen) {
|
|
20482
|
+
const d = Math.ceil(deviceLen - 1e-6) - srcLen;
|
|
20483
|
+
return d >= -1 && d <= 1;
|
|
20484
|
+
}
|
|
20453
20485
|
var BICUBIC_A = -0.5;
|
|
20454
20486
|
var HQ_BICUBIC_A = -1;
|
|
20455
20487
|
function axisFilter(kernel, scale, copy) {
|
|
@@ -20554,13 +20586,39 @@ function mirroredOrigin(o) {
|
|
|
20554
20586
|
return f === 0 ? o : Math.floor(o) + 1 - f;
|
|
20555
20587
|
}
|
|
20556
20588
|
function resampleImage(rgba, width, height, spec, surface) {
|
|
20589
|
+
if (spec.rightHalo && spec.rightHalo.length === height * 4) {
|
|
20590
|
+
const grown = new Uint8ClampedArray((width + 1) * height * 4);
|
|
20591
|
+
for (let y = 0; y < height; y++) {
|
|
20592
|
+
grown.set(rgba.subarray(y * width * 4, (y + 1) * width * 4), y * (width + 1) * 4);
|
|
20593
|
+
grown.set(spec.rightHalo.subarray(y * 4, y * 4 + 4), (y * (width + 1) + width) * 4);
|
|
20594
|
+
}
|
|
20595
|
+
return resampleImage(grown, width + 1, height, { ...spec, rightHalo: void 0 }, surface);
|
|
20596
|
+
}
|
|
20557
20597
|
if (spec.kernel === "nearest" && !spec.wrap) {
|
|
20558
20598
|
return resampleNearest(rgba, width, height, spec, surface);
|
|
20559
20599
|
}
|
|
20560
20600
|
let m = snapToDeviceGrid(spec);
|
|
20561
|
-
|
|
20601
|
+
let kernel = spec.kernel;
|
|
20562
20602
|
const hq = kernel === "hq-bilinear" || kernel === "hq-bicubic";
|
|
20563
20603
|
const axisAligned = m[1] === 0 && m[2] === 0;
|
|
20604
|
+
let farFade = false;
|
|
20605
|
+
let shiftX = 0;
|
|
20606
|
+
let shiftY = 0;
|
|
20607
|
+
if (hq && !axisAligned) {
|
|
20608
|
+
const extentU = Math.hypot(m[0], m[1]) * spec.srcW;
|
|
20609
|
+
const extentV = Math.hypot(m[2], m[3]) * spec.srcH;
|
|
20610
|
+
if (nearSourceExtent(extentU, spec.srcW) || nearSourceExtent(extentV, spec.srcH)) {
|
|
20611
|
+
kernel = kernel === "hq-bicubic" ? "bicubic" : "bilinear";
|
|
20612
|
+
} else {
|
|
20613
|
+
farFade = true;
|
|
20614
|
+
if (spec.halfPixelOffset) {
|
|
20615
|
+
const lu = Math.hypot(m[0], m[1]);
|
|
20616
|
+
const lv = Math.hypot(m[2], m[3]);
|
|
20617
|
+
shiftX = 0.5 * m[0] / lu + 0.5 * m[2] / lv;
|
|
20618
|
+
shiftY = 0.5 * m[1] / lu + 0.5 * m[3] / lv;
|
|
20619
|
+
}
|
|
20620
|
+
}
|
|
20621
|
+
}
|
|
20564
20622
|
if (hq && axisAligned) {
|
|
20565
20623
|
const ox = m[0] * spec.srcX + m[4];
|
|
20566
20624
|
m = [m[0], 0, 0, m[3], m[4] + mirroredOrigin(ox) - ox, m[5]];
|
|
@@ -20615,6 +20673,8 @@ function resampleImage(rgba, width, height, spec, surface) {
|
|
|
20615
20673
|
const vMax = spec.srcY + spec.srcH;
|
|
20616
20674
|
const wu = [];
|
|
20617
20675
|
const wv = [];
|
|
20676
|
+
const gradU = Math.hypot(inv2[0], inv2[2]);
|
|
20677
|
+
const gradV = Math.hypot(inv2[1], inv2[3]);
|
|
20618
20678
|
const c = spec.clampArgb ?? 0;
|
|
20619
20679
|
const edge = {
|
|
20620
20680
|
wrap: spec.wrap,
|
|
@@ -20626,8 +20686,8 @@ function resampleImage(rgba, width, height, spec, surface) {
|
|
|
20626
20686
|
const py = by0 + j + o;
|
|
20627
20687
|
for (let i = 0; i < w; i++) {
|
|
20628
20688
|
const px = bx0 + i + o;
|
|
20629
|
-
const u = inv2[0] * px + inv2[2] * py + inv2[4];
|
|
20630
|
-
const v = inv2[1] * px + inv2[3] * py + inv2[5];
|
|
20689
|
+
const u = inv2[0] * (px - shiftX) + inv2[2] * (py - shiftY) + inv2[4];
|
|
20690
|
+
const v = inv2[1] * (px - shiftX) + inv2[3] * (py - shiftY) + inv2[5];
|
|
20631
20691
|
const cx = px + COVERAGE_NUDGE_X;
|
|
20632
20692
|
const cy = py + COVERAGE_NUDGE_Y;
|
|
20633
20693
|
const eu = inv2[0] * cx + inv2[2] * cy + inv2[4];
|
|
@@ -20635,31 +20695,35 @@ function resampleImage(rgba, width, height, spec, surface) {
|
|
|
20635
20695
|
if (eu < spec.srcX || ev < spec.srcY || eu >= uMax || ev >= vMax) {
|
|
20636
20696
|
continue;
|
|
20637
20697
|
}
|
|
20638
|
-
const
|
|
20639
|
-
const
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
|
|
20698
|
+
const box = { x0: 0, y0: 0, x1: width, y1: height };
|
|
20699
|
+
const sampleAt = (cu, cv) => {
|
|
20700
|
+
const iu0 = Math.ceil(cu - fu.radius);
|
|
20701
|
+
const iu1 = Math.floor(cu + fu.radius);
|
|
20702
|
+
const iv0 = Math.ceil(cv - fv.radius);
|
|
20703
|
+
const iv1 = Math.floor(cv + fv.radius);
|
|
20704
|
+
wu.length = 0;
|
|
20705
|
+
wv.length = 0;
|
|
20706
|
+
for (let t = iu0; t <= iu1; t++) {
|
|
20707
|
+
wu.push(fu.weight(t, cu));
|
|
20708
|
+
}
|
|
20709
|
+
for (let t = iv0; t <= iv1; t++) {
|
|
20710
|
+
wv.push(fv.weight(t, cv));
|
|
20711
|
+
}
|
|
20712
|
+
return blendSeparable(rgba, width, box, iu0, wu, iv0, wv, kernel === "bicubic", edge);
|
|
20713
|
+
};
|
|
20714
|
+
let [r, g, b, a] = sampleAt(u, v);
|
|
20715
|
+
if (farFade && !spec.wrap && a > 0) {
|
|
20716
|
+
const du = (uMax - u) / gradU;
|
|
20717
|
+
const dv = (vMax - v) / gradV;
|
|
20718
|
+
const k = 1 - (du < 1 ? fadeAt(du, kernel === "hq-bilinear") * (sampleAt(uMax, v)[3] / a) : 0) - (dv < 1 ? fadeAt(dv, kernel === "hq-bilinear") * (sampleAt(u, vMax)[3] / a) : 0);
|
|
20719
|
+
if (k < 1) {
|
|
20720
|
+
const f = Math.max(0, k);
|
|
20721
|
+
r *= f;
|
|
20722
|
+
g *= f;
|
|
20723
|
+
b *= f;
|
|
20724
|
+
a *= f;
|
|
20725
|
+
}
|
|
20726
|
+
}
|
|
20663
20727
|
if (a <= 0) {
|
|
20664
20728
|
continue;
|
|
20665
20729
|
}
|
|
@@ -23486,7 +23550,8 @@ function effectedDraw(img, bitmap, effect, draw, source) {
|
|
|
23486
23550
|
srcY,
|
|
23487
23551
|
srcW,
|
|
23488
23552
|
srcH,
|
|
23489
|
-
toDevice: mulMatrix(draw.resample.toDevice, shift)
|
|
23553
|
+
toDevice: mulMatrix(draw.resample.toDevice, shift),
|
|
23554
|
+
rightHalo: region.haloRight
|
|
23490
23555
|
}
|
|
23491
23556
|
}
|
|
23492
23557
|
};
|
|
@@ -31425,6 +31490,15 @@ function illuminantCube(index) {
|
|
|
31425
31490
|
}
|
|
31426
31491
|
return cube;
|
|
31427
31492
|
}
|
|
31493
|
+
var ILLUMINANT_MATRICES = {
|
|
31494
|
+
1: [[1.64737, -0.59174, -0.05494], [0.02963, 0.96126, 881e-5], [0.2911, 0.40189, 0.30769]],
|
|
31495
|
+
2: [[1.22319, -0.17765, -0.04515], [275e-5, 0.99054, 645e-5], [0.05514, 0.12795, 0.81816]],
|
|
31496
|
+
3: [[1.02833, -0.01775, -0.01086], [861e-5, 0.98431, 728e-5], [-0.01616, -0.0448, 1.05995]],
|
|
31497
|
+
4: [[1.17271, -0.14059, -0.03174], [-474e-5, 1.00266, 183e-5], [0.05783, 0.14663, 0.79683]],
|
|
31498
|
+
5: [[1.10431, -0.08327, -0.02067], [-336e-5, 1.0025, 67e-5], [0.03344, 0.09072, 0.87702]],
|
|
31499
|
+
7: [[0.92515, 0.05628, 0.01822], [318e-5, 0.9967, 34e-5], [-0.02086, -0.06817, 1.08783]],
|
|
31500
|
+
8: [[1.31275, -0.26259, -0.04977], [-244e-5, 0.99695, 524e-5], [0.10854, 0.23443, 0.65827]]
|
|
31501
|
+
};
|
|
31428
31502
|
function hasIlluminant(ca) {
|
|
31429
31503
|
return illuminantCube(ca.illuminant) !== null;
|
|
31430
31504
|
}
|
|
@@ -31495,9 +31569,9 @@ var BRIGHTNESS_STEP = 0.95625;
|
|
|
31495
31569
|
var COLORFULNESS_SCALE = 1 / 100;
|
|
31496
31570
|
var TINT_RADIANS = -Math.PI / 3;
|
|
31497
31571
|
var RGB_TO_XYZ = [
|
|
31498
|
-
[0.
|
|
31499
|
-
[0.
|
|
31500
|
-
[0.
|
|
31572
|
+
[0.4123908, 0.3575843, 0.1804808],
|
|
31573
|
+
[0.212639, 0.7151687, 0.0721923],
|
|
31574
|
+
[0.0193308, 0.1191948, 0.9505322]
|
|
31501
31575
|
];
|
|
31502
31576
|
var XYZ_TO_RGB = invert3(RGB_TO_XYZ);
|
|
31503
31577
|
var WHITE = RGB_TO_XYZ.map((row2) => row2[0] + row2[1] + row2[2]);
|
|
@@ -31525,13 +31599,13 @@ function adjustChroma(r, g, b, scale, angle) {
|
|
|
31525
31599
|
const z = RGB_TO_XYZ[2][0] * r + RGB_TO_XYZ[2][1] * g + RGB_TO_XYZ[2][2] * b;
|
|
31526
31600
|
const d = x + 15 * y + 3 * z;
|
|
31527
31601
|
if (d <= 1e-9 || y <= 1e-9) {
|
|
31528
|
-
return [r, g, b];
|
|
31602
|
+
return [clamp255(r), clamp255(g), clamp255(b)];
|
|
31529
31603
|
}
|
|
31530
31604
|
const du = 4 * x / d - WHITE_U;
|
|
31531
31605
|
const dv = 9 * y / d - WHITE_V;
|
|
31532
31606
|
const cos = Math.cos(angle) * scale;
|
|
31533
31607
|
const sin = Math.sin(angle) * scale;
|
|
31534
|
-
const u = WHITE_U + du * cos - dv * sin;
|
|
31608
|
+
const u = Math.max(0, WHITE_U + du * cos - dv * sin);
|
|
31535
31609
|
const v = Math.max(1e-6, WHITE_V + du * sin + dv * cos);
|
|
31536
31610
|
const x2 = y * 9 * u / (4 * v);
|
|
31537
31611
|
const z2 = y * (12 - 3 * u - 20 * v) / (4 * v);
|
|
@@ -31550,6 +31624,7 @@ function adjustChroma(r, g, b, scale, angle) {
|
|
|
31550
31624
|
}
|
|
31551
31625
|
function colorAdjustmentMapper(ca) {
|
|
31552
31626
|
const illuminant = illuminantCube(ca.illuminant);
|
|
31627
|
+
const matrix = ILLUMINANT_MATRICES[ca.illuminant];
|
|
31553
31628
|
const gammas = [ca.redGamma / 1e4, ca.greenGamma / 1e4, ca.blueGamma / 1e4];
|
|
31554
31629
|
const black = ca.referenceBlack / 1e4 * 255;
|
|
31555
31630
|
const span = Math.max(1e-6, ca.referenceWhite / 1e4 * 255 - black);
|
|
@@ -31578,16 +31653,17 @@ function colorAdjustmentMapper(ca) {
|
|
|
31578
31653
|
}
|
|
31579
31654
|
return (rgb2) => {
|
|
31580
31655
|
let c = [rgb2 >> 16 & 255, rgb2 >> 8 & 255, rgb2 & 255];
|
|
31581
|
-
if (illuminant) {
|
|
31582
|
-
const mapped = lookupIlluminant(illuminant, c[0] / 255, c[1] / 255, c[2] / 255);
|
|
31583
|
-
c = [mapped[0] * 255, mapped[1] * 255, mapped[2] * 255];
|
|
31584
|
-
}
|
|
31585
31656
|
if (chroma) {
|
|
31586
|
-
|
|
31587
|
-
|
|
31657
|
+
c = c.map(exactPaletteLevel);
|
|
31658
|
+
if (matrix) {
|
|
31659
|
+
const [r, g, b] = c;
|
|
31660
|
+
c = matrix.map((row2) => row2[0] * r + row2[1] * g + row2[2] * b);
|
|
31588
31661
|
}
|
|
31589
|
-
c = adjustChroma(
|
|
31662
|
+
c = adjustChroma(c[0], c[1], c[2], scale, angle);
|
|
31590
31663
|
c = c.map(roundHalfUp);
|
|
31664
|
+
} else if (illuminant) {
|
|
31665
|
+
const mapped = lookupIlluminant(illuminant, c[0] / 255, c[1] / 255, c[2] / 255);
|
|
31666
|
+
c = [mapped[0] * 255, mapped[1] * 255, mapped[2] * 255];
|
|
31591
31667
|
}
|
|
31592
31668
|
return curve(c[0], 0) << 16 | curve(c[1], 1) << 8 | curve(c[2], 2);
|
|
31593
31669
|
};
|
|
@@ -31941,20 +32017,6 @@ function halftoneDespeckle(px, w, h) {
|
|
|
31941
32017
|
}
|
|
31942
32018
|
}
|
|
31943
32019
|
}
|
|
31944
|
-
function halftoneSharpen(px, w, h) {
|
|
31945
|
-
const src = px.slice();
|
|
31946
|
-
const at = (x, y, c) => src[(Math.max(0, Math.min(h - 1, y)) * w + Math.max(0, Math.min(w - 1, x))) * 3 + c];
|
|
31947
|
-
for (let y = 0; y < h; y++) {
|
|
31948
|
-
for (let x = 0; x < w; x++) {
|
|
31949
|
-
for (let c = 0; c < 3; c++) {
|
|
31950
|
-
const v = at(x, y, c);
|
|
31951
|
-
const sum = at(x - 1, y, c) + at(x + 1, y, c) + at(x, y - 1, c) + at(x, y + 1, c);
|
|
31952
|
-
const out = v + Math.floor((4 * v - sum) / 8);
|
|
31953
|
-
px[(y * w + x) * 3 + c] = out < 0 ? 0 : out > 255 ? 255 : out;
|
|
31954
|
-
}
|
|
31955
|
-
}
|
|
31956
|
-
}
|
|
31957
|
-
}
|
|
31958
32020
|
function halftonePairDespeckle(px, w, h, rows) {
|
|
31959
32021
|
const n = rows ? w : h;
|
|
31960
32022
|
const at = (k, side) => (rows ? side * w + k : k * w + side) * 3;
|
|
@@ -31977,6 +32039,46 @@ function halftoneKeepLast(px, w, h, rows) {
|
|
|
31977
32039
|
}
|
|
31978
32040
|
return out;
|
|
31979
32041
|
}
|
|
32042
|
+
function halftoneReduceBoth(rect, sw, sh, dw, dh, flipX, flipY) {
|
|
32043
|
+
const cols = halftoneReduceTaps(sw, dw, flipX);
|
|
32044
|
+
const rows = halftoneReduceTaps(sh, dh, flipY);
|
|
32045
|
+
const wide = new Int32Array(dw * sh * 3);
|
|
32046
|
+
for (let y = 0; y < sh; y++) {
|
|
32047
|
+
for (let x = 0; x < dw; x++) {
|
|
32048
|
+
let total = 0;
|
|
32049
|
+
for (const [, w] of cols[x]) total += w;
|
|
32050
|
+
for (let c = 0; c < 3; c++) {
|
|
32051
|
+
let sum = 0;
|
|
32052
|
+
for (const [xx, w] of cols[x]) sum += rect[(y * sw + xx) * 3 + c] * w;
|
|
32053
|
+
wide[(y * dw + x) * 3 + c] = Math.floor((2 * sum + total) / (2 * total));
|
|
32054
|
+
}
|
|
32055
|
+
}
|
|
32056
|
+
}
|
|
32057
|
+
const avg = new Float64Array(dw * dh * 3);
|
|
32058
|
+
for (let y = 0; y < dh; y++) {
|
|
32059
|
+
let total = 0;
|
|
32060
|
+
for (const [, w] of rows[y]) total += w;
|
|
32061
|
+
for (let x = 0; x < dw; x++) {
|
|
32062
|
+
for (let c = 0; c < 3; c++) {
|
|
32063
|
+
let sum = 0;
|
|
32064
|
+
for (const [yy, w] of rows[y]) sum += wide[(yy * dw + x) * 3 + c] * w;
|
|
32065
|
+
avg[(y * dw + x) * 3 + c] = sum / total;
|
|
32066
|
+
}
|
|
32067
|
+
}
|
|
32068
|
+
}
|
|
32069
|
+
const at = (x, y, c) => avg[(Math.max(0, Math.min(dh - 1, y)) * dw + Math.max(0, Math.min(dw - 1, x))) * 3 + c];
|
|
32070
|
+
const out = new Int32Array(dw * dh * 3);
|
|
32071
|
+
for (let y = 0; y < dh; y++) {
|
|
32072
|
+
for (let x = 0; x < dw; x++) {
|
|
32073
|
+
for (let c = 0; c < 3; c++) {
|
|
32074
|
+
const v = at(x, y, c);
|
|
32075
|
+
const sum = at(x - 1, y, c) + at(x + 1, y, c) + at(x, y - 1, c) + at(x, y + 1, c);
|
|
32076
|
+
out[(y * dw + x) * 3 + c] = Math.max(0, Math.min(255, Math.floor(v + (4 * v - sum) / 8)));
|
|
32077
|
+
}
|
|
32078
|
+
}
|
|
32079
|
+
}
|
|
32080
|
+
return out;
|
|
32081
|
+
}
|
|
31980
32082
|
function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust, adjustAfterSampling = false, directDib = false, dither, curves) {
|
|
31981
32083
|
const W = Math.max(0, Math.round(Math.abs(dw)));
|
|
31982
32084
|
const H = Math.max(0, Math.round(Math.abs(dh)));
|
|
@@ -32028,11 +32130,18 @@ function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust, adjustAfterSamplin
|
|
|
32028
32130
|
}
|
|
32029
32131
|
const reducedEven = W < SW ? SW % 2 === 0 && H % SH === 0 : SH % 2 === 0 && W % SW === 0;
|
|
32030
32132
|
const ditherAtSource = dithered && (W >= SW && H >= SH || nativeMixed && reducedEven);
|
|
32133
|
+
const extendRow = ditherAtSource && nativeMixed && W < SW && H > SH && !flipY;
|
|
32134
|
+
if (extendRow) {
|
|
32135
|
+
const bigger = new Int32Array((SH + 1) * SW * 3);
|
|
32136
|
+
bigger.set(rect);
|
|
32137
|
+
bigger.copyWithin(SH * SW * 3, (SH - 1) * SW * 3, SH * SW * 3);
|
|
32138
|
+
rect = bigger;
|
|
32139
|
+
}
|
|
32031
32140
|
if (ditherAtSource) {
|
|
32032
32141
|
ditherPixels(
|
|
32033
32142
|
rect,
|
|
32034
32143
|
SW,
|
|
32035
|
-
SH,
|
|
32144
|
+
extendRow ? SH + 1 : SH,
|
|
32036
32145
|
dither,
|
|
32037
32146
|
false,
|
|
32038
32147
|
false,
|
|
@@ -32072,7 +32181,10 @@ function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust, adjustAfterSamplin
|
|
|
32072
32181
|
linear(rows, SH, H, flipY);
|
|
32073
32182
|
}
|
|
32074
32183
|
let out = nativeMixed ? new Float64Array(sampleW * sampleH * 3) : new Int32Array(W * H * 3);
|
|
32075
|
-
|
|
32184
|
+
if (reducing) {
|
|
32185
|
+
out = halftoneReduceBoth(rect, SW, SH, W, H, flipX, flipY);
|
|
32186
|
+
}
|
|
32187
|
+
for (let y = 0; !reducing && y < sampleH; y++) {
|
|
32076
32188
|
const ys = rows[y];
|
|
32077
32189
|
for (let x = 0; x < sampleW; x++) {
|
|
32078
32190
|
const xs = cols[x];
|
|
@@ -32097,15 +32209,38 @@ function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust, adjustAfterSamplin
|
|
|
32097
32209
|
out[o + 2] = round(b);
|
|
32098
32210
|
}
|
|
32099
32211
|
}
|
|
32100
|
-
if (reducing) {
|
|
32101
|
-
halftoneSharpen(out, W, H);
|
|
32102
|
-
}
|
|
32103
32212
|
if (nativeMixed) {
|
|
32104
32213
|
const reduceHorizontal = W < SW;
|
|
32214
|
+
let extension;
|
|
32215
|
+
if (extendRow) {
|
|
32216
|
+
const reduced = new Float64Array(sampleW * 3);
|
|
32217
|
+
for (let x = 0; x < sampleW; x++) {
|
|
32218
|
+
let total = 0;
|
|
32219
|
+
const sums = [0, 0, 0];
|
|
32220
|
+
for (const [xx, wx] of cols[x]) {
|
|
32221
|
+
total += wx;
|
|
32222
|
+
for (let c = 0; c < 3; c++) sums[c] += rect[(SH * SW + xx) * 3 + c] * wx;
|
|
32223
|
+
}
|
|
32224
|
+
for (let c = 0; c < 3; c++) reduced[x * 3 + c] = sums[c] / total;
|
|
32225
|
+
}
|
|
32226
|
+
extension = reduced;
|
|
32227
|
+
}
|
|
32105
32228
|
for (const horizontal of [reduceHorizontal, !reduceHorizontal]) {
|
|
32106
32229
|
const source2 = out;
|
|
32107
32230
|
out = new Int32Array(source2.length);
|
|
32108
|
-
const
|
|
32231
|
+
const ext = extension;
|
|
32232
|
+
const at = (x, y, c) => y >= sampleH && ext ? ext[Math.max(0, Math.min(sampleW - 1, x)) * 3 + c] : source2[(Math.max(0, Math.min(sampleH - 1, y)) * sampleW + Math.max(0, Math.min(sampleW - 1, x))) * 3 + c];
|
|
32233
|
+
if (horizontal && ext) {
|
|
32234
|
+
const next = new Int32Array(sampleW * 3);
|
|
32235
|
+
for (let x = 0; x < sampleW; x++) {
|
|
32236
|
+
for (let c = 0; c < 3; c++) {
|
|
32237
|
+
const v = ext[x * 3 + c];
|
|
32238
|
+
const sum = at(x - 1, sampleH, c) + at(x + 1, sampleH, c);
|
|
32239
|
+
next[x * 3 + c] = Math.max(0, Math.min(255, Math.floor(v + (2 * v - sum) / 4)));
|
|
32240
|
+
}
|
|
32241
|
+
}
|
|
32242
|
+
extension = next;
|
|
32243
|
+
}
|
|
32109
32244
|
for (let y = 0; y < sampleH; y++) {
|
|
32110
32245
|
for (let x = 0; x < sampleW; x++) {
|
|
32111
32246
|
for (let c = 0; c < 3; c++) {
|