@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.js
CHANGED
|
@@ -1154,84 +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);
|
|
1175
|
+
offscreenCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
1176
|
+
const hasBackground = !!(op.bg && op.bg.color);
|
|
1177
|
+
needsAlphaExtraction = !hasBackground;
|
|
1171
1178
|
if (op.bg && op.bg.color) {
|
|
1172
1179
|
const { color, opacity, radius } = op.bg;
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
ctx.fillRect(0, 0, op.width, op.height);
|
|
1184
|
-
}
|
|
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);
|
|
1185
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);
|
|
1186
1196
|
}
|
|
1187
1197
|
continue;
|
|
1188
1198
|
}
|
|
1199
|
+
const renderToBoth = (renderFn) => {
|
|
1200
|
+
if (needsAlphaExtraction) {
|
|
1201
|
+
renderFn(ctx);
|
|
1202
|
+
renderFn(offscreenCtx);
|
|
1203
|
+
} else {
|
|
1204
|
+
renderFn(ctx);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1189
1207
|
if (op.op === "FillPath") {
|
|
1190
1208
|
const fillOp = op;
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
+
});
|
|
1202
1222
|
continue;
|
|
1203
1223
|
}
|
|
1204
1224
|
if (op.op === "StrokePath") {
|
|
1205
1225
|
const strokeOp = op;
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
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
|
+
});
|
|
1220
1242
|
continue;
|
|
1221
1243
|
}
|
|
1222
1244
|
if (op.op === "DecorationLine") {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
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
|
+
});
|
|
1232
1256
|
continue;
|
|
1233
1257
|
}
|
|
1234
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
|
+
}
|
|
1235
1292
|
},
|
|
1236
1293
|
async toPNG() {
|
|
1237
1294
|
return canvas.toBuffer("image/png");
|