emf-converter 4.3.4 → 4.3.5
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/index.js +282 -167
- package/dist/index.mjs +282 -167
- 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**: The `HALFTONE` stretch mode reproduces Windows' halftone engine for 32-bit output: an enlargement smooths isolated pixels and then takes the nearest source pixel, and a reduction averages each pixel's footprint and then sharpens. All seven plain `HALFTONE` fixtures (2x, 3x, 0.5x, 0.65x and 1.37x) match exactly. It is not verified for a stretch that enlarges one axis and reduces the other. `EMR_SETCOLORADJUSTMENT` is applied to the source of `HALFTONE` `StretchBlt` and `StretchDIBits` calls, as in GDI. Windows does not publish its adjustment formulas. The converter uses a model fitted to two Windows captures: contrast and brightness on CIE L*, colourfulness and red-green tint on u'v' chromaticity, with gamma as the source encoding, so a gamma on its own changes nothing. Windows also dithers the adjusted colours over 32 levels per channel, and that pattern is not reproduced. On the fixtures, 0% to 15.1% of pixels differ by more than 24. The log filter uses an unverified curve, and the illuminant is ignored. EMF+ image effects (`SerializableObject`) are applied to the image's pixels before it is drawn. Colour matrix
|
|
217
|
+
- **Colour adjustment and image effects**: The `HALFTONE` stretch mode reproduces Windows' halftone engine for 32-bit output: an enlargement smooths isolated pixels and then takes the nearest source pixel, and a reduction averages each pixel's footprint and then sharpens. All seven plain `HALFTONE` fixtures (2x, 3x, 0.5x, 0.65x and 1.37x) match exactly. It is not verified for a stretch that enlarges one axis and reduces the other. `EMR_SETCOLORADJUSTMENT` is applied to the source of `HALFTONE` `StretchBlt` and `StretchDIBits` calls, as in GDI. Windows does not publish its adjustment formulas. The converter uses a model fitted to two Windows captures: contrast and brightness on CIE L*, colourfulness and red-green tint on u'v' chromaticity, with gamma as the source encoding, so a gamma on its own changes nothing. Windows also dithers the adjusted colours over 32 levels per channel, and that pattern is not reproduced. On the fixtures, 0% to 15.1% of pixels differ by more than 24. The log filter uses an unverified curve, and the illuminant is ignored. EMF+ image effects (`SerializableObject`) are applied to the image's pixels before it is drawn. MS-EMFPLUS does not specify the algorithms, so they were measured against the effected bitmaps GDI+ itself writes into its recordings. Colour matrix, lookup table, brightness/contrast, colour balance, levels and sharpen match GDI+ exactly; blur matches to within one level (GDI+ blurs along rows only, plus a vertical blur of the top row, and so does the converter); colour curves match exactly for exposure, density, midtone and black saturation and to within one level on up to 2.3% of pixels for contrast, highlight, shadow and white saturation, whose control points were measured at intensity ±50 and are scaled for other values; hue/saturation/lightness uses GDI+'s integer HSL, but its hue rounding is only partly reproduced, so lightness and saturation changes differ on under 1% of pixels and a hue rotation is more than two levels off on 0.3% to 32% of pixels (up to 11 levels); tint is within two levels (three on 0.07% of pixels); red-eye correction is an approximation, as GDI+'s pupil detection and textured repaint are not reproduced (up to 17% of pixels differ). GDI+ follows each effect draw with a plain draw of the bitmap it effected, so Windows' playback depends on the effect only where that bitmap is translucent; played back, the image effect fixtures match Windows to within one level, except one edge of a rotated blurred draw (0.22% of pixels). An effect is applied to the draw's source rectangle only, reading one more column and row, as GDI+ does; a blur with `expandEdge` blurs transparency in at the edges but, as in GDI+, draws nothing beyond the source rectangle. When the source rectangle is not in pixels, the effect is applied to the whole image. GDI+ records a colour curve effect under the lookup table's identifier and its own playback draws the image without the effect; the converter does the same. An effect is skipped when the image cannot be decoded to pixels: PNG and BMP always can be, while JPEG, GIF, TIFF and similar formats need a canvas backend (a browser or `@napi-rs/canvas`).
|
|
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. The fixtures include dashes only under a non-uniform scale. Dashes under a uniform scale follow the same rule but have not been checked. A pen transform that cannot be inverted is ignored. Of the anchor caps, only `ArrowAnchor` is drawn with its real shape. The others are drawn as their base cap.
|
|
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; unsupported encodings (including Johab and OEM on standard runtimes) fall back to Windows-1252. Vertical `ETO_PDY` advances require 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 Bezier curves follow `WidenPath` (which Windows' direct drawing does not quite match); 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/index.js
CHANGED
|
@@ -12319,7 +12319,7 @@ function blendSeparable(rgba, width, box, iu0, wu, iv0, wv, verticalFirst, edge
|
|
|
12319
12319
|
}
|
|
12320
12320
|
var OUTSIDE_TRANSPARENT = -1;
|
|
12321
12321
|
var OUTSIDE_CLAMP = -2;
|
|
12322
|
-
function wrapTap(t, lo, hi,
|
|
12322
|
+
function wrapTap(t, lo, hi, mirror2, edge) {
|
|
12323
12323
|
if (t >= lo && t < hi) {
|
|
12324
12324
|
return t;
|
|
12325
12325
|
}
|
|
@@ -12330,7 +12330,7 @@ function wrapTap(t, lo, hi, mirror, edge) {
|
|
|
12330
12330
|
return OUTSIDE_CLAMP;
|
|
12331
12331
|
}
|
|
12332
12332
|
const n = hi - lo;
|
|
12333
|
-
if (!
|
|
12333
|
+
if (!mirror2) {
|
|
12334
12334
|
return lo + ((t - lo) % n + n) % n;
|
|
12335
12335
|
}
|
|
12336
12336
|
const q = ((t - lo) % (2 * n) + 2 * n) % (2 * n);
|
|
@@ -17630,9 +17630,9 @@ function buildPattern(ctx, rect, tw, th, wrap, brush, delta, sampling, colorAt)
|
|
|
17630
17630
|
if (typeof ctx.createPattern !== "function") {
|
|
17631
17631
|
return null;
|
|
17632
17632
|
}
|
|
17633
|
-
const
|
|
17634
|
-
const w = tw * (
|
|
17635
|
-
const h = th * (
|
|
17633
|
+
const mirror2 = wrap === "clamp" ? { x: false, y: false } : mirrorFlags(wrap);
|
|
17634
|
+
const w = tw * (mirror2.x ? 2 : 1);
|
|
17635
|
+
const h = th * (mirror2.y ? 2 : 1);
|
|
17636
17636
|
const temp = createTempCanvas(w, h);
|
|
17637
17637
|
if (!temp) {
|
|
17638
17638
|
return null;
|
|
@@ -18054,8 +18054,8 @@ function createHatchPattern(ctx, hatch, origin, user, deviceToCanvas) {
|
|
|
18054
18054
|
}
|
|
18055
18055
|
|
|
18056
18056
|
// src/emf-plus-brush-texture.ts
|
|
18057
|
-
function wrapTexel(i, n,
|
|
18058
|
-
if (!
|
|
18057
|
+
function wrapTexel(i, n, mirror2) {
|
|
18058
|
+
if (!mirror2) {
|
|
18059
18059
|
return (i % n + n) % n;
|
|
18060
18060
|
}
|
|
18061
18061
|
const p = (i % (2 * n) + 2 * n) % (2 * n);
|
|
@@ -19116,11 +19116,11 @@ function boundaryBox2(points) {
|
|
|
19116
19116
|
}
|
|
19117
19117
|
return x1 > x0 && y1 > y0 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
|
|
19118
19118
|
}
|
|
19119
|
-
function foldIntoTile(v, origin, size,
|
|
19119
|
+
function foldIntoTile(v, origin, size, mirror2, lag) {
|
|
19120
19120
|
const rel = v - origin;
|
|
19121
19121
|
const k = Math.floor(rel / size);
|
|
19122
19122
|
const local = rel - k * size;
|
|
19123
|
-
if (
|
|
19123
|
+
if (mirror2 && (k % 2 + 2) % 2 === 1) {
|
|
19124
19124
|
return origin + size - lag - local;
|
|
19125
19125
|
}
|
|
19126
19126
|
return origin + local;
|
|
@@ -23974,6 +23974,7 @@ function readGuid(view, off) {
|
|
|
23974
23974
|
].join("-");
|
|
23975
23975
|
}
|
|
23976
23976
|
var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
23977
|
+
var COLOR_CURVE_PARAMS_SIZE = 12;
|
|
23977
23978
|
function parseSerializableObject(view, dataOff, dataSize) {
|
|
23978
23979
|
if (dataSize < 20) {
|
|
23979
23980
|
return null;
|
|
@@ -23998,7 +23999,7 @@ function parseSerializableObject(view, dataOff, dataSize) {
|
|
|
23998
23999
|
case "colorBalance":
|
|
23999
24000
|
return need(12) ? { kind, cyanRed: i32(0, -100, 100), magentaGreen: i32(1, -100, 100), yellowBlue: i32(2, -100, 100) } : null;
|
|
24000
24001
|
case "colorCurve":
|
|
24001
|
-
return need(
|
|
24002
|
+
return need(COLOR_CURVE_PARAMS_SIZE) ? {
|
|
24002
24003
|
kind,
|
|
24003
24004
|
adjustment: view.getInt32(p, true),
|
|
24004
24005
|
channel: view.getInt32(p + 4, true),
|
|
@@ -24052,66 +24053,101 @@ function parseSerializableObject(view, dataOff, dataSize) {
|
|
|
24052
24053
|
return need(8) ? { kind, hue: i32(0, -180, 180), amount: i32(1, -100, 100) } : null;
|
|
24053
24054
|
}
|
|
24054
24055
|
}
|
|
24055
|
-
|
|
24056
|
+
var roundHalfDown = (v) => Math.ceil(v - 0.5);
|
|
24057
|
+
function buildLut(f, round = Math.round) {
|
|
24056
24058
|
const lut = new Uint8Array(256);
|
|
24057
24059
|
for (let v = 0; v < 256; v++) {
|
|
24058
|
-
lut[v] = clamp(
|
|
24060
|
+
lut[v] = clamp(round(f(v)), 0, 255);
|
|
24059
24061
|
}
|
|
24060
24062
|
return lut;
|
|
24061
24063
|
}
|
|
24062
|
-
function
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
const
|
|
24068
|
-
return (v) =>
|
|
24069
|
-
}
|
|
24070
|
-
function
|
|
24071
|
-
|
|
24072
|
-
|
|
24064
|
+
function midtoneGamma(t) {
|
|
24065
|
+
return t >= 0 ? 1 / (1 + t / 50) : 1 - t / 50;
|
|
24066
|
+
}
|
|
24067
|
+
function brightnessContrastLut(brightness, contrast) {
|
|
24068
|
+
const gain = contrast >= 100 ? 1e6 : contrast >= 0 ? 100 / (100 - contrast) : (100 + contrast) / 100;
|
|
24069
|
+
const half = brightness / 2;
|
|
24070
|
+
return buildLut((v) => (v + half - 127.5) * gain + 127.5 + half);
|
|
24071
|
+
}
|
|
24072
|
+
function balanceLut(t) {
|
|
24073
|
+
return buildLut((v) => v * (1 + t / 100));
|
|
24074
|
+
}
|
|
24075
|
+
function splineLut(ys) {
|
|
24076
|
+
const n = ys.length;
|
|
24077
|
+
const h = 255 / (n - 1);
|
|
24078
|
+
const m = new Float64Array(n);
|
|
24079
|
+
const c = new Float64Array(n);
|
|
24080
|
+
const d = new Float64Array(n);
|
|
24081
|
+
for (let i = 1; i < n - 1; i++) {
|
|
24082
|
+
const rhs = 6 * (ys[i + 1] - 2 * ys[i] + ys[i - 1]) / (h * h);
|
|
24083
|
+
const den = 4 - c[i - 1];
|
|
24084
|
+
c[i] = 1 / den;
|
|
24085
|
+
d[i] = (rhs - d[i - 1]) / den;
|
|
24086
|
+
}
|
|
24087
|
+
for (let i = n - 2; i >= 1; i--) {
|
|
24088
|
+
m[i] = d[i] - c[i] * m[i + 1];
|
|
24089
|
+
}
|
|
24090
|
+
return buildLut((v) => {
|
|
24091
|
+
const i = Math.min(n - 2, Math.floor(v / h));
|
|
24092
|
+
const b = (v - i * h) / h;
|
|
24093
|
+
const a = 1 - b;
|
|
24094
|
+
return a * ys[i] + b * ys[i + 1] + ((a * a * a - a) * m[i] + (b * b * b - b) * m[i + 1]) * h * h / 6;
|
|
24095
|
+
});
|
|
24073
24096
|
}
|
|
24097
|
+
var CURVE_POINTS = 23;
|
|
24098
|
+
var CURVE_X = Array.from({ length: CURVE_POINTS }, (_, k) => k * 255 / (CURVE_POINTS - 1));
|
|
24099
|
+
var CURVE_TABLES = {
|
|
24100
|
+
"contrast+": [0, 3.8, 8, 12.2, 17.4, 23.5, 31.1, 40.2, 53, 70.2, 94.5, 127.5, 160.5, 184.8, 202, 214.8, 223.9, 231.5, 237.6, 242.8, 247, 251.2, 255],
|
|
24101
|
+
"contrast-": [0, 33.1, 57.2, 74.7, 87.2, 96.6, 103.9, 109.8, 115.3, 119.6, 123.7, 127.5, 131.3, 135.4, 139.7, 145.2, 151.1, 158.4, 167.8, 180.3, 197.8, 221.9, 255],
|
|
24102
|
+
"highlight+": [0, 11.6, 23.2, 34.8, 46.4, 58, 69.6, 81.1, 92.7, 104.3, 116, 127.6, 142.3, 160.6, 186.4, 208, 220.6, 229.4, 236.2, 242, 246.6, 251, 255],
|
|
24103
|
+
"highlight-": [0, 11.6, 23.2, 34.8, 46.4, 58, 69.5, 81.1, 92.7, 104.4, 115.9, 127.5, 136.7, 144.9, 151.4, 157.1, 161.8, 167.2, 174.3, 185.3, 200.9, 223.7, 255],
|
|
24104
|
+
"shadow+": [0, 31.3, 54, 70, 82.5, 92.3, 99.5, 104.8, 109.5, 115, 121.3, 127.7, 139.2, 150.6, 162.3, 173.8, 185.5, 197, 208.6, 220.2, 231.8, 243.4, 255],
|
|
24105
|
+
"shadow-": [0, 4, 8.4, 13, 18.7, 25.7, 34.4, 45, 58.4, 80, 105.8, 127.5, 139.2, 150.7, 162.3, 173.9, 185.4, 197, 208.6, 220.2, 231.8, 243.4, 255]
|
|
24106
|
+
};
|
|
24074
24107
|
function curveAdjustmentLut(adjustment, intensity) {
|
|
24075
24108
|
const t = intensity;
|
|
24109
|
+
let ys;
|
|
24076
24110
|
switch (adjustment) {
|
|
24077
24111
|
case CurveAdjustment.Exposure:
|
|
24078
|
-
|
|
24112
|
+
ys = CURVE_X.map((x) => roundHalfDown(x) + t);
|
|
24113
|
+
break;
|
|
24079
24114
|
case CurveAdjustment.Density:
|
|
24080
|
-
|
|
24115
|
+
ys = CURVE_X.map((x) => x + t);
|
|
24116
|
+
break;
|
|
24081
24117
|
case CurveAdjustment.Contrast:
|
|
24082
|
-
return buildLut(contrastCurve(clamp(t, -100, 100)));
|
|
24083
24118
|
case CurveAdjustment.Highlight:
|
|
24084
24119
|
case CurveAdjustment.Shadow: {
|
|
24085
|
-
const
|
|
24086
|
-
const
|
|
24087
|
-
|
|
24088
|
-
|
|
24089
|
-
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24120
|
+
const c = clamp(t, -100, 100);
|
|
24121
|
+
const name = ["contrast", "highlight", "shadow"][adjustment - CurveAdjustment.Contrast] + (c >= 0 ? "+" : "-");
|
|
24122
|
+
const table = CURVE_TABLES[name];
|
|
24123
|
+
ys = CURVE_X.map((x, k) => x + Math.abs(c) / 50 * (table[k] - x));
|
|
24124
|
+
break;
|
|
24125
|
+
}
|
|
24126
|
+
case CurveAdjustment.Midtone: {
|
|
24127
|
+
const g = midtoneGamma(clamp(t, -100, 100));
|
|
24128
|
+
ys = CURVE_X.map((x) => 255 * (x / 255) ** g);
|
|
24129
|
+
break;
|
|
24095
24130
|
}
|
|
24096
|
-
case CurveAdjustment.Midtone:
|
|
24097
|
-
return buildLut(midtoneCurve(clamp(t, -100, 100)));
|
|
24098
24131
|
case CurveAdjustment.WhiteSaturation: {
|
|
24099
24132
|
const w = Math.max(1, clamp(t, 0, 255));
|
|
24100
|
-
|
|
24133
|
+
ys = CURVE_X.map((x) => x * 255 / w);
|
|
24134
|
+
break;
|
|
24101
24135
|
}
|
|
24102
24136
|
case CurveAdjustment.BlackSaturation: {
|
|
24103
24137
|
const b = Math.min(254, clamp(t, 0, 255));
|
|
24104
|
-
|
|
24138
|
+
ys = CURVE_X.map((x) => (x - b) * 255 / (255 - b));
|
|
24139
|
+
break;
|
|
24105
24140
|
}
|
|
24106
24141
|
default:
|
|
24107
24142
|
return null;
|
|
24108
24143
|
}
|
|
24144
|
+
return splineLut(ys.map((y) => clamp(y, 0, 255)));
|
|
24109
24145
|
}
|
|
24110
24146
|
function levelsLut(highlight, midtone, shadow) {
|
|
24111
24147
|
const black = shadow / 100 * 255;
|
|
24112
24148
|
const white = Math.max(black + 1, highlight / 100 * 255);
|
|
24113
|
-
const gamma =
|
|
24114
|
-
return buildLut((v) =>
|
|
24149
|
+
const gamma = midtoneGamma(midtone);
|
|
24150
|
+
return buildLut((v) => 255 * clamp((v - black) / (white - black), 0, 1) ** gamma, roundHalfDown);
|
|
24115
24151
|
}
|
|
24116
24152
|
function applyLuts(src, r, g, b, a = null) {
|
|
24117
24153
|
const out = new Uint8ClampedArray(src);
|
|
@@ -24137,26 +24173,6 @@ function applyColorMatrix(src, m) {
|
|
|
24137
24173
|
}
|
|
24138
24174
|
return out;
|
|
24139
24175
|
}
|
|
24140
|
-
function rgbToHsl(r, g, b) {
|
|
24141
|
-
const max = Math.max(r, g, b);
|
|
24142
|
-
const min = Math.min(r, g, b);
|
|
24143
|
-
const l = (max + min) / 2;
|
|
24144
|
-
const d = max - min;
|
|
24145
|
-
if (d === 0) {
|
|
24146
|
-
return [0, 0, l];
|
|
24147
|
-
}
|
|
24148
|
-
const s = d / (1 - Math.abs(2 * l - 1));
|
|
24149
|
-
let h;
|
|
24150
|
-
if (max === r) {
|
|
24151
|
-
h = (g - b) / d % 6;
|
|
24152
|
-
} else if (max === g) {
|
|
24153
|
-
h = (b - r) / d + 2;
|
|
24154
|
-
} else {
|
|
24155
|
-
h = (r - g) / d + 4;
|
|
24156
|
-
}
|
|
24157
|
-
h *= 60;
|
|
24158
|
-
return [h < 0 ? h + 360 : h, s, l];
|
|
24159
|
-
}
|
|
24160
24176
|
function hslToRgb(h, s, l) {
|
|
24161
24177
|
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
24162
24178
|
const hp = (h % 360 + 360) % 360 / 60;
|
|
@@ -24165,29 +24181,80 @@ function hslToRgb(h, s, l) {
|
|
|
24165
24181
|
const [r, g, b] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
|
|
24166
24182
|
return [r + m, g + m, b + m];
|
|
24167
24183
|
}
|
|
24184
|
+
var HUE_SEXTANT = 43;
|
|
24185
|
+
var HUE_CIRCLE = 6 * HUE_SEXTANT;
|
|
24186
|
+
function gdipHue(r, g, b) {
|
|
24187
|
+
const max = Math.max(r, g, b);
|
|
24188
|
+
const d = max - Math.min(r, g, b);
|
|
24189
|
+
const f = (x) => Math.floor(x * HUE_SEXTANT / d);
|
|
24190
|
+
if (max === r && g >= b) {
|
|
24191
|
+
return f(g - b);
|
|
24192
|
+
}
|
|
24193
|
+
if (max === g) {
|
|
24194
|
+
return b >= r ? 87 + f(b - r) : 86 - f(r - b);
|
|
24195
|
+
}
|
|
24196
|
+
if (max === b && b !== r) {
|
|
24197
|
+
return r >= g ? 172 + f(r - g) : 171 - f(g - r);
|
|
24198
|
+
}
|
|
24199
|
+
const t = f(b - g);
|
|
24200
|
+
return (256 - t - (t >= 40 ? 1 : 0)) % 256;
|
|
24201
|
+
}
|
|
24168
24202
|
function applyHueSaturationLightness(src, hue, saturation, lightness2) {
|
|
24169
24203
|
const out = new Uint8ClampedArray(src);
|
|
24170
|
-
const
|
|
24204
|
+
const shift = Math.round(hue * HUE_CIRCLE / 360);
|
|
24205
|
+
const sMul = 1 + saturation / 100;
|
|
24171
24206
|
for (let i = 0; i < src.length; i += 4) {
|
|
24172
|
-
const
|
|
24173
|
-
const
|
|
24174
|
-
|
|
24175
|
-
|
|
24176
|
-
|
|
24207
|
+
const r = src[i];
|
|
24208
|
+
const g = src[i + 1];
|
|
24209
|
+
const b = src[i + 2];
|
|
24210
|
+
const max = Math.max(r, g, b);
|
|
24211
|
+
const min = Math.min(r, g, b);
|
|
24212
|
+
const l = clamp(Math.round(((max + min >> 1) * 100 + 255 * lightness2) / 100), 0, 255);
|
|
24213
|
+
if (max === min) {
|
|
24214
|
+
out[i] = out[i + 1] = out[i + 2] = l;
|
|
24215
|
+
continue;
|
|
24216
|
+
}
|
|
24217
|
+
const sum = max + min;
|
|
24218
|
+
const s = clamp((max - min) / (sum <= 255 ? sum : 510 - sum) * sMul, 0, 1);
|
|
24219
|
+
const hi = l <= 127 ? l * (1 + s) : l + s * (255 - l);
|
|
24220
|
+
const m1 = l <= 127 ? 2 * l - Math.trunc(hi) : Math.round(2 * l - hi);
|
|
24221
|
+
const m2 = 2 * l - m1;
|
|
24222
|
+
const q = ((gdipHue(r, g, b) + shift) % HUE_CIRCLE + HUE_CIRCLE) % HUE_CIRCLE;
|
|
24223
|
+
const sextant = Math.min(5, Math.floor(q / HUE_SEXTANT));
|
|
24224
|
+
const f = (q - sextant * HUE_SEXTANT) / HUE_SEXTANT;
|
|
24225
|
+
const up = Math.trunc(m1 + (m2 - m1) * f);
|
|
24226
|
+
const down = Math.trunc(m1 + (m2 - m1) * (1 - f));
|
|
24227
|
+
const [nr, ng, nb] = [
|
|
24228
|
+
[m2, up, m1],
|
|
24229
|
+
[down, m2, m1],
|
|
24230
|
+
[m1, m2, up],
|
|
24231
|
+
[m1, down, m2],
|
|
24232
|
+
[up, m1, m2],
|
|
24233
|
+
[m2, m1, down]
|
|
24234
|
+
][sextant];
|
|
24235
|
+
out[i] = nr;
|
|
24236
|
+
out[i + 1] = ng;
|
|
24237
|
+
out[i + 2] = nb;
|
|
24177
24238
|
}
|
|
24178
24239
|
return out;
|
|
24179
24240
|
}
|
|
24180
|
-
var
|
|
24241
|
+
var LUMA_709 = [0.2126, 0.7152, 0.0722];
|
|
24242
|
+
var TINT_CHROMA_SCALE = 0.985;
|
|
24181
24243
|
function applyTint(src, hue, amount) {
|
|
24182
24244
|
const tint = hslToRgb(hue, 1, 0.5);
|
|
24183
|
-
const
|
|
24184
|
-
const
|
|
24185
|
-
const
|
|
24245
|
+
const ty = tint[0] * LUMA_709[0] + tint[1] * LUMA_709[1] + tint[2] * LUMA_709[2];
|
|
24246
|
+
const chroma = tint.map((c) => (c - ty) * TINT_CHROMA_SCALE);
|
|
24247
|
+
const a = amount / 100;
|
|
24186
24248
|
const out = new Uint8ClampedArray(src);
|
|
24187
24249
|
for (let i = 0; i < src.length; i += 4) {
|
|
24188
|
-
|
|
24189
|
-
|
|
24190
|
-
|
|
24250
|
+
const r = src[i];
|
|
24251
|
+
const g = src[i + 1];
|
|
24252
|
+
const b = src[i + 2];
|
|
24253
|
+
const y = r * LUMA_709[0] + g * LUMA_709[1] + b * LUMA_709[2];
|
|
24254
|
+
const v = Math.max(r, g, b);
|
|
24255
|
+
out[i] = Math.round((1 - a) * r + a * (y + v * chroma[0]));
|
|
24256
|
+
out[i + 1] = Math.round((1 - a) * g + a * (y + v * chroma[1]));
|
|
24257
|
+
out[i + 2] = Math.round((1 - a) * b + a * (y + v * chroma[2]));
|
|
24191
24258
|
}
|
|
24192
24259
|
return out;
|
|
24193
24260
|
}
|
|
@@ -24204,7 +24271,7 @@ function applyRedEyeCorrection(src, width, height, areas) {
|
|
|
24204
24271
|
const r = out[i];
|
|
24205
24272
|
const g = out[i + 1];
|
|
24206
24273
|
const b = out[i + 2];
|
|
24207
|
-
if (r
|
|
24274
|
+
if (r >= 2 * g && r >= 2 * b && r > 64) {
|
|
24208
24275
|
out[i] = Math.round((g + b) / 2);
|
|
24209
24276
|
}
|
|
24210
24277
|
}
|
|
@@ -24212,83 +24279,96 @@ function applyRedEyeCorrection(src, width, height, areas) {
|
|
|
24212
24279
|
}
|
|
24213
24280
|
return out;
|
|
24214
24281
|
}
|
|
24215
|
-
function
|
|
24216
|
-
const
|
|
24217
|
-
|
|
24218
|
-
|
|
24219
|
-
k
|
|
24282
|
+
function blurKernel(radius) {
|
|
24283
|
+
const taps = Math.ceil(radius);
|
|
24284
|
+
const k = new Float64Array(2 * taps + 1);
|
|
24285
|
+
if (taps === 0) {
|
|
24286
|
+
k[0] = 1;
|
|
24287
|
+
return k;
|
|
24220
24288
|
}
|
|
24221
|
-
const
|
|
24222
|
-
let
|
|
24223
|
-
for (let
|
|
24224
|
-
|
|
24225
|
-
|
|
24226
|
-
|
|
24227
|
-
}
|
|
24228
|
-
return [0, 1, 2].map((i) => i < best ? k + 1 : k);
|
|
24229
|
-
}
|
|
24230
|
-
function boxPass(src, w, h, r, horizontal) {
|
|
24231
|
-
if (r <= 0) {
|
|
24232
|
-
return src;
|
|
24233
|
-
}
|
|
24234
|
-
const out = new Float64Array(src.length);
|
|
24235
|
-
const lines = horizontal ? h : w;
|
|
24236
|
-
const len = horizontal ? w : h;
|
|
24237
|
-
const step = horizontal ? 4 : w * 4;
|
|
24238
|
-
const norm = 1 / (2 * r + 1);
|
|
24239
|
-
for (let line = 0; line < lines; line++) {
|
|
24240
|
-
const base = horizontal ? line * w * 4 : line * 4;
|
|
24241
|
-
for (let c = 0; c < 4; c++) {
|
|
24242
|
-
const at = (p) => src[base + clamp(p, 0, len - 1) * step + c];
|
|
24243
|
-
let sum = 0;
|
|
24244
|
-
for (let p = -r; p <= r; p++) {
|
|
24245
|
-
sum += at(p);
|
|
24246
|
-
}
|
|
24247
|
-
for (let p = 0; p < len; p++) {
|
|
24248
|
-
out[base + p * step + c] = sum * norm;
|
|
24249
|
-
sum += at(p + r + 1) - at(p - r);
|
|
24250
|
-
}
|
|
24251
|
-
}
|
|
24289
|
+
const sigma = radius / 1.98;
|
|
24290
|
+
let sum = 0;
|
|
24291
|
+
for (let i = -taps; i <= taps; i++) {
|
|
24292
|
+
const w = Math.exp(-(i * i) / (2 * sigma * sigma));
|
|
24293
|
+
k[i + taps] = w;
|
|
24294
|
+
sum += w;
|
|
24252
24295
|
}
|
|
24253
|
-
|
|
24296
|
+
for (let i = 0; i < k.length; i++) {
|
|
24297
|
+
k[i] /= sum;
|
|
24298
|
+
}
|
|
24299
|
+
return k;
|
|
24254
24300
|
}
|
|
24255
|
-
function
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
const a = src[i + 3] / 255;
|
|
24259
|
-
buf[i] = src[i] * a;
|
|
24260
|
-
buf[i + 1] = src[i + 1] * a;
|
|
24261
|
-
buf[i + 2] = src[i + 2] * a;
|
|
24262
|
-
buf[i + 3] = src[i + 3];
|
|
24301
|
+
function mirror(p, n) {
|
|
24302
|
+
if (n === 1) {
|
|
24303
|
+
return 0;
|
|
24263
24304
|
}
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
buf = boxPass(buf, w, h, r, false);
|
|
24305
|
+
while (p < 0 || p >= n) {
|
|
24306
|
+
p = p < 0 ? -p : 2 * (n - 1) - p;
|
|
24267
24307
|
}
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24308
|
+
return p;
|
|
24309
|
+
}
|
|
24310
|
+
function gdipBlur(src, w, h, radius, vRow = 0, vZeroAbove = false) {
|
|
24311
|
+
const k = blurKernel(radius);
|
|
24312
|
+
const taps = (k.length - 1) / 2;
|
|
24313
|
+
const out = new Float64Array(w * h * 4);
|
|
24314
|
+
for (let y = 0; y < h; y++) {
|
|
24315
|
+
const row = y * w * 4;
|
|
24316
|
+
for (let x = 0; x < w; x++) {
|
|
24317
|
+
let s0 = 0;
|
|
24318
|
+
let s1 = 0;
|
|
24319
|
+
let s2 = 0;
|
|
24320
|
+
let s3 = 0;
|
|
24321
|
+
for (let j = -taps; j <= taps; j++) {
|
|
24322
|
+
const p = row + mirror(x + j, w) * 4;
|
|
24323
|
+
const kw = k[j + taps];
|
|
24324
|
+
s0 += kw * src[p];
|
|
24325
|
+
s1 += kw * src[p + 1];
|
|
24326
|
+
s2 += kw * src[p + 2];
|
|
24327
|
+
s3 += kw * src[p + 3];
|
|
24328
|
+
}
|
|
24329
|
+
const o = row + x * 4;
|
|
24330
|
+
out[o] = s0;
|
|
24331
|
+
out[o + 1] = s1;
|
|
24332
|
+
out[o + 2] = s2;
|
|
24333
|
+
out[o + 3] = s3;
|
|
24334
|
+
}
|
|
24335
|
+
}
|
|
24336
|
+
if (taps > 0 && vRow >= 0 && vRow < h) {
|
|
24337
|
+
const col = new Float64Array(w * 4);
|
|
24338
|
+
for (let j = -taps; j <= taps; j++) {
|
|
24339
|
+
const yy = vRow + j;
|
|
24340
|
+
if (vZeroAbove && yy < vRow) {
|
|
24341
|
+
continue;
|
|
24342
|
+
}
|
|
24343
|
+
const base = mirror(yy, h) * w * 4;
|
|
24344
|
+
for (let i = 0; i < w * 4; i++) {
|
|
24345
|
+
col[i] += k[j + taps] * out[base + i];
|
|
24346
|
+
}
|
|
24272
24347
|
}
|
|
24348
|
+
out.set(col, vRow * w * 4);
|
|
24273
24349
|
}
|
|
24274
|
-
return
|
|
24350
|
+
return out;
|
|
24275
24351
|
}
|
|
24276
24352
|
function applyBlur(src, w, h, radius) {
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24353
|
+
return Uint8ClampedArray.from(gdipBlur(src, w, h, radius), Math.round);
|
|
24354
|
+
}
|
|
24355
|
+
var SHARPEN_FALLOFF = Math.log2(96 / 25);
|
|
24356
|
+
function sharpenGain(radius, amount) {
|
|
24357
|
+
const x = amount / 100;
|
|
24358
|
+
if (radius <= 3) {
|
|
24359
|
+
return x * 2 * radius / 3;
|
|
24281
24360
|
}
|
|
24282
|
-
return
|
|
24361
|
+
return x * 2 * (3 / radius) ** SHARPEN_FALLOFF;
|
|
24283
24362
|
}
|
|
24284
24363
|
function applySharpen(src, w, h, radius, amount) {
|
|
24285
|
-
const blurred =
|
|
24286
|
-
const
|
|
24364
|
+
const blurred = gdipBlur(src, w, h, radius);
|
|
24365
|
+
const gain = sharpenGain(radius, amount);
|
|
24287
24366
|
const out = new Uint8ClampedArray(src);
|
|
24288
24367
|
for (let i = 0; i < src.length; i += 4) {
|
|
24289
24368
|
for (let c = 0; c < 3; c++) {
|
|
24290
|
-
out[i + c] = Math.round(src[i + c] +
|
|
24369
|
+
out[i + c] = Math.round(src[i + c] + gain * (src[i + c] - Math.round(blurred[i + c])));
|
|
24291
24370
|
}
|
|
24371
|
+
out[i + 3] = Math.max(src[i + 3], out[i], out[i + 1], out[i + 2]);
|
|
24292
24372
|
}
|
|
24293
24373
|
return out;
|
|
24294
24374
|
}
|
|
@@ -24302,16 +24382,15 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
24302
24382
|
case "sharpen":
|
|
24303
24383
|
return applySharpen(rgba, width, height, effect.radius, effect.amount);
|
|
24304
24384
|
case "brightnessContrast": {
|
|
24305
|
-
const
|
|
24306
|
-
const lut = buildLut((v) => contrast(v) + effect.brightness);
|
|
24385
|
+
const lut = brightnessContrastLut(effect.brightness, effect.contrast);
|
|
24307
24386
|
return applyLuts(rgba, lut, lut, lut);
|
|
24308
24387
|
}
|
|
24309
24388
|
case "colorBalance":
|
|
24310
24389
|
return applyLuts(
|
|
24311
24390
|
rgba,
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24391
|
+
balanceLut(effect.cyanRed),
|
|
24392
|
+
balanceLut(effect.magentaGreen),
|
|
24393
|
+
balanceLut(effect.yellowBlue)
|
|
24315
24394
|
);
|
|
24316
24395
|
case "colorCurve": {
|
|
24317
24396
|
const lut = curveAdjustmentLut(effect.adjustment, effect.intensity);
|
|
@@ -24338,6 +24417,38 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
24338
24417
|
return applyTint(rgba, effect.hue, effect.amount);
|
|
24339
24418
|
}
|
|
24340
24419
|
}
|
|
24420
|
+
function copyBlock(src, width, sx, sy, w, h, dst, bw, dx, dy) {
|
|
24421
|
+
for (let y = 0; y < h; y++) {
|
|
24422
|
+
const from = ((sy + y) * width + sx) * 4;
|
|
24423
|
+
dst.set(src.subarray(from, from + w * 4), ((dy + y) * bw + dx) * 4);
|
|
24424
|
+
}
|
|
24425
|
+
}
|
|
24426
|
+
function expandedBlur(rgba, width, height, radius, x0, y0, x1, y1) {
|
|
24427
|
+
const r = Math.ceil(radius);
|
|
24428
|
+
const bw = x1 - x0 + 2 * r;
|
|
24429
|
+
const bh = y1 - y0 + 2 * r;
|
|
24430
|
+
const buf = new Uint8ClampedArray(bw * bh * 4);
|
|
24431
|
+
const inside = x0 - r >= 0 && y0 - r >= 0 && x1 + r <= width && y1 + r <= height;
|
|
24432
|
+
if (inside) {
|
|
24433
|
+
copyBlock(rgba, width, x0, y0, x1 - x0, y1 - y0, buf, bw, r, r);
|
|
24434
|
+
} else {
|
|
24435
|
+
const gx0 = Math.max(0, x0 - r);
|
|
24436
|
+
const gy0 = Math.max(0, y0 - r);
|
|
24437
|
+
const gx1 = Math.min(width, x1 + r);
|
|
24438
|
+
const gy1 = Math.min(height, y1 + r);
|
|
24439
|
+
copyBlock(rgba, width, gx0, gy0, gx1 - gx0, gy1 - gy0, buf, bw, gx0 - (x0 - r), gy0 - (y0 - r));
|
|
24440
|
+
}
|
|
24441
|
+
const blurred = gdipBlur(buf, bw, bh, radius, inside ? r : 0, inside);
|
|
24442
|
+
const w = x1 - x0;
|
|
24443
|
+
const out = new Uint8ClampedArray(w * (y1 - y0) * 4);
|
|
24444
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
24445
|
+
const from = ((y + r) * bw + r) * 4;
|
|
24446
|
+
for (let i = 0; i < w * 4; i++) {
|
|
24447
|
+
out[y * w * 4 + i] = Math.round(blurred[from + i]);
|
|
24448
|
+
}
|
|
24449
|
+
}
|
|
24450
|
+
return out;
|
|
24451
|
+
}
|
|
24341
24452
|
function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
24342
24453
|
if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
24343
24454
|
return null;
|
|
@@ -24349,38 +24460,43 @@ function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
|
24349
24460
|
if (src) {
|
|
24350
24461
|
x0 = clamp(Math.floor(src.x), 0, width);
|
|
24351
24462
|
y0 = clamp(Math.floor(src.y), 0, height);
|
|
24352
|
-
x1 = clamp(Math.ceil(src.x + src.w), 0, width);
|
|
24353
|
-
y1 = clamp(Math.ceil(src.y + src.h), 0, height);
|
|
24354
|
-
if (x1 <= x0 || y1 <= y0) {
|
|
24463
|
+
x1 = clamp(Math.ceil(src.x + src.w) + 1, 0, width);
|
|
24464
|
+
y1 = clamp(Math.ceil(src.y + src.h) + 1, 0, height);
|
|
24465
|
+
if (x1 <= x0 || y1 <= y0 || src.x + src.w <= x0 || src.y + src.h <= y0) {
|
|
24355
24466
|
return null;
|
|
24356
24467
|
}
|
|
24357
24468
|
}
|
|
24358
|
-
const
|
|
24359
|
-
const
|
|
24360
|
-
const
|
|
24361
|
-
const
|
|
24362
|
-
const
|
|
24469
|
+
const w = x1 - x0;
|
|
24470
|
+
const h = y1 - y0;
|
|
24471
|
+
const shownWidth = src ? Math.min(w, Math.ceil(src.x + src.w) - x0) : w;
|
|
24472
|
+
const shownHeight = src ? Math.min(h, Math.ceil(src.y + src.h) - y0) : h;
|
|
24473
|
+
const result = (out2) => {
|
|
24474
|
+
if (shownWidth === w && shownHeight === h) {
|
|
24475
|
+
return { rgba: out2, x: x0, y: y0, width: w, height: h };
|
|
24476
|
+
}
|
|
24477
|
+
const shown = new Uint8ClampedArray(shownWidth * shownHeight * 4);
|
|
24478
|
+
copyBlock(out2, w, 0, 0, shownWidth, shownHeight, shown, shownWidth, 0, 0);
|
|
24479
|
+
return { rgba: shown, x: x0, y: y0, width: shownWidth, height: shownHeight };
|
|
24480
|
+
};
|
|
24481
|
+
if (src && effect.kind === "blur" && effect.expandEdge) {
|
|
24482
|
+
return result(expandedBlur(rgba, width, height, effect.radius, x0, y0, x1, y1));
|
|
24483
|
+
}
|
|
24363
24484
|
let region = rgba;
|
|
24364
|
-
if (w !== width || h !== height
|
|
24485
|
+
if (w !== width || h !== height) {
|
|
24365
24486
|
region = new Uint8ClampedArray(w * h * 4);
|
|
24366
|
-
|
|
24367
|
-
const from = ((y0 + y) * width + x0) * 4;
|
|
24368
|
-
region.set(rgba.subarray(from, from + cw * 4), ((pad + y) * w + pad) * 4);
|
|
24369
|
-
}
|
|
24487
|
+
copyBlock(rgba, width, x0, y0, w, h, region, w, 0, 0);
|
|
24370
24488
|
}
|
|
24371
|
-
const ox = x0 - pad;
|
|
24372
|
-
const oy = y0 - pad;
|
|
24373
24489
|
const local = effect.kind === "redEyeCorrection" ? {
|
|
24374
24490
|
kind: effect.kind,
|
|
24375
24491
|
areas: effect.areas.map((a) => ({
|
|
24376
|
-
left: a.left -
|
|
24377
|
-
top: a.top -
|
|
24378
|
-
right: a.right -
|
|
24379
|
-
bottom: a.bottom -
|
|
24492
|
+
left: a.left - x0,
|
|
24493
|
+
top: a.top - y0,
|
|
24494
|
+
right: a.right - x0,
|
|
24495
|
+
bottom: a.bottom - y0
|
|
24380
24496
|
}))
|
|
24381
24497
|
} : effect;
|
|
24382
24498
|
const out = applyImageEffect(region, w, h, local);
|
|
24383
|
-
return out ?
|
|
24499
|
+
return out ? result(out) : null;
|
|
24384
24500
|
}
|
|
24385
24501
|
|
|
24386
24502
|
// src/emf-plus-draw-image.ts
|
|
@@ -24544,11 +24660,10 @@ function effectedDraw(img, bitmap, effect, draw, source) {
|
|
|
24544
24660
|
if (!rect) {
|
|
24545
24661
|
return { bitmap: out, draw };
|
|
24546
24662
|
}
|
|
24547
|
-
const
|
|
24548
|
-
const
|
|
24549
|
-
const
|
|
24550
|
-
const
|
|
24551
|
-
const srcH = rect.srcH + 2 * grow;
|
|
24663
|
+
const srcX = rect.srcX - region.x;
|
|
24664
|
+
const srcY = rect.srcY - region.y;
|
|
24665
|
+
const srcW = rect.srcW;
|
|
24666
|
+
const srcH = rect.srcH;
|
|
24552
24667
|
const shift = [1, 0, 0, 1, region.x, region.y];
|
|
24553
24668
|
const toWorld = mulMatrix(rect.toWorld(rect.srcX, rect.srcY, rect.srcW, rect.srcH), shift);
|
|
24554
24669
|
return {
|
package/dist/index.mjs
CHANGED
|
@@ -12317,7 +12317,7 @@ function blendSeparable(rgba, width, box, iu0, wu, iv0, wv, verticalFirst, edge
|
|
|
12317
12317
|
}
|
|
12318
12318
|
var OUTSIDE_TRANSPARENT = -1;
|
|
12319
12319
|
var OUTSIDE_CLAMP = -2;
|
|
12320
|
-
function wrapTap(t, lo, hi,
|
|
12320
|
+
function wrapTap(t, lo, hi, mirror2, edge) {
|
|
12321
12321
|
if (t >= lo && t < hi) {
|
|
12322
12322
|
return t;
|
|
12323
12323
|
}
|
|
@@ -12328,7 +12328,7 @@ function wrapTap(t, lo, hi, mirror, edge) {
|
|
|
12328
12328
|
return OUTSIDE_CLAMP;
|
|
12329
12329
|
}
|
|
12330
12330
|
const n = hi - lo;
|
|
12331
|
-
if (!
|
|
12331
|
+
if (!mirror2) {
|
|
12332
12332
|
return lo + ((t - lo) % n + n) % n;
|
|
12333
12333
|
}
|
|
12334
12334
|
const q = ((t - lo) % (2 * n) + 2 * n) % (2 * n);
|
|
@@ -17628,9 +17628,9 @@ function buildPattern(ctx, rect, tw, th, wrap, brush, delta, sampling, colorAt)
|
|
|
17628
17628
|
if (typeof ctx.createPattern !== "function") {
|
|
17629
17629
|
return null;
|
|
17630
17630
|
}
|
|
17631
|
-
const
|
|
17632
|
-
const w = tw * (
|
|
17633
|
-
const h = th * (
|
|
17631
|
+
const mirror2 = wrap === "clamp" ? { x: false, y: false } : mirrorFlags(wrap);
|
|
17632
|
+
const w = tw * (mirror2.x ? 2 : 1);
|
|
17633
|
+
const h = th * (mirror2.y ? 2 : 1);
|
|
17634
17634
|
const temp = createTempCanvas(w, h);
|
|
17635
17635
|
if (!temp) {
|
|
17636
17636
|
return null;
|
|
@@ -18052,8 +18052,8 @@ function createHatchPattern(ctx, hatch, origin, user, deviceToCanvas) {
|
|
|
18052
18052
|
}
|
|
18053
18053
|
|
|
18054
18054
|
// src/emf-plus-brush-texture.ts
|
|
18055
|
-
function wrapTexel(i, n,
|
|
18056
|
-
if (!
|
|
18055
|
+
function wrapTexel(i, n, mirror2) {
|
|
18056
|
+
if (!mirror2) {
|
|
18057
18057
|
return (i % n + n) % n;
|
|
18058
18058
|
}
|
|
18059
18059
|
const p = (i % (2 * n) + 2 * n) % (2 * n);
|
|
@@ -19114,11 +19114,11 @@ function boundaryBox2(points) {
|
|
|
19114
19114
|
}
|
|
19115
19115
|
return x1 > x0 && y1 > y0 ? { x: x0, y: y0, w: x1 - x0, h: y1 - y0 } : null;
|
|
19116
19116
|
}
|
|
19117
|
-
function foldIntoTile(v, origin, size,
|
|
19117
|
+
function foldIntoTile(v, origin, size, mirror2, lag) {
|
|
19118
19118
|
const rel = v - origin;
|
|
19119
19119
|
const k = Math.floor(rel / size);
|
|
19120
19120
|
const local = rel - k * size;
|
|
19121
|
-
if (
|
|
19121
|
+
if (mirror2 && (k % 2 + 2) % 2 === 1) {
|
|
19122
19122
|
return origin + size - lag - local;
|
|
19123
19123
|
}
|
|
19124
19124
|
return origin + local;
|
|
@@ -23972,6 +23972,7 @@ function readGuid(view, off) {
|
|
|
23972
23972
|
].join("-");
|
|
23973
23973
|
}
|
|
23974
23974
|
var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
23975
|
+
var COLOR_CURVE_PARAMS_SIZE = 12;
|
|
23975
23976
|
function parseSerializableObject(view, dataOff, dataSize) {
|
|
23976
23977
|
if (dataSize < 20) {
|
|
23977
23978
|
return null;
|
|
@@ -23996,7 +23997,7 @@ function parseSerializableObject(view, dataOff, dataSize) {
|
|
|
23996
23997
|
case "colorBalance":
|
|
23997
23998
|
return need(12) ? { kind, cyanRed: i32(0, -100, 100), magentaGreen: i32(1, -100, 100), yellowBlue: i32(2, -100, 100) } : null;
|
|
23998
23999
|
case "colorCurve":
|
|
23999
|
-
return need(
|
|
24000
|
+
return need(COLOR_CURVE_PARAMS_SIZE) ? {
|
|
24000
24001
|
kind,
|
|
24001
24002
|
adjustment: view.getInt32(p, true),
|
|
24002
24003
|
channel: view.getInt32(p + 4, true),
|
|
@@ -24050,66 +24051,101 @@ function parseSerializableObject(view, dataOff, dataSize) {
|
|
|
24050
24051
|
return need(8) ? { kind, hue: i32(0, -180, 180), amount: i32(1, -100, 100) } : null;
|
|
24051
24052
|
}
|
|
24052
24053
|
}
|
|
24053
|
-
|
|
24054
|
+
var roundHalfDown = (v) => Math.ceil(v - 0.5);
|
|
24055
|
+
function buildLut(f, round = Math.round) {
|
|
24054
24056
|
const lut = new Uint8Array(256);
|
|
24055
24057
|
for (let v = 0; v < 256; v++) {
|
|
24056
|
-
lut[v] = clamp(
|
|
24058
|
+
lut[v] = clamp(round(f(v)), 0, 255);
|
|
24057
24059
|
}
|
|
24058
24060
|
return lut;
|
|
24059
24061
|
}
|
|
24060
|
-
function
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
const
|
|
24066
|
-
return (v) =>
|
|
24067
|
-
}
|
|
24068
|
-
function
|
|
24069
|
-
|
|
24070
|
-
|
|
24062
|
+
function midtoneGamma(t) {
|
|
24063
|
+
return t >= 0 ? 1 / (1 + t / 50) : 1 - t / 50;
|
|
24064
|
+
}
|
|
24065
|
+
function brightnessContrastLut(brightness, contrast) {
|
|
24066
|
+
const gain = contrast >= 100 ? 1e6 : contrast >= 0 ? 100 / (100 - contrast) : (100 + contrast) / 100;
|
|
24067
|
+
const half = brightness / 2;
|
|
24068
|
+
return buildLut((v) => (v + half - 127.5) * gain + 127.5 + half);
|
|
24069
|
+
}
|
|
24070
|
+
function balanceLut(t) {
|
|
24071
|
+
return buildLut((v) => v * (1 + t / 100));
|
|
24072
|
+
}
|
|
24073
|
+
function splineLut(ys) {
|
|
24074
|
+
const n = ys.length;
|
|
24075
|
+
const h = 255 / (n - 1);
|
|
24076
|
+
const m = new Float64Array(n);
|
|
24077
|
+
const c = new Float64Array(n);
|
|
24078
|
+
const d = new Float64Array(n);
|
|
24079
|
+
for (let i = 1; i < n - 1; i++) {
|
|
24080
|
+
const rhs = 6 * (ys[i + 1] - 2 * ys[i] + ys[i - 1]) / (h * h);
|
|
24081
|
+
const den = 4 - c[i - 1];
|
|
24082
|
+
c[i] = 1 / den;
|
|
24083
|
+
d[i] = (rhs - d[i - 1]) / den;
|
|
24084
|
+
}
|
|
24085
|
+
for (let i = n - 2; i >= 1; i--) {
|
|
24086
|
+
m[i] = d[i] - c[i] * m[i + 1];
|
|
24087
|
+
}
|
|
24088
|
+
return buildLut((v) => {
|
|
24089
|
+
const i = Math.min(n - 2, Math.floor(v / h));
|
|
24090
|
+
const b = (v - i * h) / h;
|
|
24091
|
+
const a = 1 - b;
|
|
24092
|
+
return a * ys[i] + b * ys[i + 1] + ((a * a * a - a) * m[i] + (b * b * b - b) * m[i + 1]) * h * h / 6;
|
|
24093
|
+
});
|
|
24071
24094
|
}
|
|
24095
|
+
var CURVE_POINTS = 23;
|
|
24096
|
+
var CURVE_X = Array.from({ length: CURVE_POINTS }, (_, k) => k * 255 / (CURVE_POINTS - 1));
|
|
24097
|
+
var CURVE_TABLES = {
|
|
24098
|
+
"contrast+": [0, 3.8, 8, 12.2, 17.4, 23.5, 31.1, 40.2, 53, 70.2, 94.5, 127.5, 160.5, 184.8, 202, 214.8, 223.9, 231.5, 237.6, 242.8, 247, 251.2, 255],
|
|
24099
|
+
"contrast-": [0, 33.1, 57.2, 74.7, 87.2, 96.6, 103.9, 109.8, 115.3, 119.6, 123.7, 127.5, 131.3, 135.4, 139.7, 145.2, 151.1, 158.4, 167.8, 180.3, 197.8, 221.9, 255],
|
|
24100
|
+
"highlight+": [0, 11.6, 23.2, 34.8, 46.4, 58, 69.6, 81.1, 92.7, 104.3, 116, 127.6, 142.3, 160.6, 186.4, 208, 220.6, 229.4, 236.2, 242, 246.6, 251, 255],
|
|
24101
|
+
"highlight-": [0, 11.6, 23.2, 34.8, 46.4, 58, 69.5, 81.1, 92.7, 104.4, 115.9, 127.5, 136.7, 144.9, 151.4, 157.1, 161.8, 167.2, 174.3, 185.3, 200.9, 223.7, 255],
|
|
24102
|
+
"shadow+": [0, 31.3, 54, 70, 82.5, 92.3, 99.5, 104.8, 109.5, 115, 121.3, 127.7, 139.2, 150.6, 162.3, 173.8, 185.5, 197, 208.6, 220.2, 231.8, 243.4, 255],
|
|
24103
|
+
"shadow-": [0, 4, 8.4, 13, 18.7, 25.7, 34.4, 45, 58.4, 80, 105.8, 127.5, 139.2, 150.7, 162.3, 173.9, 185.4, 197, 208.6, 220.2, 231.8, 243.4, 255]
|
|
24104
|
+
};
|
|
24072
24105
|
function curveAdjustmentLut(adjustment, intensity) {
|
|
24073
24106
|
const t = intensity;
|
|
24107
|
+
let ys;
|
|
24074
24108
|
switch (adjustment) {
|
|
24075
24109
|
case CurveAdjustment.Exposure:
|
|
24076
|
-
|
|
24110
|
+
ys = CURVE_X.map((x) => roundHalfDown(x) + t);
|
|
24111
|
+
break;
|
|
24077
24112
|
case CurveAdjustment.Density:
|
|
24078
|
-
|
|
24113
|
+
ys = CURVE_X.map((x) => x + t);
|
|
24114
|
+
break;
|
|
24079
24115
|
case CurveAdjustment.Contrast:
|
|
24080
|
-
return buildLut(contrastCurve(clamp(t, -100, 100)));
|
|
24081
24116
|
case CurveAdjustment.Highlight:
|
|
24082
24117
|
case CurveAdjustment.Shadow: {
|
|
24083
|
-
const
|
|
24084
|
-
const
|
|
24085
|
-
|
|
24086
|
-
|
|
24087
|
-
|
|
24088
|
-
|
|
24089
|
-
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
|
|
24118
|
+
const c = clamp(t, -100, 100);
|
|
24119
|
+
const name = ["contrast", "highlight", "shadow"][adjustment - CurveAdjustment.Contrast] + (c >= 0 ? "+" : "-");
|
|
24120
|
+
const table = CURVE_TABLES[name];
|
|
24121
|
+
ys = CURVE_X.map((x, k) => x + Math.abs(c) / 50 * (table[k] - x));
|
|
24122
|
+
break;
|
|
24123
|
+
}
|
|
24124
|
+
case CurveAdjustment.Midtone: {
|
|
24125
|
+
const g = midtoneGamma(clamp(t, -100, 100));
|
|
24126
|
+
ys = CURVE_X.map((x) => 255 * (x / 255) ** g);
|
|
24127
|
+
break;
|
|
24093
24128
|
}
|
|
24094
|
-
case CurveAdjustment.Midtone:
|
|
24095
|
-
return buildLut(midtoneCurve(clamp(t, -100, 100)));
|
|
24096
24129
|
case CurveAdjustment.WhiteSaturation: {
|
|
24097
24130
|
const w = Math.max(1, clamp(t, 0, 255));
|
|
24098
|
-
|
|
24131
|
+
ys = CURVE_X.map((x) => x * 255 / w);
|
|
24132
|
+
break;
|
|
24099
24133
|
}
|
|
24100
24134
|
case CurveAdjustment.BlackSaturation: {
|
|
24101
24135
|
const b = Math.min(254, clamp(t, 0, 255));
|
|
24102
|
-
|
|
24136
|
+
ys = CURVE_X.map((x) => (x - b) * 255 / (255 - b));
|
|
24137
|
+
break;
|
|
24103
24138
|
}
|
|
24104
24139
|
default:
|
|
24105
24140
|
return null;
|
|
24106
24141
|
}
|
|
24142
|
+
return splineLut(ys.map((y) => clamp(y, 0, 255)));
|
|
24107
24143
|
}
|
|
24108
24144
|
function levelsLut(highlight, midtone, shadow) {
|
|
24109
24145
|
const black = shadow / 100 * 255;
|
|
24110
24146
|
const white = Math.max(black + 1, highlight / 100 * 255);
|
|
24111
|
-
const gamma =
|
|
24112
|
-
return buildLut((v) =>
|
|
24147
|
+
const gamma = midtoneGamma(midtone);
|
|
24148
|
+
return buildLut((v) => 255 * clamp((v - black) / (white - black), 0, 1) ** gamma, roundHalfDown);
|
|
24113
24149
|
}
|
|
24114
24150
|
function applyLuts(src, r, g, b, a = null) {
|
|
24115
24151
|
const out = new Uint8ClampedArray(src);
|
|
@@ -24135,26 +24171,6 @@ function applyColorMatrix(src, m) {
|
|
|
24135
24171
|
}
|
|
24136
24172
|
return out;
|
|
24137
24173
|
}
|
|
24138
|
-
function rgbToHsl(r, g, b) {
|
|
24139
|
-
const max = Math.max(r, g, b);
|
|
24140
|
-
const min = Math.min(r, g, b);
|
|
24141
|
-
const l = (max + min) / 2;
|
|
24142
|
-
const d = max - min;
|
|
24143
|
-
if (d === 0) {
|
|
24144
|
-
return [0, 0, l];
|
|
24145
|
-
}
|
|
24146
|
-
const s = d / (1 - Math.abs(2 * l - 1));
|
|
24147
|
-
let h;
|
|
24148
|
-
if (max === r) {
|
|
24149
|
-
h = (g - b) / d % 6;
|
|
24150
|
-
} else if (max === g) {
|
|
24151
|
-
h = (b - r) / d + 2;
|
|
24152
|
-
} else {
|
|
24153
|
-
h = (r - g) / d + 4;
|
|
24154
|
-
}
|
|
24155
|
-
h *= 60;
|
|
24156
|
-
return [h < 0 ? h + 360 : h, s, l];
|
|
24157
|
-
}
|
|
24158
24174
|
function hslToRgb(h, s, l) {
|
|
24159
24175
|
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
24160
24176
|
const hp = (h % 360 + 360) % 360 / 60;
|
|
@@ -24163,29 +24179,80 @@ function hslToRgb(h, s, l) {
|
|
|
24163
24179
|
const [r, g, b] = hp < 1 ? [c, x, 0] : hp < 2 ? [x, c, 0] : hp < 3 ? [0, c, x] : hp < 4 ? [0, x, c] : hp < 5 ? [x, 0, c] : [c, 0, x];
|
|
24164
24180
|
return [r + m, g + m, b + m];
|
|
24165
24181
|
}
|
|
24182
|
+
var HUE_SEXTANT = 43;
|
|
24183
|
+
var HUE_CIRCLE = 6 * HUE_SEXTANT;
|
|
24184
|
+
function gdipHue(r, g, b) {
|
|
24185
|
+
const max = Math.max(r, g, b);
|
|
24186
|
+
const d = max - Math.min(r, g, b);
|
|
24187
|
+
const f = (x) => Math.floor(x * HUE_SEXTANT / d);
|
|
24188
|
+
if (max === r && g >= b) {
|
|
24189
|
+
return f(g - b);
|
|
24190
|
+
}
|
|
24191
|
+
if (max === g) {
|
|
24192
|
+
return b >= r ? 87 + f(b - r) : 86 - f(r - b);
|
|
24193
|
+
}
|
|
24194
|
+
if (max === b && b !== r) {
|
|
24195
|
+
return r >= g ? 172 + f(r - g) : 171 - f(g - r);
|
|
24196
|
+
}
|
|
24197
|
+
const t = f(b - g);
|
|
24198
|
+
return (256 - t - (t >= 40 ? 1 : 0)) % 256;
|
|
24199
|
+
}
|
|
24166
24200
|
function applyHueSaturationLightness(src, hue, saturation, lightness2) {
|
|
24167
24201
|
const out = new Uint8ClampedArray(src);
|
|
24168
|
-
const
|
|
24202
|
+
const shift = Math.round(hue * HUE_CIRCLE / 360);
|
|
24203
|
+
const sMul = 1 + saturation / 100;
|
|
24169
24204
|
for (let i = 0; i < src.length; i += 4) {
|
|
24170
|
-
const
|
|
24171
|
-
const
|
|
24172
|
-
|
|
24173
|
-
|
|
24174
|
-
|
|
24205
|
+
const r = src[i];
|
|
24206
|
+
const g = src[i + 1];
|
|
24207
|
+
const b = src[i + 2];
|
|
24208
|
+
const max = Math.max(r, g, b);
|
|
24209
|
+
const min = Math.min(r, g, b);
|
|
24210
|
+
const l = clamp(Math.round(((max + min >> 1) * 100 + 255 * lightness2) / 100), 0, 255);
|
|
24211
|
+
if (max === min) {
|
|
24212
|
+
out[i] = out[i + 1] = out[i + 2] = l;
|
|
24213
|
+
continue;
|
|
24214
|
+
}
|
|
24215
|
+
const sum = max + min;
|
|
24216
|
+
const s = clamp((max - min) / (sum <= 255 ? sum : 510 - sum) * sMul, 0, 1);
|
|
24217
|
+
const hi = l <= 127 ? l * (1 + s) : l + s * (255 - l);
|
|
24218
|
+
const m1 = l <= 127 ? 2 * l - Math.trunc(hi) : Math.round(2 * l - hi);
|
|
24219
|
+
const m2 = 2 * l - m1;
|
|
24220
|
+
const q = ((gdipHue(r, g, b) + shift) % HUE_CIRCLE + HUE_CIRCLE) % HUE_CIRCLE;
|
|
24221
|
+
const sextant = Math.min(5, Math.floor(q / HUE_SEXTANT));
|
|
24222
|
+
const f = (q - sextant * HUE_SEXTANT) / HUE_SEXTANT;
|
|
24223
|
+
const up = Math.trunc(m1 + (m2 - m1) * f);
|
|
24224
|
+
const down = Math.trunc(m1 + (m2 - m1) * (1 - f));
|
|
24225
|
+
const [nr, ng, nb] = [
|
|
24226
|
+
[m2, up, m1],
|
|
24227
|
+
[down, m2, m1],
|
|
24228
|
+
[m1, m2, up],
|
|
24229
|
+
[m1, down, m2],
|
|
24230
|
+
[up, m1, m2],
|
|
24231
|
+
[m2, m1, down]
|
|
24232
|
+
][sextant];
|
|
24233
|
+
out[i] = nr;
|
|
24234
|
+
out[i + 1] = ng;
|
|
24235
|
+
out[i + 2] = nb;
|
|
24175
24236
|
}
|
|
24176
24237
|
return out;
|
|
24177
24238
|
}
|
|
24178
|
-
var
|
|
24239
|
+
var LUMA_709 = [0.2126, 0.7152, 0.0722];
|
|
24240
|
+
var TINT_CHROMA_SCALE = 0.985;
|
|
24179
24241
|
function applyTint(src, hue, amount) {
|
|
24180
24242
|
const tint = hslToRgb(hue, 1, 0.5);
|
|
24181
|
-
const
|
|
24182
|
-
const
|
|
24183
|
-
const
|
|
24243
|
+
const ty = tint[0] * LUMA_709[0] + tint[1] * LUMA_709[1] + tint[2] * LUMA_709[2];
|
|
24244
|
+
const chroma = tint.map((c) => (c - ty) * TINT_CHROMA_SCALE);
|
|
24245
|
+
const a = amount / 100;
|
|
24184
24246
|
const out = new Uint8ClampedArray(src);
|
|
24185
24247
|
for (let i = 0; i < src.length; i += 4) {
|
|
24186
|
-
|
|
24187
|
-
|
|
24188
|
-
|
|
24248
|
+
const r = src[i];
|
|
24249
|
+
const g = src[i + 1];
|
|
24250
|
+
const b = src[i + 2];
|
|
24251
|
+
const y = r * LUMA_709[0] + g * LUMA_709[1] + b * LUMA_709[2];
|
|
24252
|
+
const v = Math.max(r, g, b);
|
|
24253
|
+
out[i] = Math.round((1 - a) * r + a * (y + v * chroma[0]));
|
|
24254
|
+
out[i + 1] = Math.round((1 - a) * g + a * (y + v * chroma[1]));
|
|
24255
|
+
out[i + 2] = Math.round((1 - a) * b + a * (y + v * chroma[2]));
|
|
24189
24256
|
}
|
|
24190
24257
|
return out;
|
|
24191
24258
|
}
|
|
@@ -24202,7 +24269,7 @@ function applyRedEyeCorrection(src, width, height, areas) {
|
|
|
24202
24269
|
const r = out[i];
|
|
24203
24270
|
const g = out[i + 1];
|
|
24204
24271
|
const b = out[i + 2];
|
|
24205
|
-
if (r
|
|
24272
|
+
if (r >= 2 * g && r >= 2 * b && r > 64) {
|
|
24206
24273
|
out[i] = Math.round((g + b) / 2);
|
|
24207
24274
|
}
|
|
24208
24275
|
}
|
|
@@ -24210,83 +24277,96 @@ function applyRedEyeCorrection(src, width, height, areas) {
|
|
|
24210
24277
|
}
|
|
24211
24278
|
return out;
|
|
24212
24279
|
}
|
|
24213
|
-
function
|
|
24214
|
-
const
|
|
24215
|
-
|
|
24216
|
-
|
|
24217
|
-
k
|
|
24280
|
+
function blurKernel(radius) {
|
|
24281
|
+
const taps = Math.ceil(radius);
|
|
24282
|
+
const k = new Float64Array(2 * taps + 1);
|
|
24283
|
+
if (taps === 0) {
|
|
24284
|
+
k[0] = 1;
|
|
24285
|
+
return k;
|
|
24218
24286
|
}
|
|
24219
|
-
const
|
|
24220
|
-
let
|
|
24221
|
-
for (let
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
}
|
|
24226
|
-
return [0, 1, 2].map((i) => i < best ? k + 1 : k);
|
|
24227
|
-
}
|
|
24228
|
-
function boxPass(src, w, h, r, horizontal) {
|
|
24229
|
-
if (r <= 0) {
|
|
24230
|
-
return src;
|
|
24231
|
-
}
|
|
24232
|
-
const out = new Float64Array(src.length);
|
|
24233
|
-
const lines = horizontal ? h : w;
|
|
24234
|
-
const len = horizontal ? w : h;
|
|
24235
|
-
const step = horizontal ? 4 : w * 4;
|
|
24236
|
-
const norm = 1 / (2 * r + 1);
|
|
24237
|
-
for (let line = 0; line < lines; line++) {
|
|
24238
|
-
const base = horizontal ? line * w * 4 : line * 4;
|
|
24239
|
-
for (let c = 0; c < 4; c++) {
|
|
24240
|
-
const at = (p) => src[base + clamp(p, 0, len - 1) * step + c];
|
|
24241
|
-
let sum = 0;
|
|
24242
|
-
for (let p = -r; p <= r; p++) {
|
|
24243
|
-
sum += at(p);
|
|
24244
|
-
}
|
|
24245
|
-
for (let p = 0; p < len; p++) {
|
|
24246
|
-
out[base + p * step + c] = sum * norm;
|
|
24247
|
-
sum += at(p + r + 1) - at(p - r);
|
|
24248
|
-
}
|
|
24249
|
-
}
|
|
24287
|
+
const sigma = radius / 1.98;
|
|
24288
|
+
let sum = 0;
|
|
24289
|
+
for (let i = -taps; i <= taps; i++) {
|
|
24290
|
+
const w = Math.exp(-(i * i) / (2 * sigma * sigma));
|
|
24291
|
+
k[i + taps] = w;
|
|
24292
|
+
sum += w;
|
|
24250
24293
|
}
|
|
24251
|
-
|
|
24294
|
+
for (let i = 0; i < k.length; i++) {
|
|
24295
|
+
k[i] /= sum;
|
|
24296
|
+
}
|
|
24297
|
+
return k;
|
|
24252
24298
|
}
|
|
24253
|
-
function
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
const a = src[i + 3] / 255;
|
|
24257
|
-
buf[i] = src[i] * a;
|
|
24258
|
-
buf[i + 1] = src[i + 1] * a;
|
|
24259
|
-
buf[i + 2] = src[i + 2] * a;
|
|
24260
|
-
buf[i + 3] = src[i + 3];
|
|
24299
|
+
function mirror(p, n) {
|
|
24300
|
+
if (n === 1) {
|
|
24301
|
+
return 0;
|
|
24261
24302
|
}
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
buf = boxPass(buf, w, h, r, false);
|
|
24303
|
+
while (p < 0 || p >= n) {
|
|
24304
|
+
p = p < 0 ? -p : 2 * (n - 1) - p;
|
|
24265
24305
|
}
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24306
|
+
return p;
|
|
24307
|
+
}
|
|
24308
|
+
function gdipBlur(src, w, h, radius, vRow = 0, vZeroAbove = false) {
|
|
24309
|
+
const k = blurKernel(radius);
|
|
24310
|
+
const taps = (k.length - 1) / 2;
|
|
24311
|
+
const out = new Float64Array(w * h * 4);
|
|
24312
|
+
for (let y = 0; y < h; y++) {
|
|
24313
|
+
const row = y * w * 4;
|
|
24314
|
+
for (let x = 0; x < w; x++) {
|
|
24315
|
+
let s0 = 0;
|
|
24316
|
+
let s1 = 0;
|
|
24317
|
+
let s2 = 0;
|
|
24318
|
+
let s3 = 0;
|
|
24319
|
+
for (let j = -taps; j <= taps; j++) {
|
|
24320
|
+
const p = row + mirror(x + j, w) * 4;
|
|
24321
|
+
const kw = k[j + taps];
|
|
24322
|
+
s0 += kw * src[p];
|
|
24323
|
+
s1 += kw * src[p + 1];
|
|
24324
|
+
s2 += kw * src[p + 2];
|
|
24325
|
+
s3 += kw * src[p + 3];
|
|
24326
|
+
}
|
|
24327
|
+
const o = row + x * 4;
|
|
24328
|
+
out[o] = s0;
|
|
24329
|
+
out[o + 1] = s1;
|
|
24330
|
+
out[o + 2] = s2;
|
|
24331
|
+
out[o + 3] = s3;
|
|
24332
|
+
}
|
|
24333
|
+
}
|
|
24334
|
+
if (taps > 0 && vRow >= 0 && vRow < h) {
|
|
24335
|
+
const col = new Float64Array(w * 4);
|
|
24336
|
+
for (let j = -taps; j <= taps; j++) {
|
|
24337
|
+
const yy = vRow + j;
|
|
24338
|
+
if (vZeroAbove && yy < vRow) {
|
|
24339
|
+
continue;
|
|
24340
|
+
}
|
|
24341
|
+
const base = mirror(yy, h) * w * 4;
|
|
24342
|
+
for (let i = 0; i < w * 4; i++) {
|
|
24343
|
+
col[i] += k[j + taps] * out[base + i];
|
|
24344
|
+
}
|
|
24270
24345
|
}
|
|
24346
|
+
out.set(col, vRow * w * 4);
|
|
24271
24347
|
}
|
|
24272
|
-
return
|
|
24348
|
+
return out;
|
|
24273
24349
|
}
|
|
24274
24350
|
function applyBlur(src, w, h, radius) {
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24351
|
+
return Uint8ClampedArray.from(gdipBlur(src, w, h, radius), Math.round);
|
|
24352
|
+
}
|
|
24353
|
+
var SHARPEN_FALLOFF = Math.log2(96 / 25);
|
|
24354
|
+
function sharpenGain(radius, amount) {
|
|
24355
|
+
const x = amount / 100;
|
|
24356
|
+
if (radius <= 3) {
|
|
24357
|
+
return x * 2 * radius / 3;
|
|
24279
24358
|
}
|
|
24280
|
-
return
|
|
24359
|
+
return x * 2 * (3 / radius) ** SHARPEN_FALLOFF;
|
|
24281
24360
|
}
|
|
24282
24361
|
function applySharpen(src, w, h, radius, amount) {
|
|
24283
|
-
const blurred =
|
|
24284
|
-
const
|
|
24362
|
+
const blurred = gdipBlur(src, w, h, radius);
|
|
24363
|
+
const gain = sharpenGain(radius, amount);
|
|
24285
24364
|
const out = new Uint8ClampedArray(src);
|
|
24286
24365
|
for (let i = 0; i < src.length; i += 4) {
|
|
24287
24366
|
for (let c = 0; c < 3; c++) {
|
|
24288
|
-
out[i + c] = Math.round(src[i + c] +
|
|
24367
|
+
out[i + c] = Math.round(src[i + c] + gain * (src[i + c] - Math.round(blurred[i + c])));
|
|
24289
24368
|
}
|
|
24369
|
+
out[i + 3] = Math.max(src[i + 3], out[i], out[i + 1], out[i + 2]);
|
|
24290
24370
|
}
|
|
24291
24371
|
return out;
|
|
24292
24372
|
}
|
|
@@ -24300,16 +24380,15 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
24300
24380
|
case "sharpen":
|
|
24301
24381
|
return applySharpen(rgba, width, height, effect.radius, effect.amount);
|
|
24302
24382
|
case "brightnessContrast": {
|
|
24303
|
-
const
|
|
24304
|
-
const lut = buildLut((v) => contrast(v) + effect.brightness);
|
|
24383
|
+
const lut = brightnessContrastLut(effect.brightness, effect.contrast);
|
|
24305
24384
|
return applyLuts(rgba, lut, lut, lut);
|
|
24306
24385
|
}
|
|
24307
24386
|
case "colorBalance":
|
|
24308
24387
|
return applyLuts(
|
|
24309
24388
|
rgba,
|
|
24310
|
-
|
|
24311
|
-
|
|
24312
|
-
|
|
24389
|
+
balanceLut(effect.cyanRed),
|
|
24390
|
+
balanceLut(effect.magentaGreen),
|
|
24391
|
+
balanceLut(effect.yellowBlue)
|
|
24313
24392
|
);
|
|
24314
24393
|
case "colorCurve": {
|
|
24315
24394
|
const lut = curveAdjustmentLut(effect.adjustment, effect.intensity);
|
|
@@ -24336,6 +24415,38 @@ function applyImageEffect(rgba, width, height, effect) {
|
|
|
24336
24415
|
return applyTint(rgba, effect.hue, effect.amount);
|
|
24337
24416
|
}
|
|
24338
24417
|
}
|
|
24418
|
+
function copyBlock(src, width, sx, sy, w, h, dst, bw, dx, dy) {
|
|
24419
|
+
for (let y = 0; y < h; y++) {
|
|
24420
|
+
const from = ((sy + y) * width + sx) * 4;
|
|
24421
|
+
dst.set(src.subarray(from, from + w * 4), ((dy + y) * bw + dx) * 4);
|
|
24422
|
+
}
|
|
24423
|
+
}
|
|
24424
|
+
function expandedBlur(rgba, width, height, radius, x0, y0, x1, y1) {
|
|
24425
|
+
const r = Math.ceil(radius);
|
|
24426
|
+
const bw = x1 - x0 + 2 * r;
|
|
24427
|
+
const bh = y1 - y0 + 2 * r;
|
|
24428
|
+
const buf = new Uint8ClampedArray(bw * bh * 4);
|
|
24429
|
+
const inside = x0 - r >= 0 && y0 - r >= 0 && x1 + r <= width && y1 + r <= height;
|
|
24430
|
+
if (inside) {
|
|
24431
|
+
copyBlock(rgba, width, x0, y0, x1 - x0, y1 - y0, buf, bw, r, r);
|
|
24432
|
+
} else {
|
|
24433
|
+
const gx0 = Math.max(0, x0 - r);
|
|
24434
|
+
const gy0 = Math.max(0, y0 - r);
|
|
24435
|
+
const gx1 = Math.min(width, x1 + r);
|
|
24436
|
+
const gy1 = Math.min(height, y1 + r);
|
|
24437
|
+
copyBlock(rgba, width, gx0, gy0, gx1 - gx0, gy1 - gy0, buf, bw, gx0 - (x0 - r), gy0 - (y0 - r));
|
|
24438
|
+
}
|
|
24439
|
+
const blurred = gdipBlur(buf, bw, bh, radius, inside ? r : 0, inside);
|
|
24440
|
+
const w = x1 - x0;
|
|
24441
|
+
const out = new Uint8ClampedArray(w * (y1 - y0) * 4);
|
|
24442
|
+
for (let y = 0; y < y1 - y0; y++) {
|
|
24443
|
+
const from = ((y + r) * bw + r) * 4;
|
|
24444
|
+
for (let i = 0; i < w * 4; i++) {
|
|
24445
|
+
out[y * w * 4 + i] = Math.round(blurred[from + i]);
|
|
24446
|
+
}
|
|
24447
|
+
}
|
|
24448
|
+
return out;
|
|
24449
|
+
}
|
|
24339
24450
|
function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
24340
24451
|
if (width <= 0 || height <= 0 || rgba.length !== width * height * 4) {
|
|
24341
24452
|
return null;
|
|
@@ -24347,38 +24458,43 @@ function applyImageEffectToRect(rgba, width, height, effect, src) {
|
|
|
24347
24458
|
if (src) {
|
|
24348
24459
|
x0 = clamp(Math.floor(src.x), 0, width);
|
|
24349
24460
|
y0 = clamp(Math.floor(src.y), 0, height);
|
|
24350
|
-
x1 = clamp(Math.ceil(src.x + src.w), 0, width);
|
|
24351
|
-
y1 = clamp(Math.ceil(src.y + src.h), 0, height);
|
|
24352
|
-
if (x1 <= x0 || y1 <= y0) {
|
|
24461
|
+
x1 = clamp(Math.ceil(src.x + src.w) + 1, 0, width);
|
|
24462
|
+
y1 = clamp(Math.ceil(src.y + src.h) + 1, 0, height);
|
|
24463
|
+
if (x1 <= x0 || y1 <= y0 || src.x + src.w <= x0 || src.y + src.h <= y0) {
|
|
24353
24464
|
return null;
|
|
24354
24465
|
}
|
|
24355
24466
|
}
|
|
24356
|
-
const
|
|
24357
|
-
const
|
|
24358
|
-
const
|
|
24359
|
-
const
|
|
24360
|
-
const
|
|
24467
|
+
const w = x1 - x0;
|
|
24468
|
+
const h = y1 - y0;
|
|
24469
|
+
const shownWidth = src ? Math.min(w, Math.ceil(src.x + src.w) - x0) : w;
|
|
24470
|
+
const shownHeight = src ? Math.min(h, Math.ceil(src.y + src.h) - y0) : h;
|
|
24471
|
+
const result = (out2) => {
|
|
24472
|
+
if (shownWidth === w && shownHeight === h) {
|
|
24473
|
+
return { rgba: out2, x: x0, y: y0, width: w, height: h };
|
|
24474
|
+
}
|
|
24475
|
+
const shown = new Uint8ClampedArray(shownWidth * shownHeight * 4);
|
|
24476
|
+
copyBlock(out2, w, 0, 0, shownWidth, shownHeight, shown, shownWidth, 0, 0);
|
|
24477
|
+
return { rgba: shown, x: x0, y: y0, width: shownWidth, height: shownHeight };
|
|
24478
|
+
};
|
|
24479
|
+
if (src && effect.kind === "blur" && effect.expandEdge) {
|
|
24480
|
+
return result(expandedBlur(rgba, width, height, effect.radius, x0, y0, x1, y1));
|
|
24481
|
+
}
|
|
24361
24482
|
let region = rgba;
|
|
24362
|
-
if (w !== width || h !== height
|
|
24483
|
+
if (w !== width || h !== height) {
|
|
24363
24484
|
region = new Uint8ClampedArray(w * h * 4);
|
|
24364
|
-
|
|
24365
|
-
const from = ((y0 + y) * width + x0) * 4;
|
|
24366
|
-
region.set(rgba.subarray(from, from + cw * 4), ((pad + y) * w + pad) * 4);
|
|
24367
|
-
}
|
|
24485
|
+
copyBlock(rgba, width, x0, y0, w, h, region, w, 0, 0);
|
|
24368
24486
|
}
|
|
24369
|
-
const ox = x0 - pad;
|
|
24370
|
-
const oy = y0 - pad;
|
|
24371
24487
|
const local = effect.kind === "redEyeCorrection" ? {
|
|
24372
24488
|
kind: effect.kind,
|
|
24373
24489
|
areas: effect.areas.map((a) => ({
|
|
24374
|
-
left: a.left -
|
|
24375
|
-
top: a.top -
|
|
24376
|
-
right: a.right -
|
|
24377
|
-
bottom: a.bottom -
|
|
24490
|
+
left: a.left - x0,
|
|
24491
|
+
top: a.top - y0,
|
|
24492
|
+
right: a.right - x0,
|
|
24493
|
+
bottom: a.bottom - y0
|
|
24378
24494
|
}))
|
|
24379
24495
|
} : effect;
|
|
24380
24496
|
const out = applyImageEffect(region, w, h, local);
|
|
24381
|
-
return out ?
|
|
24497
|
+
return out ? result(out) : null;
|
|
24382
24498
|
}
|
|
24383
24499
|
|
|
24384
24500
|
// src/emf-plus-draw-image.ts
|
|
@@ -24542,11 +24658,10 @@ function effectedDraw(img, bitmap, effect, draw, source) {
|
|
|
24542
24658
|
if (!rect) {
|
|
24543
24659
|
return { bitmap: out, draw };
|
|
24544
24660
|
}
|
|
24545
|
-
const
|
|
24546
|
-
const
|
|
24547
|
-
const
|
|
24548
|
-
const
|
|
24549
|
-
const srcH = rect.srcH + 2 * grow;
|
|
24661
|
+
const srcX = rect.srcX - region.x;
|
|
24662
|
+
const srcY = rect.srcY - region.y;
|
|
24663
|
+
const srcW = rect.srcW;
|
|
24664
|
+
const srcH = rect.srcH;
|
|
24550
24665
|
const shift = [1, 0, 0, 1, region.x, region.y];
|
|
24551
24666
|
const toWorld = mulMatrix(rect.toWorld(rect.srcX, rect.srcY, rect.srcW, rect.srcH), shift);
|
|
24552
24667
|
return {
|