@shotstack/shotstack-canvas 1.1.4 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entry.node.cjs +104 -47
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.js +104 -47
- package/dist/entry.node.js.map +1 -1
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -1192,84 +1192,141 @@ async function createNodePainter(opts) {
|
|
|
1192
1192
|
);
|
|
1193
1193
|
const ctx = canvas.getContext("2d");
|
|
1194
1194
|
if (!ctx) throw new Error("2D context unavailable in Node (canvas).");
|
|
1195
|
+
const offscreenCanvas = createCanvas(canvas.width, canvas.height);
|
|
1196
|
+
const offscreenCtx = offscreenCanvas.getContext("2d");
|
|
1195
1197
|
const api = {
|
|
1196
1198
|
async render(ops) {
|
|
1197
1199
|
const globalBox = computeGlobalTextBounds(ops);
|
|
1200
|
+
let needsAlphaExtraction = false;
|
|
1198
1201
|
for (const op of ops) {
|
|
1199
1202
|
if (op.op === "BeginFrame") {
|
|
1200
|
-
if (op.clear) ctx.clearRect(0, 0, op.width, op.height);
|
|
1201
1203
|
const dpr = op.pixelRatio ?? opts.pixelRatio;
|
|
1202
1204
|
const wantW = Math.floor(op.width * dpr);
|
|
1203
1205
|
const wantH = Math.floor(op.height * dpr);
|
|
1204
1206
|
if (canvas.width !== wantW || canvas.height !== wantH) {
|
|
1205
1207
|
canvas.width = wantW;
|
|
1206
1208
|
canvas.height = wantH;
|
|
1209
|
+
offscreenCanvas.width = wantW;
|
|
1210
|
+
offscreenCanvas.height = wantH;
|
|
1207
1211
|
}
|
|
1208
1212
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1213
|
+
offscreenCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1214
|
+
const hasBackground = !!(op.bg && op.bg.color);
|
|
1215
|
+
needsAlphaExtraction = !hasBackground;
|
|
1209
1216
|
if (op.bg && op.bg.color) {
|
|
1210
1217
|
const { color, opacity, radius } = op.bg;
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
1222
|
-
}
|
|
1218
|
+
const c = parseHex6(color, opacity);
|
|
1219
|
+
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1220
|
+
if (radius && radius > 0) {
|
|
1221
|
+
ctx.save();
|
|
1222
|
+
ctx.beginPath();
|
|
1223
|
+
roundRectPath(ctx, 0, 0, op.width, op.height, radius);
|
|
1224
|
+
ctx.fill();
|
|
1225
|
+
ctx.restore();
|
|
1226
|
+
} else {
|
|
1227
|
+
ctx.fillRect(0, 0, op.width, op.height);
|
|
1223
1228
|
}
|
|
1229
|
+
} else {
|
|
1230
|
+
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1231
|
+
ctx.fillRect(0, 0, op.width, op.height);
|
|
1232
|
+
offscreenCtx.fillStyle = "rgb(0, 0, 0)";
|
|
1233
|
+
offscreenCtx.fillRect(0, 0, op.width, op.height);
|
|
1224
1234
|
}
|
|
1225
1235
|
continue;
|
|
1226
1236
|
}
|
|
1237
|
+
const renderToBoth = (renderFn) => {
|
|
1238
|
+
if (needsAlphaExtraction) {
|
|
1239
|
+
renderFn(ctx);
|
|
1240
|
+
renderFn(offscreenCtx);
|
|
1241
|
+
} else {
|
|
1242
|
+
renderFn(ctx);
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1227
1245
|
if (op.op === "FillPath") {
|
|
1228
1246
|
const fillOp = op;
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1247
|
+
renderToBoth((context) => {
|
|
1248
|
+
context.save();
|
|
1249
|
+
context.translate(fillOp.x, fillOp.y);
|
|
1250
|
+
const s = fillOp.scale ?? 1;
|
|
1251
|
+
context.scale(s, -s);
|
|
1252
|
+
context.beginPath();
|
|
1253
|
+
drawSvgPathOnCtx(context, fillOp.path);
|
|
1254
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1255
|
+
const fill = makeGradientFromBBox(context, fillOp.fill, bbox);
|
|
1256
|
+
context.fillStyle = fill;
|
|
1257
|
+
context.fill();
|
|
1258
|
+
context.restore();
|
|
1259
|
+
});
|
|
1240
1260
|
continue;
|
|
1241
1261
|
}
|
|
1242
1262
|
if (op.op === "StrokePath") {
|
|
1243
1263
|
const strokeOp = op;
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1264
|
+
renderToBoth((context) => {
|
|
1265
|
+
context.save();
|
|
1266
|
+
context.translate(strokeOp.x, strokeOp.y);
|
|
1267
|
+
const s = strokeOp.scale ?? 1;
|
|
1268
|
+
context.scale(s, -s);
|
|
1269
|
+
const invAbs = 1 / Math.abs(s);
|
|
1270
|
+
context.beginPath();
|
|
1271
|
+
drawSvgPathOnCtx(context, strokeOp.path);
|
|
1272
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1273
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1274
|
+
context.lineWidth = strokeOp.width * invAbs;
|
|
1275
|
+
context.lineJoin = "round";
|
|
1276
|
+
context.lineCap = "round";
|
|
1277
|
+
context.stroke();
|
|
1278
|
+
context.restore();
|
|
1279
|
+
});
|
|
1258
1280
|
continue;
|
|
1259
1281
|
}
|
|
1260
1282
|
if (op.op === "DecorationLine") {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1283
|
+
renderToBoth((context) => {
|
|
1284
|
+
context.save();
|
|
1285
|
+
const c = parseHex6(op.color, op.opacity);
|
|
1286
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1287
|
+
context.lineWidth = op.width;
|
|
1288
|
+
context.beginPath();
|
|
1289
|
+
context.moveTo(op.from.x, op.from.y);
|
|
1290
|
+
context.lineTo(op.to.x, op.to.y);
|
|
1291
|
+
context.stroke();
|
|
1292
|
+
context.restore();
|
|
1293
|
+
});
|
|
1270
1294
|
continue;
|
|
1271
1295
|
}
|
|
1272
1296
|
}
|
|
1297
|
+
if (needsAlphaExtraction) {
|
|
1298
|
+
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
1299
|
+
const blackData = offscreenCtx.getImageData(0, 0, canvas.width, canvas.height);
|
|
1300
|
+
const white = whiteData.data;
|
|
1301
|
+
const black = blackData.data;
|
|
1302
|
+
for (let i = 0; i < white.length; i += 4) {
|
|
1303
|
+
const wr = white[i];
|
|
1304
|
+
const wg = white[i + 1];
|
|
1305
|
+
const wb = white[i + 2];
|
|
1306
|
+
const br = black[i];
|
|
1307
|
+
const bg = black[i + 1];
|
|
1308
|
+
const bb = black[i + 2];
|
|
1309
|
+
const alpha = 255 - Math.max(wr - br, wg - bg, wb - bb);
|
|
1310
|
+
if (alpha <= 2) {
|
|
1311
|
+
white[i] = 0;
|
|
1312
|
+
white[i + 1] = 0;
|
|
1313
|
+
white[i + 2] = 0;
|
|
1314
|
+
white[i + 3] = 0;
|
|
1315
|
+
} else if (alpha >= 253) {
|
|
1316
|
+
white[i] = br;
|
|
1317
|
+
white[i + 1] = bg;
|
|
1318
|
+
white[i + 2] = bb;
|
|
1319
|
+
white[i + 3] = 255;
|
|
1320
|
+
} else {
|
|
1321
|
+
const alphaFactor = 255 / alpha;
|
|
1322
|
+
white[i] = Math.min(255, Math.max(0, Math.round(br * alphaFactor)));
|
|
1323
|
+
white[i + 1] = Math.min(255, Math.max(0, Math.round(bg * alphaFactor)));
|
|
1324
|
+
white[i + 2] = Math.min(255, Math.max(0, Math.round(bb * alphaFactor)));
|
|
1325
|
+
white[i + 3] = alpha;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
ctx.putImageData(whiteData, 0, 0);
|
|
1329
|
+
}
|
|
1273
1330
|
},
|
|
1274
1331
|
async toPNG() {
|
|
1275
1332
|
return canvas.toBuffer("image/png");
|