emf-converter 4.3.2 → 4.3.3
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 +2 -2
- package/dist/index.js +278 -74
- package/dist/index.mjs +278 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,8 +214,8 @@ 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.
|
|
216
216
|
|
|
217
|
-
- **Colour adjustment and image effects**: The `HALFTONE` stretch mode
|
|
218
|
-
- **Pen transforms**: nonuniform and skewed
|
|
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 and lookup table effects follow GDI+'s definitions; MS-EMFPLUS does not specify the algorithms of the others (blur, sharpen, brightness/contrast, levels, colour balance, colour curves, hue/saturation/lightness, tint, red-eye), so those use documented approximations and have not been checked against GDI+ output. An effect is applied to the draw's source rectangle only, and a blur with `expandEdge` grows it by the blur radius, as GDI+ does. When the source rectangle is not in pixels, the effect is applied to the whole image without that growth. 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
|
+
- **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.
|
|
221
221
|
- **GM_COMPATIBLE recordings**: EMF files do not record the graphics mode, and Windows plays RoundRect, Arc, Chord, Pie and null-pen Ellipse records back differently from how a GM_COMPATIBLE application drew them on screen; the converter follows Windows' playback.
|
package/dist/index.js
CHANGED
|
@@ -12702,7 +12702,10 @@ function dashFigure(pts, closed, pen) {
|
|
|
12702
12702
|
for (let i = 0; i + 1 < path.length; i++) {
|
|
12703
12703
|
const a = path[i];
|
|
12704
12704
|
const b = path[i + 1];
|
|
12705
|
-
|
|
12705
|
+
const m = pen.dashMetric;
|
|
12706
|
+
const vx = b.x - a.x;
|
|
12707
|
+
const vy = b.y - a.y;
|
|
12708
|
+
let segLen = m ? Math.hypot(m[0] * vx + m[2] * vy, m[1] * vx + m[3] * vy) : Math.hypot(vx, vy);
|
|
12706
12709
|
let t0 = 0;
|
|
12707
12710
|
while (segLen - t0 > remaining + 1e-9) {
|
|
12708
12711
|
t0 += remaining;
|
|
@@ -12730,6 +12733,26 @@ function dashFigure(pts, closed, pen) {
|
|
|
12730
12733
|
}
|
|
12731
12734
|
return out.filter((d) => d.pts.length > 1);
|
|
12732
12735
|
}
|
|
12736
|
+
var ARROW_ANCHOR = 20;
|
|
12737
|
+
function arrowAnchor(pp, atEnd, half) {
|
|
12738
|
+
const w = 2 * half;
|
|
12739
|
+
const tip = atEnd ? pp[pp.length - 1] : pp[0];
|
|
12740
|
+
const next = atEnd ? pp[pp.length - 2] : pp[1];
|
|
12741
|
+
const len = Math.hypot(next.x - tip.x, next.y - tip.y);
|
|
12742
|
+
const ux = (next.x - tip.x) / len;
|
|
12743
|
+
const uy = (next.y - tip.y) / len;
|
|
12744
|
+
const depth = 2 * w;
|
|
12745
|
+
const across = 2 * w / Math.sqrt(3);
|
|
12746
|
+
const base = { x: tip.x + ux * depth, y: tip.y + uy * depth };
|
|
12747
|
+
if (len > depth) {
|
|
12748
|
+
if (atEnd) {
|
|
12749
|
+
pp[pp.length - 1] = base;
|
|
12750
|
+
} else {
|
|
12751
|
+
pp[0] = base;
|
|
12752
|
+
}
|
|
12753
|
+
}
|
|
12754
|
+
return [tip, { x: base.x - uy * across, y: base.y + ux * across }, { x: base.x + uy * across, y: base.y - ux * across }];
|
|
12755
|
+
}
|
|
12733
12756
|
var MIN_DASH = 1 / 256;
|
|
12734
12757
|
function signedArea(pts) {
|
|
12735
12758
|
let a = 0;
|
|
@@ -12780,6 +12803,19 @@ function widenFigures(figures, pen) {
|
|
|
12780
12803
|
if (pp.length < 2) {
|
|
12781
12804
|
continue;
|
|
12782
12805
|
}
|
|
12806
|
+
const arrows = [];
|
|
12807
|
+
let startCapOverride = null;
|
|
12808
|
+
let endCapOverride = null;
|
|
12809
|
+
if (!pieceClosed && !(pen.dash && closed)) {
|
|
12810
|
+
if (piece.first && pen.startCap === ARROW_ANCHOR) {
|
|
12811
|
+
arrows.push(arrowAnchor(pp, false, h));
|
|
12812
|
+
startCapOverride = 0;
|
|
12813
|
+
}
|
|
12814
|
+
if (piece.last && pen.endCap === ARROW_ANCHOR) {
|
|
12815
|
+
arrows.push(arrowAnchor(pp, true, h));
|
|
12816
|
+
endCapOverride = 0;
|
|
12817
|
+
}
|
|
12818
|
+
}
|
|
12783
12819
|
for (const [o1, o2] of bands) {
|
|
12784
12820
|
if (pieceClosed) {
|
|
12785
12821
|
polys.push(sidePoints(pp, true, o2, pen));
|
|
@@ -12790,11 +12826,16 @@ function widenFigures(figures, pen) {
|
|
|
12790
12826
|
const right = sidePoints(pp, false, o1, pen).reverse();
|
|
12791
12827
|
const fEnd = frame(pp[pp.length - 2], pp[pp.length - 1]);
|
|
12792
12828
|
const f0 = frame(pp[1], pp[0]);
|
|
12793
|
-
const endCap = piece.last && !(pen.dash && closed) ? pen.endCap : pen.dashCap;
|
|
12794
|
-
const startCap = piece.first && !(pen.dash && closed) ? pen.startCap : pen.dashCap;
|
|
12829
|
+
const endCap = endCapOverride ?? (piece.last && !(pen.dash && closed) ? pen.endCap : pen.dashCap);
|
|
12830
|
+
const startCap = startCapOverride ?? (piece.first && !(pen.dash && closed) ? pen.startCap : pen.dashCap);
|
|
12795
12831
|
const capEnd = capPoints(pp[pp.length - 1], fEnd, o1, o2, endCap, h);
|
|
12796
12832
|
const capStart = capPoints(pp[0], f0, -o2, -o1, startCap, h);
|
|
12797
|
-
|
|
12833
|
+
const poly = [...left.slice(0, -1), ...capEnd, ...right.slice(1, -1), ...capStart];
|
|
12834
|
+
polys.push(poly);
|
|
12835
|
+
const sign = Math.sign(signedArea(poly));
|
|
12836
|
+
for (const arrow of arrows) {
|
|
12837
|
+
polys.push(Math.sign(signedArea(arrow)) === sign ? arrow : arrow.slice().reverse());
|
|
12838
|
+
}
|
|
12798
12839
|
}
|
|
12799
12840
|
}
|
|
12800
12841
|
}
|
|
@@ -20085,50 +20126,111 @@ function readColorAdjustment(view, off) {
|
|
|
20085
20126
|
function isIdentityColorAdjustment(ca) {
|
|
20086
20127
|
return !ca || ca.flags === 0 && ca.redGamma === 1e4 && ca.greenGamma === 1e4 && ca.blueGamma === 1e4 && ca.referenceBlack === 0 && ca.referenceWhite === 1e4 && ca.contrast === 0 && ca.brightness === 0 && ca.colorfulness === 0 && ca.redGreenTint === 0;
|
|
20087
20128
|
}
|
|
20088
|
-
|
|
20089
|
-
|
|
20129
|
+
var CONTRAST_GAIN = 1.88;
|
|
20130
|
+
var BRIGHTNESS_GAIN = 78.5;
|
|
20131
|
+
var COLORFULNESS_GAIN = 1.335;
|
|
20132
|
+
var TINT_RADIANS = -1.135;
|
|
20133
|
+
var RGB_TO_XYZ = [
|
|
20134
|
+
[0.4124, 0.3576, 0.1805],
|
|
20135
|
+
[0.2126, 0.7152, 0.0722],
|
|
20136
|
+
[0.0193, 0.1192, 0.9505]
|
|
20137
|
+
];
|
|
20138
|
+
var XYZ_TO_RGB = invert3(RGB_TO_XYZ);
|
|
20139
|
+
var WHITE = RGB_TO_XYZ.map((row) => row[0] + row[1] + row[2]);
|
|
20140
|
+
var WHITE_D = WHITE[0] + 15 * WHITE[1] + 3 * WHITE[2];
|
|
20141
|
+
var WHITE_U = 4 * WHITE[0] / WHITE_D;
|
|
20142
|
+
var WHITE_V = 9 * WHITE[1] / WHITE_D;
|
|
20143
|
+
function invert3(m) {
|
|
20144
|
+
const [[a, b, c], [d, e, f], [g, h, i]] = m;
|
|
20145
|
+
const A = e * i - f * h;
|
|
20146
|
+
const B = f * g - d * i;
|
|
20147
|
+
const C = d * h - e * g;
|
|
20148
|
+
const det = a * A + b * B + c * C;
|
|
20149
|
+
return [
|
|
20150
|
+
[A / det, (c * h - b * i) / det, (b * f - c * e) / det],
|
|
20151
|
+
[B / det, (a * i - c * g) / det, (c * d - a * f) / det],
|
|
20152
|
+
[C / det, (b * g - a * h) / det, (a * e - b * d) / det]
|
|
20153
|
+
];
|
|
20154
|
+
}
|
|
20155
|
+
function lightness(t) {
|
|
20156
|
+
return t > 8856e-6 ? 116 * Math.cbrt(t) - 16 : 903.3 * t;
|
|
20157
|
+
}
|
|
20158
|
+
function fromLightness(l) {
|
|
20159
|
+
return l > 8 ? ((l + 16) / 116) ** 3 : l / 903.3;
|
|
20090
20160
|
}
|
|
20091
20161
|
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
20092
20162
|
function colorAdjustmentMapper(ca) {
|
|
20093
20163
|
const black = ca.referenceBlack / 1e4;
|
|
20094
20164
|
const span = Math.max(1e-6, ca.referenceWhite / 1e4 - black);
|
|
20095
20165
|
const gammas = [ca.redGamma / 1e4, ca.greenGamma / 1e4, ca.blueGamma / 1e4];
|
|
20096
|
-
const
|
|
20166
|
+
const negative = (ca.flags & CA_NEGATIVE) !== 0;
|
|
20167
|
+
const linear = gammas.map((gamma) => {
|
|
20097
20168
|
const table = new Float64Array(256);
|
|
20098
20169
|
for (let i = 0; i < 256; i++) {
|
|
20099
|
-
|
|
20100
|
-
|
|
20101
|
-
table[i] = ca.flags & CA_NEGATIVE ? 1 - v : v;
|
|
20170
|
+
const v = negative ? 1 - i / 255 : i / 255;
|
|
20171
|
+
table[i] = clamp012((v - black) / span) ** gamma;
|
|
20102
20172
|
}
|
|
20103
20173
|
return table;
|
|
20104
20174
|
});
|
|
20105
|
-
const chroma =
|
|
20106
|
-
const
|
|
20107
|
-
const
|
|
20108
|
-
const
|
|
20175
|
+
const chroma = ca.colorfulness < 0 ? 1 + ca.colorfulness / 100 : 1 + COLORFULNESS_GAIN * ca.colorfulness / 100;
|
|
20176
|
+
const angle = TINT_RADIANS * ca.redGreenTint / 100;
|
|
20177
|
+
const cos = Math.cos(angle) * chroma;
|
|
20178
|
+
const sin = Math.sin(angle) * chroma;
|
|
20179
|
+
const slope = Math.exp(CONTRAST_GAIN * ca.contrast / 100);
|
|
20180
|
+
const offset = 50 - 50 * slope + BRIGHTNESS_GAIN * ca.brightness / 100;
|
|
20109
20181
|
const log = (ca.flags & CA_LOG_FILTER) !== 0;
|
|
20110
|
-
const
|
|
20111
|
-
|
|
20182
|
+
const colour = ca.colorfulness !== 0 || ca.redGreenTint !== 0;
|
|
20183
|
+
const out = (v, c) => {
|
|
20184
|
+
v = clamp012(fromLightness(slope * lightness(clamp012(v)) + offset)) ** (1 / gammas[c]);
|
|
20112
20185
|
if (log) {
|
|
20113
20186
|
v = Math.log10(1 + 9 * v);
|
|
20114
20187
|
}
|
|
20115
20188
|
return Math.round(v * 255);
|
|
20116
20189
|
};
|
|
20117
20190
|
return (rgb2) => {
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20191
|
+
let r = linear[0][rgb2 >> 16 & 255];
|
|
20192
|
+
let g = linear[1][rgb2 >> 8 & 255];
|
|
20193
|
+
let b = linear[2][rgb2 & 255];
|
|
20194
|
+
if (colour) {
|
|
20195
|
+
const x = RGB_TO_XYZ[0][0] * r + RGB_TO_XYZ[0][1] * g + RGB_TO_XYZ[0][2] * b;
|
|
20196
|
+
const y = RGB_TO_XYZ[1][0] * r + RGB_TO_XYZ[1][1] * g + RGB_TO_XYZ[1][2] * b;
|
|
20197
|
+
const z = RGB_TO_XYZ[2][0] * r + RGB_TO_XYZ[2][1] * g + RGB_TO_XYZ[2][2] * b;
|
|
20198
|
+
const d = x + 15 * y + 3 * z;
|
|
20199
|
+
if (d > 1e-12 && y > 1e-12) {
|
|
20200
|
+
const du = 4 * x / d - WHITE_U;
|
|
20201
|
+
const dv = 9 * y / d - WHITE_V;
|
|
20202
|
+
const u = WHITE_U + du * cos - dv * sin;
|
|
20203
|
+
const v = Math.max(1e-6, WHITE_V + du * sin + dv * cos);
|
|
20204
|
+
const x2 = y * 9 * u / (4 * v);
|
|
20205
|
+
const z2 = y * (12 - 3 * u - 20 * v) / (4 * v);
|
|
20206
|
+
r = XYZ_TO_RGB[0][0] * x2 + XYZ_TO_RGB[0][1] * y + XYZ_TO_RGB[0][2] * z2;
|
|
20207
|
+
g = XYZ_TO_RGB[1][0] * x2 + XYZ_TO_RGB[1][1] * y + XYZ_TO_RGB[1][2] * z2;
|
|
20208
|
+
b = XYZ_TO_RGB[2][0] * x2 + XYZ_TO_RGB[2][1] * y + XYZ_TO_RGB[2][2] * z2;
|
|
20209
|
+
}
|
|
20210
|
+
}
|
|
20211
|
+
return out(r, 0) << 16 | out(g, 1) << 8 | out(b, 2);
|
|
20130
20212
|
};
|
|
20131
20213
|
}
|
|
20214
|
+
function colorAdjustRgb(rgbs, ca) {
|
|
20215
|
+
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20216
|
+
return;
|
|
20217
|
+
}
|
|
20218
|
+
const map = colorAdjustmentMapper(ca);
|
|
20219
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20220
|
+
for (let i = 0; i + 2 < rgbs.length; i += 3) {
|
|
20221
|
+
const rgb2 = rgbs[i] << 16 | rgbs[i + 1] << 8 | rgbs[i + 2];
|
|
20222
|
+
let v = cache.get(rgb2);
|
|
20223
|
+
if (v === void 0) {
|
|
20224
|
+
v = map(rgb2);
|
|
20225
|
+
if (cache.size < 65536) {
|
|
20226
|
+
cache.set(rgb2, v);
|
|
20227
|
+
}
|
|
20228
|
+
}
|
|
20229
|
+
rgbs[i] = v >> 16 & 255;
|
|
20230
|
+
rgbs[i + 1] = v >> 8 & 255;
|
|
20231
|
+
rgbs[i + 2] = v & 255;
|
|
20232
|
+
}
|
|
20233
|
+
}
|
|
20132
20234
|
function applyColorAdjustment(pixels, ca) {
|
|
20133
20235
|
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20134
20236
|
return;
|
|
@@ -20223,64 +20325,160 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
|
|
|
20223
20325
|
}
|
|
20224
20326
|
return { width: W, height: H, data };
|
|
20225
20327
|
}
|
|
20226
|
-
|
|
20227
|
-
|
|
20328
|
+
var FIX = 65536;
|
|
20329
|
+
function halftoneNearest(i, srcLen, dstLen) {
|
|
20330
|
+
const num = (2 * i + 1) * srcLen;
|
|
20331
|
+
const den = 2 * dstLen;
|
|
20332
|
+
let k = Math.floor(num / den);
|
|
20333
|
+
if (k * den === num) {
|
|
20334
|
+
k--;
|
|
20335
|
+
}
|
|
20336
|
+
return Math.max(0, Math.min(srcLen - 1, k));
|
|
20337
|
+
}
|
|
20338
|
+
function halftoneAxis(start, srcLen, dstLen, reverse) {
|
|
20339
|
+
const taps = [];
|
|
20228
20340
|
if (dstLen <= 0 || srcLen <= 0) {
|
|
20229
|
-
return
|
|
20341
|
+
return taps;
|
|
20230
20342
|
}
|
|
20231
|
-
const at = (k) => reverse ?
|
|
20343
|
+
const at = (k) => reverse ? start + srcLen - 1 - k : start + k;
|
|
20344
|
+
if (dstLen >= srcLen) {
|
|
20345
|
+
for (let i = 0; i < dstLen; i++) {
|
|
20346
|
+
taps.push([[at(halftoneNearest(i, srcLen, dstLen)), 1]]);
|
|
20347
|
+
}
|
|
20348
|
+
return taps;
|
|
20349
|
+
}
|
|
20350
|
+
const step = Math.ceil(srcLen * FIX / dstLen);
|
|
20232
20351
|
for (let i = 0; i < dstLen; i++) {
|
|
20233
|
-
const a = i *
|
|
20234
|
-
const b = (i + 1) *
|
|
20235
|
-
const
|
|
20236
|
-
for (let k = Math.floor(a /
|
|
20237
|
-
const overlap = Math.min(b, (k + 1) *
|
|
20352
|
+
const a = i * step;
|
|
20353
|
+
const b = (i + 1) * step;
|
|
20354
|
+
const run = [];
|
|
20355
|
+
for (let k = Math.floor(a / FIX); k * FIX < b && k < srcLen; k++) {
|
|
20356
|
+
const overlap = Math.min(b, (k + 1) * FIX) - Math.max(a, k * FIX);
|
|
20238
20357
|
if (overlap > 0) {
|
|
20239
|
-
|
|
20358
|
+
run.push([at(k), overlap]);
|
|
20359
|
+
}
|
|
20360
|
+
}
|
|
20361
|
+
taps.push(run);
|
|
20362
|
+
}
|
|
20363
|
+
return taps;
|
|
20364
|
+
}
|
|
20365
|
+
var SPECKLE_OVERSHOOT = 4;
|
|
20366
|
+
function halftoneDespeckle(px, w, h) {
|
|
20367
|
+
const src = px.slice();
|
|
20368
|
+
const at = (x, y, c) => src[(y * w + x) * 3 + c];
|
|
20369
|
+
const over = (v) => v < -SPECKLE_OVERSHOOT * 4 || v > (255 + SPECKLE_OVERSHOOT) * 4;
|
|
20370
|
+
for (let y = 0; y < h; y++) {
|
|
20371
|
+
const up = y > 0 ? y - 1 : Math.min(h - 1, y + 1);
|
|
20372
|
+
const down = y < h - 1 ? y + 1 : Math.max(0, y - 1);
|
|
20373
|
+
for (let x = 0; x < w; x++) {
|
|
20374
|
+
const left = x > 0 ? x - 1 : Math.min(w - 1, x + 1);
|
|
20375
|
+
const right = x < w - 1 ? x + 1 : Math.max(0, x - 1);
|
|
20376
|
+
let hClip = 0;
|
|
20377
|
+
let vClip = 0;
|
|
20378
|
+
for (let c = 0; c < 3; c++) {
|
|
20379
|
+
const v = at(x, y, c);
|
|
20380
|
+
if (over(6 * v - at(left, y, c) - at(right, y, c))) {
|
|
20381
|
+
hClip++;
|
|
20382
|
+
}
|
|
20383
|
+
if (over(6 * v - at(x, up, c) - at(x, down, c))) {
|
|
20384
|
+
vClip++;
|
|
20385
|
+
}
|
|
20386
|
+
}
|
|
20387
|
+
if (hClip < 2 || vClip < 2) {
|
|
20388
|
+
continue;
|
|
20389
|
+
}
|
|
20390
|
+
for (let c = 0; c < 3; c++) {
|
|
20391
|
+
px[(y * w + x) * 3 + c] = at(left, y, c) + 2 * at(x, y, c) + at(right, y, c) + 2 >> 2;
|
|
20392
|
+
}
|
|
20393
|
+
}
|
|
20394
|
+
}
|
|
20395
|
+
}
|
|
20396
|
+
function halftoneSharpen(px, w, h) {
|
|
20397
|
+
const src = px.slice();
|
|
20398
|
+
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];
|
|
20399
|
+
for (let y = 0; y < h; y++) {
|
|
20400
|
+
for (let x = 0; x < w; x++) {
|
|
20401
|
+
for (let c = 0; c < 3; c++) {
|
|
20402
|
+
const v = at(x, y, c);
|
|
20403
|
+
const sum = at(x - 1, y, c) + at(x + 1, y, c) + at(x, y - 1, c) + at(x, y + 1, c);
|
|
20404
|
+
const out = v + Math.floor((4 * v - sum) / 8);
|
|
20405
|
+
px[(y * w + x) * 3 + c] = out < 0 ? 0 : out > 255 ? 255 : out;
|
|
20240
20406
|
}
|
|
20241
20407
|
}
|
|
20242
|
-
out.push(taps);
|
|
20243
20408
|
}
|
|
20244
|
-
return out;
|
|
20245
20409
|
}
|
|
20246
|
-
function stretchHalftone(src, sx, sy, sw, sh, dw, dh) {
|
|
20410
|
+
function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust) {
|
|
20247
20411
|
const W = Math.max(0, Math.round(Math.abs(dw)));
|
|
20248
20412
|
const H = Math.max(0, Math.round(Math.abs(dh)));
|
|
20413
|
+
const SW = Math.round(Math.abs(sw));
|
|
20414
|
+
const SH = Math.round(Math.abs(sh));
|
|
20415
|
+
const x0 = Math.round(Math.min(sx, sx + sw));
|
|
20416
|
+
const y0 = Math.round(Math.min(sy, sy + sh));
|
|
20249
20417
|
const flipX = dw < 0 !== sw < 0;
|
|
20250
20418
|
const flipY = dh < 0 !== sh < 0;
|
|
20251
|
-
const cols = axisBoxWeights(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX);
|
|
20252
|
-
const rows = axisBoxWeights(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY);
|
|
20253
20419
|
const data = new Uint8ClampedArray(W * H * 4);
|
|
20254
|
-
|
|
20420
|
+
if (W === 0 || H === 0 || SW === 0 || SH === 0) {
|
|
20421
|
+
return { width: W, height: H, data };
|
|
20422
|
+
}
|
|
20423
|
+
const rect = new Int32Array(SW * SH * 3);
|
|
20424
|
+
for (let y = 0; y < SH; y++) {
|
|
20425
|
+
const yy = y0 + y;
|
|
20426
|
+
for (let x = 0; x < SW; x++) {
|
|
20427
|
+
const xx = x0 + x;
|
|
20428
|
+
if (yy < 0 || yy >= src.height || xx < 0 || xx >= src.width) {
|
|
20429
|
+
continue;
|
|
20430
|
+
}
|
|
20431
|
+
const i = (yy * src.width + xx) * 4;
|
|
20432
|
+
const o = (y * SW + x) * 3;
|
|
20433
|
+
rect[o] = src.data[i];
|
|
20434
|
+
rect[o + 1] = src.data[i + 1];
|
|
20435
|
+
rect[o + 2] = src.data[i + 2];
|
|
20436
|
+
}
|
|
20437
|
+
}
|
|
20438
|
+
const enlarging = W >= SW && H >= SH && (W > SW || H > SH);
|
|
20439
|
+
const reducing = W < SW && H < SH;
|
|
20440
|
+
if (enlarging) {
|
|
20441
|
+
halftoneDespeckle(rect, SW, SH);
|
|
20442
|
+
}
|
|
20443
|
+
if (adjust) {
|
|
20444
|
+
adjust(rect);
|
|
20445
|
+
}
|
|
20446
|
+
const cols = halftoneAxis(0, SW, W, flipX);
|
|
20447
|
+
const rows = halftoneAxis(0, SH, H, flipY);
|
|
20448
|
+
const out = new Int32Array(W * H * 3);
|
|
20255
20449
|
for (let y = 0; y < H; y++) {
|
|
20256
|
-
const ys = rows[y]
|
|
20450
|
+
const ys = rows[y];
|
|
20257
20451
|
for (let x = 0; x < W; x++) {
|
|
20258
|
-
const xs = cols[x]
|
|
20452
|
+
const xs = cols[x];
|
|
20259
20453
|
let r = 0;
|
|
20260
20454
|
let g = 0;
|
|
20261
20455
|
let b = 0;
|
|
20456
|
+
let total = 0;
|
|
20262
20457
|
for (const [yy, wy] of ys) {
|
|
20263
|
-
if (yy < 0 || yy >= src.height) {
|
|
20264
|
-
continue;
|
|
20265
|
-
}
|
|
20266
20458
|
for (const [xx, wx] of xs) {
|
|
20267
|
-
if (xx < 0 || xx >= src.width) {
|
|
20268
|
-
continue;
|
|
20269
|
-
}
|
|
20270
20459
|
const w = wx * wy;
|
|
20271
|
-
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20460
|
+
total += w;
|
|
20461
|
+
const i = (yy * SW + xx) * 3;
|
|
20462
|
+
r += rect[i] * w;
|
|
20463
|
+
g += rect[i + 1] * w;
|
|
20464
|
+
b += rect[i + 2] * w;
|
|
20275
20465
|
}
|
|
20276
20466
|
}
|
|
20277
|
-
const o = (y * W + x) *
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
data[o + 3] = 255;
|
|
20467
|
+
const o = (y * W + x) * 3;
|
|
20468
|
+
out[o] = Math.floor((2 * r + total) / (2 * total));
|
|
20469
|
+
out[o + 1] = Math.floor((2 * g + total) / (2 * total));
|
|
20470
|
+
out[o + 2] = Math.floor((2 * b + total) / (2 * total));
|
|
20282
20471
|
}
|
|
20283
20472
|
}
|
|
20473
|
+
if (reducing) {
|
|
20474
|
+
halftoneSharpen(out, W, H);
|
|
20475
|
+
}
|
|
20476
|
+
for (let i = 0, o = 0; i < W * H; i++, o += 3) {
|
|
20477
|
+
data[i * 4] = out[o];
|
|
20478
|
+
data[i * 4 + 1] = out[o + 1];
|
|
20479
|
+
data[i * 4 + 2] = out[o + 2];
|
|
20480
|
+
data[i * 4 + 3] = 255;
|
|
20481
|
+
}
|
|
20284
20482
|
return { width: W, height: H, data };
|
|
20285
20483
|
}
|
|
20286
20484
|
|
|
@@ -20306,7 +20504,7 @@ function patternOperand(rCtx, brush) {
|
|
|
20306
20504
|
function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
|
|
20307
20505
|
const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
|
|
20308
20506
|
const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
|
|
20309
|
-
const px = mode === HALFTONE ? stretchHalftone(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh) : stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
|
|
20507
|
+
const px = mode === HALFTONE ? stretchHalftone(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, decoded.adjust) : stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
|
|
20310
20508
|
if (px.width === 0 || px.height === 0) {
|
|
20311
20509
|
return;
|
|
20312
20510
|
}
|
|
@@ -20334,15 +20532,14 @@ function decodeSource(rCtx, req) {
|
|
|
20334
20532
|
if (!image) {
|
|
20335
20533
|
return null;
|
|
20336
20534
|
}
|
|
20337
|
-
|
|
20338
|
-
|
|
20339
|
-
}
|
|
20535
|
+
const ca = rCtx.state.colorAdjustment;
|
|
20536
|
+
const adjust = req.stretch && rCtx.state.stretchBltMode === HALFTONE && ca ? (rgb2) => colorAdjustRgb(rgb2, ca) : void 0;
|
|
20340
20537
|
let { sy } = req;
|
|
20341
20538
|
if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
|
|
20342
20539
|
sy = image.height - sy - req.sh;
|
|
20343
20540
|
}
|
|
20344
20541
|
return {
|
|
20345
|
-
decoded: { pixels: image },
|
|
20542
|
+
decoded: { pixels: image, adjust },
|
|
20346
20543
|
req: { ...req, sy }
|
|
20347
20544
|
};
|
|
20348
20545
|
}
|
|
@@ -20498,6 +20695,9 @@ function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, s
|
|
|
20498
20695
|
return;
|
|
20499
20696
|
}
|
|
20500
20697
|
src = decoded.decoded.pixels;
|
|
20698
|
+
if (decoded.decoded.adjust) {
|
|
20699
|
+
applyColorAdjustment(src, rCtx.state.colorAdjustment);
|
|
20700
|
+
}
|
|
20501
20701
|
srcX = decoded.req.sx;
|
|
20502
20702
|
srcY = decoded.req.sy;
|
|
20503
20703
|
}
|
|
@@ -23965,12 +24165,12 @@ function hslToRgb(h, s, l) {
|
|
|
23965
24165
|
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];
|
|
23966
24166
|
return [r + m, g + m, b + m];
|
|
23967
24167
|
}
|
|
23968
|
-
function applyHueSaturationLightness(src, hue, saturation,
|
|
24168
|
+
function applyHueSaturationLightness(src, hue, saturation, lightness2) {
|
|
23969
24169
|
const out = new Uint8ClampedArray(src);
|
|
23970
24170
|
const push = (v, t) => t >= 0 ? v + (1 - v) * (t / 100) : v * (1 + t / 100);
|
|
23971
24171
|
for (let i = 0; i < src.length; i += 4) {
|
|
23972
24172
|
const [h, s, l] = rgbToHsl(src[i] / 255, src[i + 1] / 255, src[i + 2] / 255);
|
|
23973
|
-
const [r, g, b] = hslToRgb(h + hue, push(s, saturation), push(l,
|
|
24173
|
+
const [r, g, b] = hslToRgb(h + hue, push(s, saturation), push(l, lightness2));
|
|
23974
24174
|
out[i] = Math.round(r * 255);
|
|
23975
24175
|
out[i + 1] = Math.round(g * 255);
|
|
23976
24176
|
out[i + 2] = Math.round(b * 255);
|
|
@@ -24661,6 +24861,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24661
24861
|
}
|
|
24662
24862
|
};
|
|
24663
24863
|
if (pen) {
|
|
24864
|
+
const nibDashed = !!nib && penDashArray(pen).length > 0;
|
|
24664
24865
|
applyPlusPenStyle(ctx, pen, widthScale);
|
|
24665
24866
|
const mode = plusRasterMode(rCtx);
|
|
24666
24867
|
let sampler = pen.brush && !isSvgContext(ctx) ? brushSampler(rCtx, pen.brush) : null;
|
|
@@ -24675,7 +24876,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24675
24876
|
if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure)) {
|
|
24676
24877
|
return;
|
|
24677
24878
|
}
|
|
24678
|
-
const outline = hasCustomCap(pen) ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24879
|
+
const outline = hasCustomCap(pen) || nibDashed ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24679
24880
|
let box = null;
|
|
24680
24881
|
if (size && outline) {
|
|
24681
24882
|
box = deviceBounds(outline.flat(), device, size, 2);
|
|
@@ -24705,7 +24906,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24705
24906
|
}
|
|
24706
24907
|
}
|
|
24707
24908
|
const paint = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
|
|
24708
|
-
if ((hasCustomCap(pen) || nib && typeof paint !== "string") && fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure)) {
|
|
24909
|
+
if ((hasCustomCap(pen) || nibDashed || nib && typeof paint !== "string") && fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure)) {
|
|
24709
24910
|
return;
|
|
24710
24911
|
}
|
|
24711
24912
|
ctx.strokeStyle = paint;
|
|
@@ -24727,7 +24928,9 @@ function resolvePenTransform(pen) {
|
|
|
24727
24928
|
return { pen: { ...plain, width: pen.width === 0 ? 1 : pen.width }, nib: null };
|
|
24728
24929
|
}
|
|
24729
24930
|
if (Math.abs(sx - sy) <= 1e-6 * sx && Math.abs(dot) <= 1e-6 * sx * sy) {
|
|
24730
|
-
|
|
24931
|
+
const pattern = pen.dashStyle === 5 ? pen.dashPattern : DASH_PATTERNS[pen.dashStyle];
|
|
24932
|
+
const dashed = pattern && pattern.length > 0 ? { dashStyle: 5, dashPattern: pattern.map((v) => v / sx), dashOffset: (pen.dashOffset ?? 0) / sx } : {};
|
|
24933
|
+
return { pen: { ...plain, ...dashed, width: pen.width * sx }, nib: null };
|
|
24731
24934
|
}
|
|
24732
24935
|
return { pen: plain, nib: [a, b, c, d, 0, 0] };
|
|
24733
24936
|
}
|
|
@@ -24752,7 +24955,7 @@ function penFrame(device, nib) {
|
|
|
24752
24955
|
toDevice: mulMatrix(full, [1 / unit, 0, 0, 1 / unit, 0, 0])
|
|
24753
24956
|
};
|
|
24754
24957
|
}
|
|
24755
|
-
function framePen(pen, unit) {
|
|
24958
|
+
function framePen(pen, unit, nib = null) {
|
|
24756
24959
|
const dash = penDashArray(pen).map((v) => v * unit);
|
|
24757
24960
|
return {
|
|
24758
24961
|
half: (pen.width || 1) * unit / 2,
|
|
@@ -24763,6 +24966,7 @@ function framePen(pen, unit) {
|
|
|
24763
24966
|
dashCap: pen.dashCap ?? 0,
|
|
24764
24967
|
dash: dash.length > 0 ? dash : null,
|
|
24765
24968
|
dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * unit,
|
|
24969
|
+
dashMetric: nib ? [nib[0], nib[1], nib[2], nib[3]] : void 0,
|
|
24766
24970
|
compound: pen.compound ?? null,
|
|
24767
24971
|
inset: pen.alignment === 1
|
|
24768
24972
|
};
|
|
@@ -24790,7 +24994,7 @@ function strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, c
|
|
|
24790
24994
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
24791
24995
|
}
|
|
24792
24996
|
const width = (pen.width || 1) * frame2.unit;
|
|
24793
|
-
const devicePen = framePen(pen, frame2.unit);
|
|
24997
|
+
const devicePen = framePen(pen, frame2.unit, nib);
|
|
24794
24998
|
const antialias = mode === "gdiplus-aa";
|
|
24795
24999
|
const toFix = (poly) => {
|
|
24796
25000
|
const m = frame2.toDevice;
|
|
@@ -24882,7 +25086,7 @@ function penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) {
|
|
|
24882
25086
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
24883
25087
|
}
|
|
24884
25088
|
const width = (pen.width || 1) * frame2.unit;
|
|
24885
|
-
const devicePen = framePen(pen, frame2.unit);
|
|
25089
|
+
const devicePen = framePen(pen, frame2.unit, nib);
|
|
24886
25090
|
const outline = hasCustomCap(pen) ? customCapOutline(figures, width, devicePen, pen) : widenFigures(figures, devicePen);
|
|
24887
25091
|
const m = mulMatrix(deviceInv, frame2.toDevice);
|
|
24888
25092
|
return outline.map((poly) => poly.map((p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] })));
|
package/dist/index.mjs
CHANGED
|
@@ -12700,7 +12700,10 @@ function dashFigure(pts, closed, pen) {
|
|
|
12700
12700
|
for (let i = 0; i + 1 < path.length; i++) {
|
|
12701
12701
|
const a = path[i];
|
|
12702
12702
|
const b = path[i + 1];
|
|
12703
|
-
|
|
12703
|
+
const m = pen.dashMetric;
|
|
12704
|
+
const vx = b.x - a.x;
|
|
12705
|
+
const vy = b.y - a.y;
|
|
12706
|
+
let segLen = m ? Math.hypot(m[0] * vx + m[2] * vy, m[1] * vx + m[3] * vy) : Math.hypot(vx, vy);
|
|
12704
12707
|
let t0 = 0;
|
|
12705
12708
|
while (segLen - t0 > remaining + 1e-9) {
|
|
12706
12709
|
t0 += remaining;
|
|
@@ -12728,6 +12731,26 @@ function dashFigure(pts, closed, pen) {
|
|
|
12728
12731
|
}
|
|
12729
12732
|
return out.filter((d) => d.pts.length > 1);
|
|
12730
12733
|
}
|
|
12734
|
+
var ARROW_ANCHOR = 20;
|
|
12735
|
+
function arrowAnchor(pp, atEnd, half) {
|
|
12736
|
+
const w = 2 * half;
|
|
12737
|
+
const tip = atEnd ? pp[pp.length - 1] : pp[0];
|
|
12738
|
+
const next = atEnd ? pp[pp.length - 2] : pp[1];
|
|
12739
|
+
const len = Math.hypot(next.x - tip.x, next.y - tip.y);
|
|
12740
|
+
const ux = (next.x - tip.x) / len;
|
|
12741
|
+
const uy = (next.y - tip.y) / len;
|
|
12742
|
+
const depth = 2 * w;
|
|
12743
|
+
const across = 2 * w / Math.sqrt(3);
|
|
12744
|
+
const base = { x: tip.x + ux * depth, y: tip.y + uy * depth };
|
|
12745
|
+
if (len > depth) {
|
|
12746
|
+
if (atEnd) {
|
|
12747
|
+
pp[pp.length - 1] = base;
|
|
12748
|
+
} else {
|
|
12749
|
+
pp[0] = base;
|
|
12750
|
+
}
|
|
12751
|
+
}
|
|
12752
|
+
return [tip, { x: base.x - uy * across, y: base.y + ux * across }, { x: base.x + uy * across, y: base.y - ux * across }];
|
|
12753
|
+
}
|
|
12731
12754
|
var MIN_DASH = 1 / 256;
|
|
12732
12755
|
function signedArea(pts) {
|
|
12733
12756
|
let a = 0;
|
|
@@ -12778,6 +12801,19 @@ function widenFigures(figures, pen) {
|
|
|
12778
12801
|
if (pp.length < 2) {
|
|
12779
12802
|
continue;
|
|
12780
12803
|
}
|
|
12804
|
+
const arrows = [];
|
|
12805
|
+
let startCapOverride = null;
|
|
12806
|
+
let endCapOverride = null;
|
|
12807
|
+
if (!pieceClosed && !(pen.dash && closed)) {
|
|
12808
|
+
if (piece.first && pen.startCap === ARROW_ANCHOR) {
|
|
12809
|
+
arrows.push(arrowAnchor(pp, false, h));
|
|
12810
|
+
startCapOverride = 0;
|
|
12811
|
+
}
|
|
12812
|
+
if (piece.last && pen.endCap === ARROW_ANCHOR) {
|
|
12813
|
+
arrows.push(arrowAnchor(pp, true, h));
|
|
12814
|
+
endCapOverride = 0;
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12781
12817
|
for (const [o1, o2] of bands) {
|
|
12782
12818
|
if (pieceClosed) {
|
|
12783
12819
|
polys.push(sidePoints(pp, true, o2, pen));
|
|
@@ -12788,11 +12824,16 @@ function widenFigures(figures, pen) {
|
|
|
12788
12824
|
const right = sidePoints(pp, false, o1, pen).reverse();
|
|
12789
12825
|
const fEnd = frame(pp[pp.length - 2], pp[pp.length - 1]);
|
|
12790
12826
|
const f0 = frame(pp[1], pp[0]);
|
|
12791
|
-
const endCap = piece.last && !(pen.dash && closed) ? pen.endCap : pen.dashCap;
|
|
12792
|
-
const startCap = piece.first && !(pen.dash && closed) ? pen.startCap : pen.dashCap;
|
|
12827
|
+
const endCap = endCapOverride ?? (piece.last && !(pen.dash && closed) ? pen.endCap : pen.dashCap);
|
|
12828
|
+
const startCap = startCapOverride ?? (piece.first && !(pen.dash && closed) ? pen.startCap : pen.dashCap);
|
|
12793
12829
|
const capEnd = capPoints(pp[pp.length - 1], fEnd, o1, o2, endCap, h);
|
|
12794
12830
|
const capStart = capPoints(pp[0], f0, -o2, -o1, startCap, h);
|
|
12795
|
-
|
|
12831
|
+
const poly = [...left.slice(0, -1), ...capEnd, ...right.slice(1, -1), ...capStart];
|
|
12832
|
+
polys.push(poly);
|
|
12833
|
+
const sign = Math.sign(signedArea(poly));
|
|
12834
|
+
for (const arrow of arrows) {
|
|
12835
|
+
polys.push(Math.sign(signedArea(arrow)) === sign ? arrow : arrow.slice().reverse());
|
|
12836
|
+
}
|
|
12796
12837
|
}
|
|
12797
12838
|
}
|
|
12798
12839
|
}
|
|
@@ -20083,50 +20124,111 @@ function readColorAdjustment(view, off) {
|
|
|
20083
20124
|
function isIdentityColorAdjustment(ca) {
|
|
20084
20125
|
return !ca || ca.flags === 0 && ca.redGamma === 1e4 && ca.greenGamma === 1e4 && ca.blueGamma === 1e4 && ca.referenceBlack === 0 && ca.referenceWhite === 1e4 && ca.contrast === 0 && ca.brightness === 0 && ca.colorfulness === 0 && ca.redGreenTint === 0;
|
|
20085
20126
|
}
|
|
20086
|
-
|
|
20087
|
-
|
|
20127
|
+
var CONTRAST_GAIN = 1.88;
|
|
20128
|
+
var BRIGHTNESS_GAIN = 78.5;
|
|
20129
|
+
var COLORFULNESS_GAIN = 1.335;
|
|
20130
|
+
var TINT_RADIANS = -1.135;
|
|
20131
|
+
var RGB_TO_XYZ = [
|
|
20132
|
+
[0.4124, 0.3576, 0.1805],
|
|
20133
|
+
[0.2126, 0.7152, 0.0722],
|
|
20134
|
+
[0.0193, 0.1192, 0.9505]
|
|
20135
|
+
];
|
|
20136
|
+
var XYZ_TO_RGB = invert3(RGB_TO_XYZ);
|
|
20137
|
+
var WHITE = RGB_TO_XYZ.map((row) => row[0] + row[1] + row[2]);
|
|
20138
|
+
var WHITE_D = WHITE[0] + 15 * WHITE[1] + 3 * WHITE[2];
|
|
20139
|
+
var WHITE_U = 4 * WHITE[0] / WHITE_D;
|
|
20140
|
+
var WHITE_V = 9 * WHITE[1] / WHITE_D;
|
|
20141
|
+
function invert3(m) {
|
|
20142
|
+
const [[a, b, c], [d, e, f], [g, h, i]] = m;
|
|
20143
|
+
const A = e * i - f * h;
|
|
20144
|
+
const B = f * g - d * i;
|
|
20145
|
+
const C = d * h - e * g;
|
|
20146
|
+
const det = a * A + b * B + c * C;
|
|
20147
|
+
return [
|
|
20148
|
+
[A / det, (c * h - b * i) / det, (b * f - c * e) / det],
|
|
20149
|
+
[B / det, (a * i - c * g) / det, (c * d - a * f) / det],
|
|
20150
|
+
[C / det, (b * g - a * h) / det, (a * e - b * d) / det]
|
|
20151
|
+
];
|
|
20152
|
+
}
|
|
20153
|
+
function lightness(t) {
|
|
20154
|
+
return t > 8856e-6 ? 116 * Math.cbrt(t) - 16 : 903.3 * t;
|
|
20155
|
+
}
|
|
20156
|
+
function fromLightness(l) {
|
|
20157
|
+
return l > 8 ? ((l + 16) / 116) ** 3 : l / 903.3;
|
|
20088
20158
|
}
|
|
20089
20159
|
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
20090
20160
|
function colorAdjustmentMapper(ca) {
|
|
20091
20161
|
const black = ca.referenceBlack / 1e4;
|
|
20092
20162
|
const span = Math.max(1e-6, ca.referenceWhite / 1e4 - black);
|
|
20093
20163
|
const gammas = [ca.redGamma / 1e4, ca.greenGamma / 1e4, ca.blueGamma / 1e4];
|
|
20094
|
-
const
|
|
20164
|
+
const negative = (ca.flags & CA_NEGATIVE) !== 0;
|
|
20165
|
+
const linear = gammas.map((gamma) => {
|
|
20095
20166
|
const table = new Float64Array(256);
|
|
20096
20167
|
for (let i = 0; i < 256; i++) {
|
|
20097
|
-
|
|
20098
|
-
|
|
20099
|
-
table[i] = ca.flags & CA_NEGATIVE ? 1 - v : v;
|
|
20168
|
+
const v = negative ? 1 - i / 255 : i / 255;
|
|
20169
|
+
table[i] = clamp012((v - black) / span) ** gamma;
|
|
20100
20170
|
}
|
|
20101
20171
|
return table;
|
|
20102
20172
|
});
|
|
20103
|
-
const chroma =
|
|
20104
|
-
const
|
|
20105
|
-
const
|
|
20106
|
-
const
|
|
20173
|
+
const chroma = ca.colorfulness < 0 ? 1 + ca.colorfulness / 100 : 1 + COLORFULNESS_GAIN * ca.colorfulness / 100;
|
|
20174
|
+
const angle = TINT_RADIANS * ca.redGreenTint / 100;
|
|
20175
|
+
const cos = Math.cos(angle) * chroma;
|
|
20176
|
+
const sin = Math.sin(angle) * chroma;
|
|
20177
|
+
const slope = Math.exp(CONTRAST_GAIN * ca.contrast / 100);
|
|
20178
|
+
const offset = 50 - 50 * slope + BRIGHTNESS_GAIN * ca.brightness / 100;
|
|
20107
20179
|
const log = (ca.flags & CA_LOG_FILTER) !== 0;
|
|
20108
|
-
const
|
|
20109
|
-
|
|
20180
|
+
const colour = ca.colorfulness !== 0 || ca.redGreenTint !== 0;
|
|
20181
|
+
const out = (v, c) => {
|
|
20182
|
+
v = clamp012(fromLightness(slope * lightness(clamp012(v)) + offset)) ** (1 / gammas[c]);
|
|
20110
20183
|
if (log) {
|
|
20111
20184
|
v = Math.log10(1 + 9 * v);
|
|
20112
20185
|
}
|
|
20113
20186
|
return Math.round(v * 255);
|
|
20114
20187
|
};
|
|
20115
20188
|
return (rgb2) => {
|
|
20116
|
-
|
|
20117
|
-
|
|
20118
|
-
|
|
20119
|
-
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20189
|
+
let r = linear[0][rgb2 >> 16 & 255];
|
|
20190
|
+
let g = linear[1][rgb2 >> 8 & 255];
|
|
20191
|
+
let b = linear[2][rgb2 & 255];
|
|
20192
|
+
if (colour) {
|
|
20193
|
+
const x = RGB_TO_XYZ[0][0] * r + RGB_TO_XYZ[0][1] * g + RGB_TO_XYZ[0][2] * b;
|
|
20194
|
+
const y = RGB_TO_XYZ[1][0] * r + RGB_TO_XYZ[1][1] * g + RGB_TO_XYZ[1][2] * b;
|
|
20195
|
+
const z = RGB_TO_XYZ[2][0] * r + RGB_TO_XYZ[2][1] * g + RGB_TO_XYZ[2][2] * b;
|
|
20196
|
+
const d = x + 15 * y + 3 * z;
|
|
20197
|
+
if (d > 1e-12 && y > 1e-12) {
|
|
20198
|
+
const du = 4 * x / d - WHITE_U;
|
|
20199
|
+
const dv = 9 * y / d - WHITE_V;
|
|
20200
|
+
const u = WHITE_U + du * cos - dv * sin;
|
|
20201
|
+
const v = Math.max(1e-6, WHITE_V + du * sin + dv * cos);
|
|
20202
|
+
const x2 = y * 9 * u / (4 * v);
|
|
20203
|
+
const z2 = y * (12 - 3 * u - 20 * v) / (4 * v);
|
|
20204
|
+
r = XYZ_TO_RGB[0][0] * x2 + XYZ_TO_RGB[0][1] * y + XYZ_TO_RGB[0][2] * z2;
|
|
20205
|
+
g = XYZ_TO_RGB[1][0] * x2 + XYZ_TO_RGB[1][1] * y + XYZ_TO_RGB[1][2] * z2;
|
|
20206
|
+
b = XYZ_TO_RGB[2][0] * x2 + XYZ_TO_RGB[2][1] * y + XYZ_TO_RGB[2][2] * z2;
|
|
20207
|
+
}
|
|
20208
|
+
}
|
|
20209
|
+
return out(r, 0) << 16 | out(g, 1) << 8 | out(b, 2);
|
|
20128
20210
|
};
|
|
20129
20211
|
}
|
|
20212
|
+
function colorAdjustRgb(rgbs, ca) {
|
|
20213
|
+
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20214
|
+
return;
|
|
20215
|
+
}
|
|
20216
|
+
const map = colorAdjustmentMapper(ca);
|
|
20217
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20218
|
+
for (let i = 0; i + 2 < rgbs.length; i += 3) {
|
|
20219
|
+
const rgb2 = rgbs[i] << 16 | rgbs[i + 1] << 8 | rgbs[i + 2];
|
|
20220
|
+
let v = cache.get(rgb2);
|
|
20221
|
+
if (v === void 0) {
|
|
20222
|
+
v = map(rgb2);
|
|
20223
|
+
if (cache.size < 65536) {
|
|
20224
|
+
cache.set(rgb2, v);
|
|
20225
|
+
}
|
|
20226
|
+
}
|
|
20227
|
+
rgbs[i] = v >> 16 & 255;
|
|
20228
|
+
rgbs[i + 1] = v >> 8 & 255;
|
|
20229
|
+
rgbs[i + 2] = v & 255;
|
|
20230
|
+
}
|
|
20231
|
+
}
|
|
20130
20232
|
function applyColorAdjustment(pixels, ca) {
|
|
20131
20233
|
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20132
20234
|
return;
|
|
@@ -20221,64 +20323,160 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
|
|
|
20221
20323
|
}
|
|
20222
20324
|
return { width: W, height: H, data };
|
|
20223
20325
|
}
|
|
20224
|
-
|
|
20225
|
-
|
|
20326
|
+
var FIX = 65536;
|
|
20327
|
+
function halftoneNearest(i, srcLen, dstLen) {
|
|
20328
|
+
const num = (2 * i + 1) * srcLen;
|
|
20329
|
+
const den = 2 * dstLen;
|
|
20330
|
+
let k = Math.floor(num / den);
|
|
20331
|
+
if (k * den === num) {
|
|
20332
|
+
k--;
|
|
20333
|
+
}
|
|
20334
|
+
return Math.max(0, Math.min(srcLen - 1, k));
|
|
20335
|
+
}
|
|
20336
|
+
function halftoneAxis(start, srcLen, dstLen, reverse) {
|
|
20337
|
+
const taps = [];
|
|
20226
20338
|
if (dstLen <= 0 || srcLen <= 0) {
|
|
20227
|
-
return
|
|
20339
|
+
return taps;
|
|
20228
20340
|
}
|
|
20229
|
-
const at = (k) => reverse ?
|
|
20341
|
+
const at = (k) => reverse ? start + srcLen - 1 - k : start + k;
|
|
20342
|
+
if (dstLen >= srcLen) {
|
|
20343
|
+
for (let i = 0; i < dstLen; i++) {
|
|
20344
|
+
taps.push([[at(halftoneNearest(i, srcLen, dstLen)), 1]]);
|
|
20345
|
+
}
|
|
20346
|
+
return taps;
|
|
20347
|
+
}
|
|
20348
|
+
const step = Math.ceil(srcLen * FIX / dstLen);
|
|
20230
20349
|
for (let i = 0; i < dstLen; i++) {
|
|
20231
|
-
const a = i *
|
|
20232
|
-
const b = (i + 1) *
|
|
20233
|
-
const
|
|
20234
|
-
for (let k = Math.floor(a /
|
|
20235
|
-
const overlap = Math.min(b, (k + 1) *
|
|
20350
|
+
const a = i * step;
|
|
20351
|
+
const b = (i + 1) * step;
|
|
20352
|
+
const run = [];
|
|
20353
|
+
for (let k = Math.floor(a / FIX); k * FIX < b && k < srcLen; k++) {
|
|
20354
|
+
const overlap = Math.min(b, (k + 1) * FIX) - Math.max(a, k * FIX);
|
|
20236
20355
|
if (overlap > 0) {
|
|
20237
|
-
|
|
20356
|
+
run.push([at(k), overlap]);
|
|
20357
|
+
}
|
|
20358
|
+
}
|
|
20359
|
+
taps.push(run);
|
|
20360
|
+
}
|
|
20361
|
+
return taps;
|
|
20362
|
+
}
|
|
20363
|
+
var SPECKLE_OVERSHOOT = 4;
|
|
20364
|
+
function halftoneDespeckle(px, w, h) {
|
|
20365
|
+
const src = px.slice();
|
|
20366
|
+
const at = (x, y, c) => src[(y * w + x) * 3 + c];
|
|
20367
|
+
const over = (v) => v < -SPECKLE_OVERSHOOT * 4 || v > (255 + SPECKLE_OVERSHOOT) * 4;
|
|
20368
|
+
for (let y = 0; y < h; y++) {
|
|
20369
|
+
const up = y > 0 ? y - 1 : Math.min(h - 1, y + 1);
|
|
20370
|
+
const down = y < h - 1 ? y + 1 : Math.max(0, y - 1);
|
|
20371
|
+
for (let x = 0; x < w; x++) {
|
|
20372
|
+
const left = x > 0 ? x - 1 : Math.min(w - 1, x + 1);
|
|
20373
|
+
const right = x < w - 1 ? x + 1 : Math.max(0, x - 1);
|
|
20374
|
+
let hClip = 0;
|
|
20375
|
+
let vClip = 0;
|
|
20376
|
+
for (let c = 0; c < 3; c++) {
|
|
20377
|
+
const v = at(x, y, c);
|
|
20378
|
+
if (over(6 * v - at(left, y, c) - at(right, y, c))) {
|
|
20379
|
+
hClip++;
|
|
20380
|
+
}
|
|
20381
|
+
if (over(6 * v - at(x, up, c) - at(x, down, c))) {
|
|
20382
|
+
vClip++;
|
|
20383
|
+
}
|
|
20384
|
+
}
|
|
20385
|
+
if (hClip < 2 || vClip < 2) {
|
|
20386
|
+
continue;
|
|
20387
|
+
}
|
|
20388
|
+
for (let c = 0; c < 3; c++) {
|
|
20389
|
+
px[(y * w + x) * 3 + c] = at(left, y, c) + 2 * at(x, y, c) + at(right, y, c) + 2 >> 2;
|
|
20390
|
+
}
|
|
20391
|
+
}
|
|
20392
|
+
}
|
|
20393
|
+
}
|
|
20394
|
+
function halftoneSharpen(px, w, h) {
|
|
20395
|
+
const src = px.slice();
|
|
20396
|
+
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];
|
|
20397
|
+
for (let y = 0; y < h; y++) {
|
|
20398
|
+
for (let x = 0; x < w; x++) {
|
|
20399
|
+
for (let c = 0; c < 3; c++) {
|
|
20400
|
+
const v = at(x, y, c);
|
|
20401
|
+
const sum = at(x - 1, y, c) + at(x + 1, y, c) + at(x, y - 1, c) + at(x, y + 1, c);
|
|
20402
|
+
const out = v + Math.floor((4 * v - sum) / 8);
|
|
20403
|
+
px[(y * w + x) * 3 + c] = out < 0 ? 0 : out > 255 ? 255 : out;
|
|
20238
20404
|
}
|
|
20239
20405
|
}
|
|
20240
|
-
out.push(taps);
|
|
20241
20406
|
}
|
|
20242
|
-
return out;
|
|
20243
20407
|
}
|
|
20244
|
-
function stretchHalftone(src, sx, sy, sw, sh, dw, dh) {
|
|
20408
|
+
function stretchHalftone(src, sx, sy, sw, sh, dw, dh, adjust) {
|
|
20245
20409
|
const W = Math.max(0, Math.round(Math.abs(dw)));
|
|
20246
20410
|
const H = Math.max(0, Math.round(Math.abs(dh)));
|
|
20411
|
+
const SW = Math.round(Math.abs(sw));
|
|
20412
|
+
const SH = Math.round(Math.abs(sh));
|
|
20413
|
+
const x0 = Math.round(Math.min(sx, sx + sw));
|
|
20414
|
+
const y0 = Math.round(Math.min(sy, sy + sh));
|
|
20247
20415
|
const flipX = dw < 0 !== sw < 0;
|
|
20248
20416
|
const flipY = dh < 0 !== sh < 0;
|
|
20249
|
-
const cols = axisBoxWeights(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX);
|
|
20250
|
-
const rows = axisBoxWeights(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY);
|
|
20251
20417
|
const data = new Uint8ClampedArray(W * H * 4);
|
|
20252
|
-
|
|
20418
|
+
if (W === 0 || H === 0 || SW === 0 || SH === 0) {
|
|
20419
|
+
return { width: W, height: H, data };
|
|
20420
|
+
}
|
|
20421
|
+
const rect = new Int32Array(SW * SH * 3);
|
|
20422
|
+
for (let y = 0; y < SH; y++) {
|
|
20423
|
+
const yy = y0 + y;
|
|
20424
|
+
for (let x = 0; x < SW; x++) {
|
|
20425
|
+
const xx = x0 + x;
|
|
20426
|
+
if (yy < 0 || yy >= src.height || xx < 0 || xx >= src.width) {
|
|
20427
|
+
continue;
|
|
20428
|
+
}
|
|
20429
|
+
const i = (yy * src.width + xx) * 4;
|
|
20430
|
+
const o = (y * SW + x) * 3;
|
|
20431
|
+
rect[o] = src.data[i];
|
|
20432
|
+
rect[o + 1] = src.data[i + 1];
|
|
20433
|
+
rect[o + 2] = src.data[i + 2];
|
|
20434
|
+
}
|
|
20435
|
+
}
|
|
20436
|
+
const enlarging = W >= SW && H >= SH && (W > SW || H > SH);
|
|
20437
|
+
const reducing = W < SW && H < SH;
|
|
20438
|
+
if (enlarging) {
|
|
20439
|
+
halftoneDespeckle(rect, SW, SH);
|
|
20440
|
+
}
|
|
20441
|
+
if (adjust) {
|
|
20442
|
+
adjust(rect);
|
|
20443
|
+
}
|
|
20444
|
+
const cols = halftoneAxis(0, SW, W, flipX);
|
|
20445
|
+
const rows = halftoneAxis(0, SH, H, flipY);
|
|
20446
|
+
const out = new Int32Array(W * H * 3);
|
|
20253
20447
|
for (let y = 0; y < H; y++) {
|
|
20254
|
-
const ys = rows[y]
|
|
20448
|
+
const ys = rows[y];
|
|
20255
20449
|
for (let x = 0; x < W; x++) {
|
|
20256
|
-
const xs = cols[x]
|
|
20450
|
+
const xs = cols[x];
|
|
20257
20451
|
let r = 0;
|
|
20258
20452
|
let g = 0;
|
|
20259
20453
|
let b = 0;
|
|
20454
|
+
let total = 0;
|
|
20260
20455
|
for (const [yy, wy] of ys) {
|
|
20261
|
-
if (yy < 0 || yy >= src.height) {
|
|
20262
|
-
continue;
|
|
20263
|
-
}
|
|
20264
20456
|
for (const [xx, wx] of xs) {
|
|
20265
|
-
if (xx < 0 || xx >= src.width) {
|
|
20266
|
-
continue;
|
|
20267
|
-
}
|
|
20268
20457
|
const w = wx * wy;
|
|
20269
|
-
|
|
20270
|
-
|
|
20271
|
-
|
|
20272
|
-
|
|
20458
|
+
total += w;
|
|
20459
|
+
const i = (yy * SW + xx) * 3;
|
|
20460
|
+
r += rect[i] * w;
|
|
20461
|
+
g += rect[i + 1] * w;
|
|
20462
|
+
b += rect[i + 2] * w;
|
|
20273
20463
|
}
|
|
20274
20464
|
}
|
|
20275
|
-
const o = (y * W + x) *
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
data[o + 3] = 255;
|
|
20465
|
+
const o = (y * W + x) * 3;
|
|
20466
|
+
out[o] = Math.floor((2 * r + total) / (2 * total));
|
|
20467
|
+
out[o + 1] = Math.floor((2 * g + total) / (2 * total));
|
|
20468
|
+
out[o + 2] = Math.floor((2 * b + total) / (2 * total));
|
|
20280
20469
|
}
|
|
20281
20470
|
}
|
|
20471
|
+
if (reducing) {
|
|
20472
|
+
halftoneSharpen(out, W, H);
|
|
20473
|
+
}
|
|
20474
|
+
for (let i = 0, o = 0; i < W * H; i++, o += 3) {
|
|
20475
|
+
data[i * 4] = out[o];
|
|
20476
|
+
data[i * 4 + 1] = out[o + 1];
|
|
20477
|
+
data[i * 4 + 2] = out[o + 2];
|
|
20478
|
+
data[i * 4 + 3] = 255;
|
|
20479
|
+
}
|
|
20282
20480
|
return { width: W, height: H, data };
|
|
20283
20481
|
}
|
|
20284
20482
|
|
|
@@ -20304,7 +20502,7 @@ function patternOperand(rCtx, brush) {
|
|
|
20304
20502
|
function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
|
|
20305
20503
|
const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
|
|
20306
20504
|
const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
|
|
20307
|
-
const px = mode === HALFTONE ? stretchHalftone(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh) : stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
|
|
20505
|
+
const px = mode === HALFTONE ? stretchHalftone(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, decoded.adjust) : stretchGdi(decoded.pixels, req.sx, req.sy, req.sw, req.sh, req.dw, req.dh, mode);
|
|
20308
20506
|
if (px.width === 0 || px.height === 0) {
|
|
20309
20507
|
return;
|
|
20310
20508
|
}
|
|
@@ -20332,15 +20530,14 @@ function decodeSource(rCtx, req) {
|
|
|
20332
20530
|
if (!image) {
|
|
20333
20531
|
return null;
|
|
20334
20532
|
}
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
}
|
|
20533
|
+
const ca = rCtx.state.colorAdjustment;
|
|
20534
|
+
const adjust = req.stretch && rCtx.state.stretchBltMode === HALFTONE && ca ? (rgb2) => colorAdjustRgb(rgb2, ca) : void 0;
|
|
20338
20535
|
let { sy } = req;
|
|
20339
20536
|
if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
|
|
20340
20537
|
sy = image.height - sy - req.sh;
|
|
20341
20538
|
}
|
|
20342
20539
|
return {
|
|
20343
|
-
decoded: { pixels: image },
|
|
20540
|
+
decoded: { pixels: image, adjust },
|
|
20344
20541
|
req: { ...req, sy }
|
|
20345
20542
|
};
|
|
20346
20543
|
}
|
|
@@ -20496,6 +20693,9 @@ function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, s
|
|
|
20496
20693
|
return;
|
|
20497
20694
|
}
|
|
20498
20695
|
src = decoded.decoded.pixels;
|
|
20696
|
+
if (decoded.decoded.adjust) {
|
|
20697
|
+
applyColorAdjustment(src, rCtx.state.colorAdjustment);
|
|
20698
|
+
}
|
|
20499
20699
|
srcX = decoded.req.sx;
|
|
20500
20700
|
srcY = decoded.req.sy;
|
|
20501
20701
|
}
|
|
@@ -23963,12 +24163,12 @@ function hslToRgb(h, s, l) {
|
|
|
23963
24163
|
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];
|
|
23964
24164
|
return [r + m, g + m, b + m];
|
|
23965
24165
|
}
|
|
23966
|
-
function applyHueSaturationLightness(src, hue, saturation,
|
|
24166
|
+
function applyHueSaturationLightness(src, hue, saturation, lightness2) {
|
|
23967
24167
|
const out = new Uint8ClampedArray(src);
|
|
23968
24168
|
const push = (v, t) => t >= 0 ? v + (1 - v) * (t / 100) : v * (1 + t / 100);
|
|
23969
24169
|
for (let i = 0; i < src.length; i += 4) {
|
|
23970
24170
|
const [h, s, l] = rgbToHsl(src[i] / 255, src[i + 1] / 255, src[i + 2] / 255);
|
|
23971
|
-
const [r, g, b] = hslToRgb(h + hue, push(s, saturation), push(l,
|
|
24171
|
+
const [r, g, b] = hslToRgb(h + hue, push(s, saturation), push(l, lightness2));
|
|
23972
24172
|
out[i] = Math.round(r * 255);
|
|
23973
24173
|
out[i + 1] = Math.round(g * 255);
|
|
23974
24174
|
out[i + 2] = Math.round(b * 255);
|
|
@@ -24659,6 +24859,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24659
24859
|
}
|
|
24660
24860
|
};
|
|
24661
24861
|
if (pen) {
|
|
24862
|
+
const nibDashed = !!nib && penDashArray(pen).length > 0;
|
|
24662
24863
|
applyPlusPenStyle(ctx, pen, widthScale);
|
|
24663
24864
|
const mode = plusRasterMode(rCtx);
|
|
24664
24865
|
let sampler = pen.brush && !isSvgContext(ctx) ? brushSampler(rCtx, pen.brush) : null;
|
|
@@ -24673,7 +24874,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24673
24874
|
if (mode !== "canvas" && size && strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, closedFigure)) {
|
|
24674
24875
|
return;
|
|
24675
24876
|
}
|
|
24676
|
-
const outline = hasCustomCap(pen) ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24877
|
+
const outline = hasCustomCap(pen) || nibDashed ? penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) : null;
|
|
24677
24878
|
let box = null;
|
|
24678
24879
|
if (size && outline) {
|
|
24679
24880
|
box = deviceBounds(outline.flat(), device, size, 2);
|
|
@@ -24703,7 +24904,7 @@ function strokePlusGeometry(rCtx, pen, buildPath, points, closedFigure = false)
|
|
|
24703
24904
|
}
|
|
24704
24905
|
}
|
|
24705
24906
|
const paint = pen.brush ? brushPaint2(rCtx, pen.brush) : pen.color;
|
|
24706
|
-
if ((hasCustomCap(pen) || nib && typeof paint !== "string") && fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure)) {
|
|
24907
|
+
if ((hasCustomCap(pen) || nibDashed || nib && typeof paint !== "string") && fillPenOutline(rCtx, pen, nib, paint, buildPath, closedFigure)) {
|
|
24707
24908
|
return;
|
|
24708
24909
|
}
|
|
24709
24910
|
ctx.strokeStyle = paint;
|
|
@@ -24725,7 +24926,9 @@ function resolvePenTransform(pen) {
|
|
|
24725
24926
|
return { pen: { ...plain, width: pen.width === 0 ? 1 : pen.width }, nib: null };
|
|
24726
24927
|
}
|
|
24727
24928
|
if (Math.abs(sx - sy) <= 1e-6 * sx && Math.abs(dot) <= 1e-6 * sx * sy) {
|
|
24728
|
-
|
|
24929
|
+
const pattern = pen.dashStyle === 5 ? pen.dashPattern : DASH_PATTERNS[pen.dashStyle];
|
|
24930
|
+
const dashed = pattern && pattern.length > 0 ? { dashStyle: 5, dashPattern: pattern.map((v) => v / sx), dashOffset: (pen.dashOffset ?? 0) / sx } : {};
|
|
24931
|
+
return { pen: { ...plain, ...dashed, width: pen.width * sx }, nib: null };
|
|
24729
24932
|
}
|
|
24730
24933
|
return { pen: plain, nib: [a, b, c, d, 0, 0] };
|
|
24731
24934
|
}
|
|
@@ -24750,7 +24953,7 @@ function penFrame(device, nib) {
|
|
|
24750
24953
|
toDevice: mulMatrix(full, [1 / unit, 0, 0, 1 / unit, 0, 0])
|
|
24751
24954
|
};
|
|
24752
24955
|
}
|
|
24753
|
-
function framePen(pen, unit) {
|
|
24956
|
+
function framePen(pen, unit, nib = null) {
|
|
24754
24957
|
const dash = penDashArray(pen).map((v) => v * unit);
|
|
24755
24958
|
return {
|
|
24756
24959
|
half: (pen.width || 1) * unit / 2,
|
|
@@ -24761,6 +24964,7 @@ function framePen(pen, unit) {
|
|
|
24761
24964
|
dashCap: pen.dashCap ?? 0,
|
|
24762
24965
|
dash: dash.length > 0 ? dash : null,
|
|
24763
24966
|
dashOffset: (pen.dashOffset ?? 0) * (pen.width || 1) * unit,
|
|
24967
|
+
dashMetric: nib ? [nib[0], nib[1], nib[2], nib[3]] : void 0,
|
|
24764
24968
|
compound: pen.compound ?? null,
|
|
24765
24969
|
inset: pen.alignment === 1
|
|
24766
24970
|
};
|
|
@@ -24788,7 +24992,7 @@ function strokeGdiplus(rCtx, pen, nib, sampler, buildPath, device, size, mode, c
|
|
|
24788
24992
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
24789
24993
|
}
|
|
24790
24994
|
const width = (pen.width || 1) * frame2.unit;
|
|
24791
|
-
const devicePen = framePen(pen, frame2.unit);
|
|
24995
|
+
const devicePen = framePen(pen, frame2.unit, nib);
|
|
24792
24996
|
const antialias = mode === "gdiplus-aa";
|
|
24793
24997
|
const toFix = (poly) => {
|
|
24794
24998
|
const m = frame2.toDevice;
|
|
@@ -24880,7 +25084,7 @@ function penOutlineWorld(rCtx, pen, nib, buildPath, closedFigure) {
|
|
|
24880
25084
|
figures = figures.map((f) => ({ ...f, closed: true }));
|
|
24881
25085
|
}
|
|
24882
25086
|
const width = (pen.width || 1) * frame2.unit;
|
|
24883
|
-
const devicePen = framePen(pen, frame2.unit);
|
|
25087
|
+
const devicePen = framePen(pen, frame2.unit, nib);
|
|
24884
25088
|
const outline = hasCustomCap(pen) ? customCapOutline(figures, width, devicePen, pen) : widenFigures(figures, devicePen);
|
|
24885
25089
|
const m = mulMatrix(deviceInv, frame2.toDevice);
|
|
24886
25090
|
return outline.map((poly) => poly.map((p) => ({ x: m[0] * p.x + m[2] * p.y + m[4], y: m[1] * p.x + m[3] * p.y + m[5] })));
|