emf-converter 4.2.1 → 4.3.1
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 +176 -62
- package/dist/index.mjs +176 -62
- 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.
|
|
216
216
|
|
|
217
|
-
- **Colour adjustment and image effects**: `
|
|
217
|
+
- **Colour adjustment and image effects**: The `HALFTONE` stretch mode resamples with an area-averaging box filter. Integer enlargements match Windows exactly; reductions do not, because Windows' halftone engine uses a narrower kernel and its own colour mapping (1.05% of pixels differ on the fixture). `EMR_SETCOLORADJUSTMENT` is applied to the source of `HALFTONE` `StretchBlt` and `StretchDIBits` calls, as in GDI. Windows does not publish its adjustment formulas, so gamma, reference black/white, contrast, brightness, colourfulness, red-green tint, negative and log filter use documented approximations, and Windows' dithering of the adjusted colours is not reproduced (6.6% of pixels differ by more than 24 on the fixture). 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
218
|
- **Pen transforms**: nonuniform and skewed EMF+ pen transforms are drawn with the transformed (elliptical or sheared) nib, but unlike uniform ones they are not yet verified against Windows output, and a singular pen transform is ignored.
|
|
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
|
@@ -19982,6 +19982,97 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
|
|
|
19982
19982
|
}
|
|
19983
19983
|
}
|
|
19984
19984
|
|
|
19985
|
+
// src/emf-gdi-color-adjust.ts
|
|
19986
|
+
var CA_NEGATIVE = 1;
|
|
19987
|
+
var CA_LOG_FILTER = 2;
|
|
19988
|
+
var COLOR_ADJUSTMENT_SIZE = 24;
|
|
19989
|
+
function readColorAdjustment(view, off) {
|
|
19990
|
+
const ca = {
|
|
19991
|
+
flags: view.getUint16(off + 2, true),
|
|
19992
|
+
illuminant: view.getUint16(off + 4, true),
|
|
19993
|
+
redGamma: view.getUint16(off + 6, true),
|
|
19994
|
+
greenGamma: view.getUint16(off + 8, true),
|
|
19995
|
+
blueGamma: view.getUint16(off + 10, true),
|
|
19996
|
+
referenceBlack: view.getUint16(off + 12, true),
|
|
19997
|
+
referenceWhite: view.getUint16(off + 14, true),
|
|
19998
|
+
contrast: view.getInt16(off + 16, true),
|
|
19999
|
+
brightness: view.getInt16(off + 18, true),
|
|
20000
|
+
colorfulness: view.getInt16(off + 20, true),
|
|
20001
|
+
redGreenTint: view.getInt16(off + 22, true)
|
|
20002
|
+
};
|
|
20003
|
+
const within = (v, lo, hi) => v >= lo && v <= hi;
|
|
20004
|
+
const valid = (ca.flags & -4) === 0 && within(ca.illuminant, 0, 8) && within(ca.redGamma, 2500, 65e3) && within(ca.greenGamma, 2500, 65e3) && within(ca.blueGamma, 2500, 65e3) && within(ca.referenceBlack, 0, 4e3) && within(ca.referenceWhite, 6e3, 1e4) && within(ca.contrast, -100, 100) && within(ca.brightness, -100, 100) && within(ca.colorfulness, -100, 100) && within(ca.redGreenTint, -100, 100);
|
|
20005
|
+
return valid ? ca : null;
|
|
20006
|
+
}
|
|
20007
|
+
function isIdentityColorAdjustment(ca) {
|
|
20008
|
+
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;
|
|
20009
|
+
}
|
|
20010
|
+
function colorfulnessScale(c) {
|
|
20011
|
+
return c > 0 ? 1 + 2 * c / 100 : 1 + c / 100;
|
|
20012
|
+
}
|
|
20013
|
+
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
20014
|
+
function colorAdjustmentMapper(ca) {
|
|
20015
|
+
const black = ca.referenceBlack / 1e4;
|
|
20016
|
+
const span = Math.max(1e-6, ca.referenceWhite / 1e4 - black);
|
|
20017
|
+
const gammas = [ca.redGamma / 1e4, ca.greenGamma / 1e4, ca.blueGamma / 1e4];
|
|
20018
|
+
const curves = gammas.map((gamma) => {
|
|
20019
|
+
const table = new Float64Array(256);
|
|
20020
|
+
for (let i = 0; i < 256; i++) {
|
|
20021
|
+
let v = clamp012((i / 255 - black) / span);
|
|
20022
|
+
v = Math.pow(v, gamma);
|
|
20023
|
+
table[i] = ca.flags & CA_NEGATIVE ? 1 - v : v;
|
|
20024
|
+
}
|
|
20025
|
+
return table;
|
|
20026
|
+
});
|
|
20027
|
+
const chroma = colorfulnessScale(ca.colorfulness);
|
|
20028
|
+
const contrast = 1 + ca.contrast / 100;
|
|
20029
|
+
const brightness = 0.8 * ca.brightness / 100;
|
|
20030
|
+
const tint = 0.3 * ca.redGreenTint / 100;
|
|
20031
|
+
const log = (ca.flags & CA_LOG_FILTER) !== 0;
|
|
20032
|
+
const out = (v) => {
|
|
20033
|
+
v = clamp012(v);
|
|
20034
|
+
if (log) {
|
|
20035
|
+
v = Math.log10(1 + 9 * v);
|
|
20036
|
+
}
|
|
20037
|
+
return Math.round(v * 255);
|
|
20038
|
+
};
|
|
20039
|
+
return (rgb2) => {
|
|
20040
|
+
const r = curves[0][rgb2 >> 16 & 255];
|
|
20041
|
+
const g = curves[1][rgb2 >> 8 & 255];
|
|
20042
|
+
const b = curves[2][rgb2 & 255];
|
|
20043
|
+
let y = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
20044
|
+
let cr = (r - y) * chroma;
|
|
20045
|
+
const cb = (b - y) * chroma;
|
|
20046
|
+
y = (y - 0.5) * contrast + 0.5 + brightness;
|
|
20047
|
+
cr += tint;
|
|
20048
|
+
const r2 = y + cr;
|
|
20049
|
+
const b2 = y + cb;
|
|
20050
|
+
const g2 = (y - 0.299 * r2 - 0.114 * b2) / 0.587;
|
|
20051
|
+
return out(r2) << 16 | out(g2) << 8 | out(b2);
|
|
20052
|
+
};
|
|
20053
|
+
}
|
|
20054
|
+
function applyColorAdjustment(pixels, ca) {
|
|
20055
|
+
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20056
|
+
return;
|
|
20057
|
+
}
|
|
20058
|
+
const map = colorAdjustmentMapper(ca);
|
|
20059
|
+
const d = pixels.data;
|
|
20060
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20061
|
+
for (let i = 0; i + 3 < d.length; i += 4) {
|
|
20062
|
+
const rgb2 = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
|
|
20063
|
+
let v = cache.get(rgb2);
|
|
20064
|
+
if (v === void 0) {
|
|
20065
|
+
v = map(rgb2);
|
|
20066
|
+
if (cache.size < 65536) {
|
|
20067
|
+
cache.set(rgb2, v);
|
|
20068
|
+
}
|
|
20069
|
+
}
|
|
20070
|
+
d[i] = v >> 16 & 255;
|
|
20071
|
+
d[i + 1] = v >> 8 & 255;
|
|
20072
|
+
d[i + 2] = v & 255;
|
|
20073
|
+
}
|
|
20074
|
+
}
|
|
20075
|
+
|
|
19985
20076
|
// src/emf-gdi-stretch.ts
|
|
19986
20077
|
var BLACKONWHITE = 1;
|
|
19987
20078
|
var WHITEONBLACK = 2;
|
|
@@ -20054,6 +20145,66 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
|
|
|
20054
20145
|
}
|
|
20055
20146
|
return { width: W, height: H, data };
|
|
20056
20147
|
}
|
|
20148
|
+
function axisBoxWeights(srcStart, srcLen, dstLen, reverse) {
|
|
20149
|
+
const out = [];
|
|
20150
|
+
if (dstLen <= 0 || srcLen <= 0) {
|
|
20151
|
+
return out;
|
|
20152
|
+
}
|
|
20153
|
+
const at = (k) => reverse ? srcStart + srcLen - 1 - k : srcStart + k;
|
|
20154
|
+
for (let i = 0; i < dstLen; i++) {
|
|
20155
|
+
const a = i * srcLen;
|
|
20156
|
+
const b = (i + 1) * srcLen;
|
|
20157
|
+
const taps = [];
|
|
20158
|
+
for (let k = Math.floor(a / dstLen); k * dstLen < b; k++) {
|
|
20159
|
+
const overlap = Math.min(b, (k + 1) * dstLen) - Math.max(a, k * dstLen);
|
|
20160
|
+
if (overlap > 0) {
|
|
20161
|
+
taps.push([at(k), overlap / srcLen]);
|
|
20162
|
+
}
|
|
20163
|
+
}
|
|
20164
|
+
out.push(taps);
|
|
20165
|
+
}
|
|
20166
|
+
return out;
|
|
20167
|
+
}
|
|
20168
|
+
function stretchHalftone(src, sx, sy, sw, sh, dw, dh) {
|
|
20169
|
+
const W = Math.max(0, Math.round(Math.abs(dw)));
|
|
20170
|
+
const H = Math.max(0, Math.round(Math.abs(dh)));
|
|
20171
|
+
const flipX = dw < 0 !== sw < 0;
|
|
20172
|
+
const flipY = dh < 0 !== sh < 0;
|
|
20173
|
+
const cols = axisBoxWeights(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX);
|
|
20174
|
+
const rows = axisBoxWeights(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY);
|
|
20175
|
+
const data = new Uint8ClampedArray(W * H * 4);
|
|
20176
|
+
const s = src.data;
|
|
20177
|
+
for (let y = 0; y < H; y++) {
|
|
20178
|
+
const ys = rows[y] ?? [];
|
|
20179
|
+
for (let x = 0; x < W; x++) {
|
|
20180
|
+
const xs = cols[x] ?? [];
|
|
20181
|
+
let r = 0;
|
|
20182
|
+
let g = 0;
|
|
20183
|
+
let b = 0;
|
|
20184
|
+
for (const [yy, wy] of ys) {
|
|
20185
|
+
if (yy < 0 || yy >= src.height) {
|
|
20186
|
+
continue;
|
|
20187
|
+
}
|
|
20188
|
+
for (const [xx, wx] of xs) {
|
|
20189
|
+
if (xx < 0 || xx >= src.width) {
|
|
20190
|
+
continue;
|
|
20191
|
+
}
|
|
20192
|
+
const w = wx * wy;
|
|
20193
|
+
const i = (yy * src.width + xx) * 4;
|
|
20194
|
+
r += s[i] * w;
|
|
20195
|
+
g += s[i + 1] * w;
|
|
20196
|
+
b += s[i + 2] * w;
|
|
20197
|
+
}
|
|
20198
|
+
}
|
|
20199
|
+
const o = (y * W + x) * 4;
|
|
20200
|
+
data[o] = Math.floor(r + 1e-6);
|
|
20201
|
+
data[o + 1] = Math.floor(g + 1e-6);
|
|
20202
|
+
data[o + 2] = Math.floor(b + 1e-6);
|
|
20203
|
+
data[o + 3] = 255;
|
|
20204
|
+
}
|
|
20205
|
+
}
|
|
20206
|
+
return { width: W, height: H, data };
|
|
20207
|
+
}
|
|
20057
20208
|
|
|
20058
20209
|
// src/emf-gdi-draw-bitmap.ts
|
|
20059
20210
|
function patternOperand(rCtx, brush) {
|
|
@@ -20077,46 +20228,18 @@ function patternOperand(rCtx, brush) {
|
|
|
20077
20228
|
function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
|
|
20078
20229
|
const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
|
|
20079
20230
|
const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
|
|
20080
|
-
|
|
20081
|
-
|
|
20082
|
-
const tile = createTempCanvas(px.width, px.height);
|
|
20083
|
-
if (!tile) {
|
|
20084
|
-
return;
|
|
20085
|
-
}
|
|
20086
|
-
canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
|
|
20087
|
-
target.save();
|
|
20088
|
-
target.globalCompositeOperation = "source-over";
|
|
20089
|
-
canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
|
|
20090
|
-
target.restore();
|
|
20231
|
+
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);
|
|
20232
|
+
if (px.width === 0 || px.height === 0) {
|
|
20091
20233
|
return;
|
|
20092
20234
|
}
|
|
20093
|
-
const
|
|
20094
|
-
|
|
20095
|
-
|
|
20096
|
-
|
|
20235
|
+
const tile = createTempCanvas(px.width, px.height);
|
|
20236
|
+
if (!tile) {
|
|
20237
|
+
return;
|
|
20238
|
+
}
|
|
20239
|
+
canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
|
|
20097
20240
|
target.save();
|
|
20098
|
-
target.
|
|
20099
|
-
target.
|
|
20100
|
-
flipX ? -1 : 1,
|
|
20101
|
-
0,
|
|
20102
|
-
0,
|
|
20103
|
-
flipY ? -1 : 1,
|
|
20104
|
-
flipX ? dLeft * 2 + adw : 0,
|
|
20105
|
-
flipY ? dTop * 2 + adh : 0
|
|
20106
|
-
);
|
|
20107
|
-
const draw = target.drawImage;
|
|
20108
|
-
draw.call(
|
|
20109
|
-
target,
|
|
20110
|
-
decoded.canvas,
|
|
20111
|
-
Math.min(req.sx, req.sx + req.sw),
|
|
20112
|
-
Math.min(req.sy, req.sy + req.sh),
|
|
20113
|
-
Math.abs(req.sw),
|
|
20114
|
-
Math.abs(req.sh),
|
|
20115
|
-
dLeft,
|
|
20116
|
-
dTop,
|
|
20117
|
-
adw,
|
|
20118
|
-
adh
|
|
20119
|
-
);
|
|
20241
|
+
target.globalCompositeOperation = "source-over";
|
|
20242
|
+
canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
|
|
20120
20243
|
target.restore();
|
|
20121
20244
|
}
|
|
20122
20245
|
function decodeSource(rCtx, req) {
|
|
@@ -20133,17 +20256,15 @@ function decodeSource(rCtx, req) {
|
|
|
20133
20256
|
if (!image) {
|
|
20134
20257
|
return null;
|
|
20135
20258
|
}
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
return null;
|
|
20259
|
+
if (req.stretch && rCtx.state.stretchBltMode === HALFTONE) {
|
|
20260
|
+
applyColorAdjustment(image, rCtx.state.colorAdjustment);
|
|
20139
20261
|
}
|
|
20140
|
-
canvasPutImageData(temp.ctx, image, 0, 0);
|
|
20141
20262
|
let { sy } = req;
|
|
20142
20263
|
if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
|
|
20143
20264
|
sy = image.height - sy - req.sh;
|
|
20144
20265
|
}
|
|
20145
20266
|
return {
|
|
20146
|
-
decoded: { pixels: image
|
|
20267
|
+
decoded: { pixels: image },
|
|
20147
20268
|
req: { ...req, sy }
|
|
20148
20269
|
};
|
|
20149
20270
|
}
|
|
@@ -20256,7 +20377,7 @@ function nudgedFloor(num, den, dx, dy) {
|
|
|
20256
20377
|
const q = Math.floor(num / den);
|
|
20257
20378
|
return num === q * den && nudgedSign(0, dx, dy) < 0 ? q - 1 : q;
|
|
20258
20379
|
}
|
|
20259
|
-
function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, sy, sw, sh, dibOrigin) {
|
|
20380
|
+
function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, sy, sw, sh, dibOrigin, stretch) {
|
|
20260
20381
|
const { ctx } = rCtx;
|
|
20261
20382
|
const index = rop3Index(rop);
|
|
20262
20383
|
if (index === 170) {
|
|
@@ -20292,7 +20413,8 @@ function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, s
|
|
|
20292
20413
|
sy,
|
|
20293
20414
|
sw,
|
|
20294
20415
|
sh,
|
|
20295
|
-
dibOrigin
|
|
20416
|
+
dibOrigin,
|
|
20417
|
+
stretch
|
|
20296
20418
|
});
|
|
20297
20419
|
if (!decoded) {
|
|
20298
20420
|
return;
|
|
@@ -20460,7 +20582,8 @@ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
|
|
|
20460
20582
|
srcRect.sy,
|
|
20461
20583
|
srcRect.sw,
|
|
20462
20584
|
srcRect.sh,
|
|
20463
|
-
"top-left"
|
|
20585
|
+
"top-left",
|
|
20586
|
+
stretch
|
|
20464
20587
|
);
|
|
20465
20588
|
return true;
|
|
20466
20589
|
}
|
|
@@ -20472,7 +20595,8 @@ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
|
|
|
20472
20595
|
dh: gmh(rCtx, cyDest),
|
|
20473
20596
|
source,
|
|
20474
20597
|
...srcRect,
|
|
20475
|
-
dibOrigin: "top-left"
|
|
20598
|
+
dibOrigin: "top-left",
|
|
20599
|
+
stretch
|
|
20476
20600
|
});
|
|
20477
20601
|
return true;
|
|
20478
20602
|
}
|
|
@@ -20499,7 +20623,7 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
|
|
|
20499
20623
|
const sw = view.getInt32(dataOff + 32, true);
|
|
20500
20624
|
const sh = view.getInt32(dataOff + 36, true);
|
|
20501
20625
|
if (hasWorldRotation(rCtx)) {
|
|
20502
|
-
executeRotatedBlit(rCtx, xDest, yDest, cxDest, cyDest, rop, source, sx, sy, sw, sh, "bottom-left");
|
|
20626
|
+
executeRotatedBlit(rCtx, xDest, yDest, cxDest, cyDest, rop, source, sx, sy, sw, sh, "bottom-left", true);
|
|
20503
20627
|
return true;
|
|
20504
20628
|
}
|
|
20505
20629
|
executeBlit(rCtx, {
|
|
@@ -20513,7 +20637,8 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
|
|
|
20513
20637
|
sy,
|
|
20514
20638
|
sw,
|
|
20515
20639
|
sh,
|
|
20516
|
-
dibOrigin: "bottom-left"
|
|
20640
|
+
dibOrigin: "bottom-left",
|
|
20641
|
+
stretch: true
|
|
20517
20642
|
});
|
|
20518
20643
|
return true;
|
|
20519
20644
|
}
|
|
@@ -23029,20 +23154,9 @@ function handleEmfGdiMiscRecord(rCtx, recType, dataOff, recSize) {
|
|
|
23029
23154
|
return true;
|
|
23030
23155
|
}
|
|
23031
23156
|
case EMR_SETCOLORADJUSTMENT: {
|
|
23032
|
-
|
|
23033
|
-
|
|
23034
|
-
|
|
23035
|
-
illuminant: view.getUint16(dataOff + 4, true),
|
|
23036
|
-
redGamma: view.getUint16(dataOff + 6, true),
|
|
23037
|
-
greenGamma: view.getUint16(dataOff + 8, true),
|
|
23038
|
-
blueGamma: view.getUint16(dataOff + 10, true),
|
|
23039
|
-
referenceBlack: view.getUint16(dataOff + 12, true),
|
|
23040
|
-
referenceWhite: view.getUint16(dataOff + 14, true),
|
|
23041
|
-
contrast: view.getInt16(dataOff + 16, true),
|
|
23042
|
-
brightness: view.getInt16(dataOff + 18, true),
|
|
23043
|
-
colorfulness: view.getInt16(dataOff + 20, true),
|
|
23044
|
-
redGreenTint: view.getInt16(dataOff + 22, true)
|
|
23045
|
-
};
|
|
23157
|
+
const ca = recSize >= 8 + COLOR_ADJUSTMENT_SIZE ? readColorAdjustment(view, dataOff) : null;
|
|
23158
|
+
if (ca) {
|
|
23159
|
+
state.colorAdjustment = ca;
|
|
23046
23160
|
}
|
|
23047
23161
|
return true;
|
|
23048
23162
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -19980,6 +19980,97 @@ function handleEmfGdiShapeRecord(rCtx, recType, dataOff, recSize) {
|
|
|
19980
19980
|
}
|
|
19981
19981
|
}
|
|
19982
19982
|
|
|
19983
|
+
// src/emf-gdi-color-adjust.ts
|
|
19984
|
+
var CA_NEGATIVE = 1;
|
|
19985
|
+
var CA_LOG_FILTER = 2;
|
|
19986
|
+
var COLOR_ADJUSTMENT_SIZE = 24;
|
|
19987
|
+
function readColorAdjustment(view, off) {
|
|
19988
|
+
const ca = {
|
|
19989
|
+
flags: view.getUint16(off + 2, true),
|
|
19990
|
+
illuminant: view.getUint16(off + 4, true),
|
|
19991
|
+
redGamma: view.getUint16(off + 6, true),
|
|
19992
|
+
greenGamma: view.getUint16(off + 8, true),
|
|
19993
|
+
blueGamma: view.getUint16(off + 10, true),
|
|
19994
|
+
referenceBlack: view.getUint16(off + 12, true),
|
|
19995
|
+
referenceWhite: view.getUint16(off + 14, true),
|
|
19996
|
+
contrast: view.getInt16(off + 16, true),
|
|
19997
|
+
brightness: view.getInt16(off + 18, true),
|
|
19998
|
+
colorfulness: view.getInt16(off + 20, true),
|
|
19999
|
+
redGreenTint: view.getInt16(off + 22, true)
|
|
20000
|
+
};
|
|
20001
|
+
const within = (v, lo, hi) => v >= lo && v <= hi;
|
|
20002
|
+
const valid = (ca.flags & -4) === 0 && within(ca.illuminant, 0, 8) && within(ca.redGamma, 2500, 65e3) && within(ca.greenGamma, 2500, 65e3) && within(ca.blueGamma, 2500, 65e3) && within(ca.referenceBlack, 0, 4e3) && within(ca.referenceWhite, 6e3, 1e4) && within(ca.contrast, -100, 100) && within(ca.brightness, -100, 100) && within(ca.colorfulness, -100, 100) && within(ca.redGreenTint, -100, 100);
|
|
20003
|
+
return valid ? ca : null;
|
|
20004
|
+
}
|
|
20005
|
+
function isIdentityColorAdjustment(ca) {
|
|
20006
|
+
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;
|
|
20007
|
+
}
|
|
20008
|
+
function colorfulnessScale(c) {
|
|
20009
|
+
return c > 0 ? 1 + 2 * c / 100 : 1 + c / 100;
|
|
20010
|
+
}
|
|
20011
|
+
var clamp012 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
20012
|
+
function colorAdjustmentMapper(ca) {
|
|
20013
|
+
const black = ca.referenceBlack / 1e4;
|
|
20014
|
+
const span = Math.max(1e-6, ca.referenceWhite / 1e4 - black);
|
|
20015
|
+
const gammas = [ca.redGamma / 1e4, ca.greenGamma / 1e4, ca.blueGamma / 1e4];
|
|
20016
|
+
const curves = gammas.map((gamma) => {
|
|
20017
|
+
const table = new Float64Array(256);
|
|
20018
|
+
for (let i = 0; i < 256; i++) {
|
|
20019
|
+
let v = clamp012((i / 255 - black) / span);
|
|
20020
|
+
v = Math.pow(v, gamma);
|
|
20021
|
+
table[i] = ca.flags & CA_NEGATIVE ? 1 - v : v;
|
|
20022
|
+
}
|
|
20023
|
+
return table;
|
|
20024
|
+
});
|
|
20025
|
+
const chroma = colorfulnessScale(ca.colorfulness);
|
|
20026
|
+
const contrast = 1 + ca.contrast / 100;
|
|
20027
|
+
const brightness = 0.8 * ca.brightness / 100;
|
|
20028
|
+
const tint = 0.3 * ca.redGreenTint / 100;
|
|
20029
|
+
const log = (ca.flags & CA_LOG_FILTER) !== 0;
|
|
20030
|
+
const out = (v) => {
|
|
20031
|
+
v = clamp012(v);
|
|
20032
|
+
if (log) {
|
|
20033
|
+
v = Math.log10(1 + 9 * v);
|
|
20034
|
+
}
|
|
20035
|
+
return Math.round(v * 255);
|
|
20036
|
+
};
|
|
20037
|
+
return (rgb2) => {
|
|
20038
|
+
const r = curves[0][rgb2 >> 16 & 255];
|
|
20039
|
+
const g = curves[1][rgb2 >> 8 & 255];
|
|
20040
|
+
const b = curves[2][rgb2 & 255];
|
|
20041
|
+
let y = 0.299 * r + 0.587 * g + 0.114 * b;
|
|
20042
|
+
let cr = (r - y) * chroma;
|
|
20043
|
+
const cb = (b - y) * chroma;
|
|
20044
|
+
y = (y - 0.5) * contrast + 0.5 + brightness;
|
|
20045
|
+
cr += tint;
|
|
20046
|
+
const r2 = y + cr;
|
|
20047
|
+
const b2 = y + cb;
|
|
20048
|
+
const g2 = (y - 0.299 * r2 - 0.114 * b2) / 0.587;
|
|
20049
|
+
return out(r2) << 16 | out(g2) << 8 | out(b2);
|
|
20050
|
+
};
|
|
20051
|
+
}
|
|
20052
|
+
function applyColorAdjustment(pixels, ca) {
|
|
20053
|
+
if (!ca || isIdentityColorAdjustment(ca)) {
|
|
20054
|
+
return;
|
|
20055
|
+
}
|
|
20056
|
+
const map = colorAdjustmentMapper(ca);
|
|
20057
|
+
const d = pixels.data;
|
|
20058
|
+
const cache = /* @__PURE__ */ new Map();
|
|
20059
|
+
for (let i = 0; i + 3 < d.length; i += 4) {
|
|
20060
|
+
const rgb2 = d[i] << 16 | d[i + 1] << 8 | d[i + 2];
|
|
20061
|
+
let v = cache.get(rgb2);
|
|
20062
|
+
if (v === void 0) {
|
|
20063
|
+
v = map(rgb2);
|
|
20064
|
+
if (cache.size < 65536) {
|
|
20065
|
+
cache.set(rgb2, v);
|
|
20066
|
+
}
|
|
20067
|
+
}
|
|
20068
|
+
d[i] = v >> 16 & 255;
|
|
20069
|
+
d[i + 1] = v >> 8 & 255;
|
|
20070
|
+
d[i + 2] = v & 255;
|
|
20071
|
+
}
|
|
20072
|
+
}
|
|
20073
|
+
|
|
19983
20074
|
// src/emf-gdi-stretch.ts
|
|
19984
20075
|
var BLACKONWHITE = 1;
|
|
19985
20076
|
var WHITEONBLACK = 2;
|
|
@@ -20052,6 +20143,66 @@ function stretchGdi(src, sx, sy, sw, sh, dw, dh, mode) {
|
|
|
20052
20143
|
}
|
|
20053
20144
|
return { width: W, height: H, data };
|
|
20054
20145
|
}
|
|
20146
|
+
function axisBoxWeights(srcStart, srcLen, dstLen, reverse) {
|
|
20147
|
+
const out = [];
|
|
20148
|
+
if (dstLen <= 0 || srcLen <= 0) {
|
|
20149
|
+
return out;
|
|
20150
|
+
}
|
|
20151
|
+
const at = (k) => reverse ? srcStart + srcLen - 1 - k : srcStart + k;
|
|
20152
|
+
for (let i = 0; i < dstLen; i++) {
|
|
20153
|
+
const a = i * srcLen;
|
|
20154
|
+
const b = (i + 1) * srcLen;
|
|
20155
|
+
const taps = [];
|
|
20156
|
+
for (let k = Math.floor(a / dstLen); k * dstLen < b; k++) {
|
|
20157
|
+
const overlap = Math.min(b, (k + 1) * dstLen) - Math.max(a, k * dstLen);
|
|
20158
|
+
if (overlap > 0) {
|
|
20159
|
+
taps.push([at(k), overlap / srcLen]);
|
|
20160
|
+
}
|
|
20161
|
+
}
|
|
20162
|
+
out.push(taps);
|
|
20163
|
+
}
|
|
20164
|
+
return out;
|
|
20165
|
+
}
|
|
20166
|
+
function stretchHalftone(src, sx, sy, sw, sh, dw, dh) {
|
|
20167
|
+
const W = Math.max(0, Math.round(Math.abs(dw)));
|
|
20168
|
+
const H = Math.max(0, Math.round(Math.abs(dh)));
|
|
20169
|
+
const flipX = dw < 0 !== sw < 0;
|
|
20170
|
+
const flipY = dh < 0 !== sh < 0;
|
|
20171
|
+
const cols = axisBoxWeights(Math.round(Math.min(sx, sx + sw)), Math.round(Math.abs(sw)), W, flipX);
|
|
20172
|
+
const rows = axisBoxWeights(Math.round(Math.min(sy, sy + sh)), Math.round(Math.abs(sh)), H, flipY);
|
|
20173
|
+
const data = new Uint8ClampedArray(W * H * 4);
|
|
20174
|
+
const s = src.data;
|
|
20175
|
+
for (let y = 0; y < H; y++) {
|
|
20176
|
+
const ys = rows[y] ?? [];
|
|
20177
|
+
for (let x = 0; x < W; x++) {
|
|
20178
|
+
const xs = cols[x] ?? [];
|
|
20179
|
+
let r = 0;
|
|
20180
|
+
let g = 0;
|
|
20181
|
+
let b = 0;
|
|
20182
|
+
for (const [yy, wy] of ys) {
|
|
20183
|
+
if (yy < 0 || yy >= src.height) {
|
|
20184
|
+
continue;
|
|
20185
|
+
}
|
|
20186
|
+
for (const [xx, wx] of xs) {
|
|
20187
|
+
if (xx < 0 || xx >= src.width) {
|
|
20188
|
+
continue;
|
|
20189
|
+
}
|
|
20190
|
+
const w = wx * wy;
|
|
20191
|
+
const i = (yy * src.width + xx) * 4;
|
|
20192
|
+
r += s[i] * w;
|
|
20193
|
+
g += s[i + 1] * w;
|
|
20194
|
+
b += s[i + 2] * w;
|
|
20195
|
+
}
|
|
20196
|
+
}
|
|
20197
|
+
const o = (y * W + x) * 4;
|
|
20198
|
+
data[o] = Math.floor(r + 1e-6);
|
|
20199
|
+
data[o + 1] = Math.floor(g + 1e-6);
|
|
20200
|
+
data[o + 2] = Math.floor(b + 1e-6);
|
|
20201
|
+
data[o + 3] = 255;
|
|
20202
|
+
}
|
|
20203
|
+
}
|
|
20204
|
+
return { width: W, height: H, data };
|
|
20205
|
+
}
|
|
20055
20206
|
|
|
20056
20207
|
// src/emf-gdi-draw-bitmap.ts
|
|
20057
20208
|
function patternOperand(rCtx, brush) {
|
|
@@ -20075,46 +20226,18 @@ function patternOperand(rCtx, brush) {
|
|
|
20075
20226
|
function drawSourceMapped(target, decoded, req, offsetX, offsetY, mode) {
|
|
20076
20227
|
const dLeft = Math.min(req.dx, req.dx + req.dw) - offsetX;
|
|
20077
20228
|
const dTop = Math.min(req.dy, req.dy + req.dh) - offsetY;
|
|
20078
|
-
|
|
20079
|
-
|
|
20080
|
-
const tile = createTempCanvas(px.width, px.height);
|
|
20081
|
-
if (!tile) {
|
|
20082
|
-
return;
|
|
20083
|
-
}
|
|
20084
|
-
canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
|
|
20085
|
-
target.save();
|
|
20086
|
-
target.globalCompositeOperation = "source-over";
|
|
20087
|
-
canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
|
|
20088
|
-
target.restore();
|
|
20229
|
+
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);
|
|
20230
|
+
if (px.width === 0 || px.height === 0) {
|
|
20089
20231
|
return;
|
|
20090
20232
|
}
|
|
20091
|
-
const
|
|
20092
|
-
|
|
20093
|
-
|
|
20094
|
-
|
|
20233
|
+
const tile = createTempCanvas(px.width, px.height);
|
|
20234
|
+
if (!tile) {
|
|
20235
|
+
return;
|
|
20236
|
+
}
|
|
20237
|
+
canvasPutImageData(tile.ctx, createImageDataCompat(px.data, px.width, px.height), 0, 0);
|
|
20095
20238
|
target.save();
|
|
20096
|
-
target.
|
|
20097
|
-
target.
|
|
20098
|
-
flipX ? -1 : 1,
|
|
20099
|
-
0,
|
|
20100
|
-
0,
|
|
20101
|
-
flipY ? -1 : 1,
|
|
20102
|
-
flipX ? dLeft * 2 + adw : 0,
|
|
20103
|
-
flipY ? dTop * 2 + adh : 0
|
|
20104
|
-
);
|
|
20105
|
-
const draw = target.drawImage;
|
|
20106
|
-
draw.call(
|
|
20107
|
-
target,
|
|
20108
|
-
decoded.canvas,
|
|
20109
|
-
Math.min(req.sx, req.sx + req.sw),
|
|
20110
|
-
Math.min(req.sy, req.sy + req.sh),
|
|
20111
|
-
Math.abs(req.sw),
|
|
20112
|
-
Math.abs(req.sh),
|
|
20113
|
-
dLeft,
|
|
20114
|
-
dTop,
|
|
20115
|
-
adw,
|
|
20116
|
-
adh
|
|
20117
|
-
);
|
|
20239
|
+
target.globalCompositeOperation = "source-over";
|
|
20240
|
+
canvasDrawImage(target, tile.canvas, Math.round(dLeft), Math.round(dTop), px.width, px.height);
|
|
20118
20241
|
target.restore();
|
|
20119
20242
|
}
|
|
20120
20243
|
function decodeSource(rCtx, req) {
|
|
@@ -20131,17 +20254,15 @@ function decodeSource(rCtx, req) {
|
|
|
20131
20254
|
if (!image) {
|
|
20132
20255
|
return null;
|
|
20133
20256
|
}
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
return null;
|
|
20257
|
+
if (req.stretch && rCtx.state.stretchBltMode === HALFTONE) {
|
|
20258
|
+
applyColorAdjustment(image, rCtx.state.colorAdjustment);
|
|
20137
20259
|
}
|
|
20138
|
-
canvasPutImageData(temp.ctx, image, 0, 0);
|
|
20139
20260
|
let { sy } = req;
|
|
20140
20261
|
if (req.dibOrigin === "bottom-left" && rCtx.view.getInt32(req.source.bmi + 8, true) > 0) {
|
|
20141
20262
|
sy = image.height - sy - req.sh;
|
|
20142
20263
|
}
|
|
20143
20264
|
return {
|
|
20144
|
-
decoded: { pixels: image
|
|
20265
|
+
decoded: { pixels: image },
|
|
20145
20266
|
req: { ...req, sy }
|
|
20146
20267
|
};
|
|
20147
20268
|
}
|
|
@@ -20254,7 +20375,7 @@ function nudgedFloor(num, den, dx, dy) {
|
|
|
20254
20375
|
const q = Math.floor(num / den);
|
|
20255
20376
|
return num === q * den && nudgedSign(0, dx, dy) < 0 ? q - 1 : q;
|
|
20256
20377
|
}
|
|
20257
|
-
function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, sy, sw, sh, dibOrigin) {
|
|
20378
|
+
function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, sy, sw, sh, dibOrigin, stretch) {
|
|
20258
20379
|
const { ctx } = rCtx;
|
|
20259
20380
|
const index = rop3Index(rop);
|
|
20260
20381
|
if (index === 170) {
|
|
@@ -20290,7 +20411,8 @@ function executeRotatedBlit(rCtx, logDx, logDy, logDw, logDh, rop, source, sx, s
|
|
|
20290
20411
|
sy,
|
|
20291
20412
|
sw,
|
|
20292
20413
|
sh,
|
|
20293
|
-
dibOrigin
|
|
20414
|
+
dibOrigin,
|
|
20415
|
+
stretch
|
|
20294
20416
|
});
|
|
20295
20417
|
if (!decoded) {
|
|
20296
20418
|
return;
|
|
@@ -20458,7 +20580,8 @@ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
|
|
|
20458
20580
|
srcRect.sy,
|
|
20459
20581
|
srcRect.sw,
|
|
20460
20582
|
srcRect.sh,
|
|
20461
|
-
"top-left"
|
|
20583
|
+
"top-left",
|
|
20584
|
+
stretch
|
|
20462
20585
|
);
|
|
20463
20586
|
return true;
|
|
20464
20587
|
}
|
|
@@ -20470,7 +20593,8 @@ function handleBlt(rCtx, offset, dataOff, recSize, stretch) {
|
|
|
20470
20593
|
dh: gmh(rCtx, cyDest),
|
|
20471
20594
|
source,
|
|
20472
20595
|
...srcRect,
|
|
20473
|
-
dibOrigin: "top-left"
|
|
20596
|
+
dibOrigin: "top-left",
|
|
20597
|
+
stretch
|
|
20474
20598
|
});
|
|
20475
20599
|
return true;
|
|
20476
20600
|
}
|
|
@@ -20497,7 +20621,7 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
|
|
|
20497
20621
|
const sw = view.getInt32(dataOff + 32, true);
|
|
20498
20622
|
const sh = view.getInt32(dataOff + 36, true);
|
|
20499
20623
|
if (hasWorldRotation(rCtx)) {
|
|
20500
|
-
executeRotatedBlit(rCtx, xDest, yDest, cxDest, cyDest, rop, source, sx, sy, sw, sh, "bottom-left");
|
|
20624
|
+
executeRotatedBlit(rCtx, xDest, yDest, cxDest, cyDest, rop, source, sx, sy, sw, sh, "bottom-left", true);
|
|
20501
20625
|
return true;
|
|
20502
20626
|
}
|
|
20503
20627
|
executeBlit(rCtx, {
|
|
@@ -20511,7 +20635,8 @@ function handleStretchDibits(rCtx, offset, dataOff, recSize) {
|
|
|
20511
20635
|
sy,
|
|
20512
20636
|
sw,
|
|
20513
20637
|
sh,
|
|
20514
|
-
dibOrigin: "bottom-left"
|
|
20638
|
+
dibOrigin: "bottom-left",
|
|
20639
|
+
stretch: true
|
|
20515
20640
|
});
|
|
20516
20641
|
return true;
|
|
20517
20642
|
}
|
|
@@ -23027,20 +23152,9 @@ function handleEmfGdiMiscRecord(rCtx, recType, dataOff, recSize) {
|
|
|
23027
23152
|
return true;
|
|
23028
23153
|
}
|
|
23029
23154
|
case EMR_SETCOLORADJUSTMENT: {
|
|
23030
|
-
|
|
23031
|
-
|
|
23032
|
-
|
|
23033
|
-
illuminant: view.getUint16(dataOff + 4, true),
|
|
23034
|
-
redGamma: view.getUint16(dataOff + 6, true),
|
|
23035
|
-
greenGamma: view.getUint16(dataOff + 8, true),
|
|
23036
|
-
blueGamma: view.getUint16(dataOff + 10, true),
|
|
23037
|
-
referenceBlack: view.getUint16(dataOff + 12, true),
|
|
23038
|
-
referenceWhite: view.getUint16(dataOff + 14, true),
|
|
23039
|
-
contrast: view.getInt16(dataOff + 16, true),
|
|
23040
|
-
brightness: view.getInt16(dataOff + 18, true),
|
|
23041
|
-
colorfulness: view.getInt16(dataOff + 20, true),
|
|
23042
|
-
redGreenTint: view.getInt16(dataOff + 22, true)
|
|
23043
|
-
};
|
|
23155
|
+
const ca = recSize >= 8 + COLOR_ADJUSTMENT_SIZE ? readColorAdjustment(view, dataOff) : null;
|
|
23156
|
+
if (ca) {
|
|
23157
|
+
state.colorAdjustment = ca;
|
|
23044
23158
|
}
|
|
23045
23159
|
return true;
|
|
23046
23160
|
}
|