@shotstack/shotstack-canvas 1.1.5 → 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 -50
- package/dist/entry.node.cjs.map +1 -1
- package/dist/entry.node.js +104 -50
- package/dist/entry.node.js.map +1 -1
- package/package.json +1 -1
package/dist/entry.node.cjs
CHANGED
|
@@ -1192,87 +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);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1213
|
+
offscreenCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1214
|
+
const hasBackground = !!(op.bg && op.bg.color);
|
|
1215
|
+
needsAlphaExtraction = !hasBackground;
|
|
1212
1216
|
if (op.bg && op.bg.color) {
|
|
1213
1217
|
const { color, opacity, radius } = op.bg;
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
1225
|
-
}
|
|
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);
|
|
1226
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);
|
|
1227
1234
|
}
|
|
1228
1235
|
continue;
|
|
1229
1236
|
}
|
|
1237
|
+
const renderToBoth = (renderFn) => {
|
|
1238
|
+
if (needsAlphaExtraction) {
|
|
1239
|
+
renderFn(ctx);
|
|
1240
|
+
renderFn(offscreenCtx);
|
|
1241
|
+
} else {
|
|
1242
|
+
renderFn(ctx);
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1230
1245
|
if (op.op === "FillPath") {
|
|
1231
1246
|
const fillOp = op;
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
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
|
+
});
|
|
1243
1260
|
continue;
|
|
1244
1261
|
}
|
|
1245
1262
|
if (op.op === "StrokePath") {
|
|
1246
1263
|
const strokeOp = op;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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
|
+
});
|
|
1261
1280
|
continue;
|
|
1262
1281
|
}
|
|
1263
1282
|
if (op.op === "DecorationLine") {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
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
|
+
});
|
|
1273
1294
|
continue;
|
|
1274
1295
|
}
|
|
1275
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
|
+
}
|
|
1276
1330
|
},
|
|
1277
1331
|
async toPNG() {
|
|
1278
1332
|
return canvas.toBuffer("image/png");
|