@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.js
CHANGED
|
@@ -1154,87 +1154,141 @@ async function createNodePainter(opts) {
|
|
|
1154
1154
|
);
|
|
1155
1155
|
const ctx = canvas.getContext("2d");
|
|
1156
1156
|
if (!ctx) throw new Error("2D context unavailable in Node (canvas).");
|
|
1157
|
+
const offscreenCanvas = createCanvas(canvas.width, canvas.height);
|
|
1158
|
+
const offscreenCtx = offscreenCanvas.getContext("2d");
|
|
1157
1159
|
const api = {
|
|
1158
1160
|
async render(ops) {
|
|
1159
1161
|
const globalBox = computeGlobalTextBounds(ops);
|
|
1162
|
+
let needsAlphaExtraction = false;
|
|
1160
1163
|
for (const op of ops) {
|
|
1161
1164
|
if (op.op === "BeginFrame") {
|
|
1162
|
-
if (op.clear) ctx.clearRect(0, 0, op.width, op.height);
|
|
1163
1165
|
const dpr = op.pixelRatio ?? opts.pixelRatio;
|
|
1164
1166
|
const wantW = Math.floor(op.width * dpr);
|
|
1165
1167
|
const wantH = Math.floor(op.height * dpr);
|
|
1166
1168
|
if (canvas.width !== wantW || canvas.height !== wantH) {
|
|
1167
1169
|
canvas.width = wantW;
|
|
1168
1170
|
canvas.height = wantH;
|
|
1171
|
+
offscreenCanvas.width = wantW;
|
|
1172
|
+
offscreenCanvas.height = wantH;
|
|
1169
1173
|
}
|
|
1170
1174
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1175
|
+
offscreenCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1176
|
+
const hasBackground = !!(op.bg && op.bg.color);
|
|
1177
|
+
needsAlphaExtraction = !hasBackground;
|
|
1174
1178
|
if (op.bg && op.bg.color) {
|
|
1175
1179
|
const { color, opacity, radius } = op.bg;
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
1187
|
-
}
|
|
1180
|
+
const c = parseHex6(color, opacity);
|
|
1181
|
+
ctx.fillStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1182
|
+
if (radius && radius > 0) {
|
|
1183
|
+
ctx.save();
|
|
1184
|
+
ctx.beginPath();
|
|
1185
|
+
roundRectPath(ctx, 0, 0, op.width, op.height, radius);
|
|
1186
|
+
ctx.fill();
|
|
1187
|
+
ctx.restore();
|
|
1188
|
+
} else {
|
|
1189
|
+
ctx.fillRect(0, 0, op.width, op.height);
|
|
1188
1190
|
}
|
|
1191
|
+
} else {
|
|
1192
|
+
ctx.fillStyle = "rgb(255, 255, 255)";
|
|
1193
|
+
ctx.fillRect(0, 0, op.width, op.height);
|
|
1194
|
+
offscreenCtx.fillStyle = "rgb(0, 0, 0)";
|
|
1195
|
+
offscreenCtx.fillRect(0, 0, op.width, op.height);
|
|
1189
1196
|
}
|
|
1190
1197
|
continue;
|
|
1191
1198
|
}
|
|
1199
|
+
const renderToBoth = (renderFn) => {
|
|
1200
|
+
if (needsAlphaExtraction) {
|
|
1201
|
+
renderFn(ctx);
|
|
1202
|
+
renderFn(offscreenCtx);
|
|
1203
|
+
} else {
|
|
1204
|
+
renderFn(ctx);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1192
1207
|
if (op.op === "FillPath") {
|
|
1193
1208
|
const fillOp = op;
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1209
|
+
renderToBoth((context) => {
|
|
1210
|
+
context.save();
|
|
1211
|
+
context.translate(fillOp.x, fillOp.y);
|
|
1212
|
+
const s = fillOp.scale ?? 1;
|
|
1213
|
+
context.scale(s, -s);
|
|
1214
|
+
context.beginPath();
|
|
1215
|
+
drawSvgPathOnCtx(context, fillOp.path);
|
|
1216
|
+
const bbox = fillOp.gradientBBox ?? globalBox;
|
|
1217
|
+
const fill = makeGradientFromBBox(context, fillOp.fill, bbox);
|
|
1218
|
+
context.fillStyle = fill;
|
|
1219
|
+
context.fill();
|
|
1220
|
+
context.restore();
|
|
1221
|
+
});
|
|
1205
1222
|
continue;
|
|
1206
1223
|
}
|
|
1207
1224
|
if (op.op === "StrokePath") {
|
|
1208
1225
|
const strokeOp = op;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1226
|
+
renderToBoth((context) => {
|
|
1227
|
+
context.save();
|
|
1228
|
+
context.translate(strokeOp.x, strokeOp.y);
|
|
1229
|
+
const s = strokeOp.scale ?? 1;
|
|
1230
|
+
context.scale(s, -s);
|
|
1231
|
+
const invAbs = 1 / Math.abs(s);
|
|
1232
|
+
context.beginPath();
|
|
1233
|
+
drawSvgPathOnCtx(context, strokeOp.path);
|
|
1234
|
+
const c = parseHex6(strokeOp.color, strokeOp.opacity);
|
|
1235
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1236
|
+
context.lineWidth = strokeOp.width * invAbs;
|
|
1237
|
+
context.lineJoin = "round";
|
|
1238
|
+
context.lineCap = "round";
|
|
1239
|
+
context.stroke();
|
|
1240
|
+
context.restore();
|
|
1241
|
+
});
|
|
1223
1242
|
continue;
|
|
1224
1243
|
}
|
|
1225
1244
|
if (op.op === "DecorationLine") {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1245
|
+
renderToBoth((context) => {
|
|
1246
|
+
context.save();
|
|
1247
|
+
const c = parseHex6(op.color, op.opacity);
|
|
1248
|
+
context.strokeStyle = `rgba(${c.r},${c.g},${c.b},${c.a})`;
|
|
1249
|
+
context.lineWidth = op.width;
|
|
1250
|
+
context.beginPath();
|
|
1251
|
+
context.moveTo(op.from.x, op.from.y);
|
|
1252
|
+
context.lineTo(op.to.x, op.to.y);
|
|
1253
|
+
context.stroke();
|
|
1254
|
+
context.restore();
|
|
1255
|
+
});
|
|
1235
1256
|
continue;
|
|
1236
1257
|
}
|
|
1237
1258
|
}
|
|
1259
|
+
if (needsAlphaExtraction) {
|
|
1260
|
+
const whiteData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
1261
|
+
const blackData = offscreenCtx.getImageData(0, 0, canvas.width, canvas.height);
|
|
1262
|
+
const white = whiteData.data;
|
|
1263
|
+
const black = blackData.data;
|
|
1264
|
+
for (let i = 0; i < white.length; i += 4) {
|
|
1265
|
+
const wr = white[i];
|
|
1266
|
+
const wg = white[i + 1];
|
|
1267
|
+
const wb = white[i + 2];
|
|
1268
|
+
const br = black[i];
|
|
1269
|
+
const bg = black[i + 1];
|
|
1270
|
+
const bb = black[i + 2];
|
|
1271
|
+
const alpha = 255 - Math.max(wr - br, wg - bg, wb - bb);
|
|
1272
|
+
if (alpha <= 2) {
|
|
1273
|
+
white[i] = 0;
|
|
1274
|
+
white[i + 1] = 0;
|
|
1275
|
+
white[i + 2] = 0;
|
|
1276
|
+
white[i + 3] = 0;
|
|
1277
|
+
} else if (alpha >= 253) {
|
|
1278
|
+
white[i] = br;
|
|
1279
|
+
white[i + 1] = bg;
|
|
1280
|
+
white[i + 2] = bb;
|
|
1281
|
+
white[i + 3] = 255;
|
|
1282
|
+
} else {
|
|
1283
|
+
const alphaFactor = 255 / alpha;
|
|
1284
|
+
white[i] = Math.min(255, Math.max(0, Math.round(br * alphaFactor)));
|
|
1285
|
+
white[i + 1] = Math.min(255, Math.max(0, Math.round(bg * alphaFactor)));
|
|
1286
|
+
white[i + 2] = Math.min(255, Math.max(0, Math.round(bb * alphaFactor)));
|
|
1287
|
+
white[i + 3] = alpha;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
ctx.putImageData(whiteData, 0, 0);
|
|
1291
|
+
}
|
|
1238
1292
|
},
|
|
1239
1293
|
async toPNG() {
|
|
1240
1294
|
return canvas.toBuffer("image/png");
|