asciify-engine 1.0.70 → 1.0.71

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/index.cjs CHANGED
@@ -1167,23 +1167,14 @@ function getSourceDims(el) {
1167
1167
  if (el instanceof HTMLImageElement) return { w: el.naturalWidth || el.width, h: el.naturalHeight || el.height };
1168
1168
  return { w: el.width, h: el.height };
1169
1169
  }
1170
- function makeHiResCanvas(srcW, srcH, displayW, displayH) {
1171
- if (srcW <= displayW && srcH <= displayH) return null;
1170
+ function computeRenderDims(srcW, srcH) {
1172
1171
  const MAX = 2048;
1173
1172
  const scale = Math.min(1, MAX / Math.max(srcW, srcH));
1174
- const offW = Math.round(srcW * scale);
1175
- const offH = Math.round(srcH * scale);
1176
- if (offW <= displayW && offH <= displayH) return null;
1177
- const offCanvas = document.createElement("canvas");
1178
- offCanvas.width = offW;
1179
- offCanvas.height = offH;
1180
- const offCtx = offCanvas.getContext("2d");
1181
- if (!offCtx) return null;
1182
- return { offCanvas, offCtx, offW, offH };
1173
+ return { renderW: Math.round(srcW * scale), renderH: Math.round(srcH * scale) };
1183
1174
  }
1184
1175
  function sizeCanvasToContainer(canvas, container, aspect, srcW, srcH) {
1185
1176
  const { width, height } = container.getBoundingClientRect();
1186
- if (!width || !height) return;
1177
+ if (!width || !height) return { renderW: 0, renderH: 0, dpr: 1 };
1187
1178
  let cssW = width, cssH = cssW / aspect;
1188
1179
  if (cssH > height) {
1189
1180
  cssH = height;
@@ -1191,21 +1182,21 @@ function sizeCanvasToContainer(canvas, container, aspect, srcW, srcH) {
1191
1182
  }
1192
1183
  cssW = Math.round(cssW);
1193
1184
  cssH = Math.round(cssH);
1194
- const MAX_BUF = 2048;
1195
- let bufW, bufH;
1196
- if (srcW && srcH && (srcW > cssW || srcH > cssH)) {
1197
- const scale = Math.min(1, MAX_BUF / Math.max(srcW, srcH));
1198
- bufW = Math.round(srcW * scale);
1199
- bufH = Math.round(srcH * scale);
1185
+ let renderW, renderH;
1186
+ if (srcW && srcH) {
1187
+ ({ renderW, renderH } = computeRenderDims(srcW, srcH));
1200
1188
  } else {
1201
- const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1202
- bufW = Math.round(cssW * dpr);
1203
- bufH = Math.round(cssH * dpr);
1204
- }
1205
- canvas.width = bufW;
1206
- canvas.height = bufH;
1189
+ renderW = cssW;
1190
+ renderH = cssH;
1191
+ }
1192
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1193
+ const MAX_PX = 8e6;
1194
+ const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
1195
+ canvas.width = Math.round(renderW * cappedDpr);
1196
+ canvas.height = Math.round(renderH * cappedDpr);
1207
1197
  canvas.style.width = cssW + "px";
1208
1198
  canvas.style.height = cssH + "px";
1199
+ return { renderW, renderH, dpr: cappedDpr };
1209
1200
  }
1210
1201
  async function asciify(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
1211
1202
  let el;
@@ -1233,16 +1224,19 @@ async function asciify(source, canvas, { fontSize, artStyle = "classic", options
1233
1224
  const ctx = canvas.getContext("2d");
1234
1225
  if (!ctx) throw new Error("Could not get 2d context from canvas");
1235
1226
  const { w: srcW, h: srcH } = getSourceDims(el);
1236
- const hires = makeHiResCanvas(srcW, srcH, canvas.width, canvas.height);
1237
- if (hires) {
1238
- const { frame } = imageToAsciiFrame(el, merged, hires.offW, hires.offH);
1239
- renderFrameToCanvas(hires.offCtx, frame, merged, hires.offW, hires.offH);
1240
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1241
- ctx.drawImage(hires.offCanvas, 0, 0, canvas.width, canvas.height);
1242
- } else {
1243
- const { frame } = imageToAsciiFrame(el, merged, canvas.width, canvas.height);
1244
- renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height);
1245
- }
1227
+ const { renderW, renderH } = computeRenderDims(srcW, srcH);
1228
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1229
+ const MAX_PX = 8e6;
1230
+ const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
1231
+ if (canvas.width < renderW || canvas.height < renderH) {
1232
+ canvas.width = Math.round(renderW * cappedDpr);
1233
+ canvas.height = Math.round(renderH * cappedDpr);
1234
+ }
1235
+ const { frame } = imageToAsciiFrame(el, merged, renderW, renderH);
1236
+ ctx.save();
1237
+ ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
1238
+ renderFrameToCanvas(ctx, frame, merged, renderW, renderH);
1239
+ ctx.restore();
1246
1240
  }
1247
1241
  async function asciifyGif(source, canvas, { fontSize, artStyle = "classic", options = {} } = {}) {
1248
1242
  const buffer = typeof source === "string" ? await fetch(source).then((r) => r.arrayBuffer()) : source;
@@ -1295,24 +1289,26 @@ async function asciifyVideo(source, canvas, { fontSize, artStyle = "classic", op
1295
1289
  video2 = source;
1296
1290
  }
1297
1291
  if (container) sizeCanvasToContainer(canvas, container, video2.videoWidth / video2.videoHeight, video2.videoWidth, video2.videoHeight);
1298
- const hires2 = makeHiResCanvas(video2.videoWidth, video2.videoHeight, canvas.width, canvas.height);
1299
- const renderW = hires2 ? hires2.offW : canvas.width;
1300
- const renderH = hires2 ? hires2.offH : canvas.height;
1292
+ const { renderW: renderW2, renderH: renderH2 } = computeRenderDims(video2.videoWidth, video2.videoHeight);
1293
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1294
+ const MAX_PX = 8e6;
1295
+ const cappedDpr = renderW2 * dpr * renderH2 * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW2 * renderH2)) : dpr;
1296
+ if (canvas.width < Math.round(renderW2 * cappedDpr)) {
1297
+ canvas.width = Math.round(renderW2 * cappedDpr);
1298
+ canvas.height = Math.round(renderH2 * cappedDpr);
1299
+ }
1301
1300
  const maxDur = trimEnd !== void 0 ? trimEnd - trimStart : 10;
1302
- const { frames, fps } = await videoToAsciiFrames(video2, merged, renderW, renderH, void 0, maxDur, void 0, trimStart);
1301
+ const { frames, fps } = await videoToAsciiFrames(video2, merged, renderW2, renderH2, void 0, maxDur, void 0, trimStart);
1303
1302
  let cancelled2 = false, animId2, i = 0, last = performance.now();
1304
1303
  let firstFrame2 = true;
1305
1304
  const interval = 1e3 / fps;
1306
1305
  const tick2 = (now) => {
1307
1306
  if (cancelled2) return;
1308
1307
  if (now - last >= interval) {
1309
- if (hires2) {
1310
- renderFrameToCanvas(hires2.offCtx, frames[i], merged, hires2.offW, hires2.offH);
1311
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1312
- ctx.drawImage(hires2.offCanvas, 0, 0, canvas.width, canvas.height);
1313
- } else {
1314
- renderFrameToCanvas(ctx, frames[i], merged, canvas.width, canvas.height);
1315
- }
1308
+ ctx.save();
1309
+ ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
1310
+ renderFrameToCanvas(ctx, frames[i], merged, renderW2, renderH2);
1311
+ ctx.restore();
1316
1312
  i = (i + 1) % frames.length;
1317
1313
  last = now;
1318
1314
  if (firstFrame2) {
@@ -1383,14 +1379,29 @@ async function asciifyVideo(source, canvas, { fontSize, artStyle = "classic", op
1383
1379
  video.addEventListener("timeupdate", timeupdateHandler);
1384
1380
  }
1385
1381
  let ro = null;
1382
+ const { renderW, renderH } = computeRenderDims(video.videoWidth, video.videoHeight);
1386
1383
  if (container) {
1387
1384
  const aspect = video.videoWidth / video.videoHeight;
1388
1385
  const vw = video.videoWidth, vh = video.videoHeight;
1389
- sizeCanvasToContainer(canvas, container, aspect, vw, vh);
1390
- ro = new ResizeObserver(() => sizeCanvasToContainer(canvas, container, aspect, vw, vh));
1386
+ const sizing = sizeCanvasToContainer(canvas, container, aspect, vw, vh);
1387
+ const sCtx = canvas.getContext("2d");
1388
+ if (sCtx) sCtx.setTransform(sizing.dpr, 0, 0, sizing.dpr, 0, 0);
1389
+ ro = new ResizeObserver(() => {
1390
+ const s = sizeCanvasToContainer(canvas, container, aspect, vw, vh);
1391
+ const rCtx = canvas.getContext("2d");
1392
+ if (rCtx) rCtx.setTransform(s.dpr, 0, 0, s.dpr, 0, 0);
1393
+ });
1391
1394
  ro.observe(container);
1395
+ } else {
1396
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1397
+ const MAX_PX = 8e6;
1398
+ const cappedDpr = renderW * dpr * renderH * dpr > MAX_PX ? Math.sqrt(MAX_PX / (renderW * renderH)) : dpr;
1399
+ if (canvas.width < Math.round(renderW * cappedDpr)) {
1400
+ canvas.width = Math.round(renderW * cappedDpr);
1401
+ canvas.height = Math.round(renderH * cappedDpr);
1402
+ }
1403
+ ctx.setTransform(cappedDpr, 0, 0, cappedDpr, 0, 0);
1392
1404
  }
1393
- const hires = makeHiResCanvas(video.videoWidth, video.videoHeight, canvas.width, canvas.height);
1394
1405
  let cancelled = false;
1395
1406
  let animId;
1396
1407
  let firstFrame = true;
@@ -1400,28 +1411,14 @@ async function asciifyVideo(source, canvas, { fontSize, artStyle = "classic", op
1400
1411
  if (video.readyState < 2 || canvas.width === 0 || canvas.height === 0) return;
1401
1412
  if (trimStart > 0 && video.currentTime < trimStart) return;
1402
1413
  if (trimEnd !== void 0 && video.currentTime >= trimEnd) return;
1403
- if (hires) {
1404
- const { frame } = imageToAsciiFrame(video, merged, hires.offW, hires.offH);
1405
- if (frame.length > 0) {
1406
- renderFrameToCanvas(hires.offCtx, frame, merged, hires.offW, hires.offH, 0, null);
1407
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1408
- ctx.drawImage(hires.offCanvas, 0, 0, canvas.width, canvas.height);
1409
- if (firstFrame) {
1410
- firstFrame = false;
1411
- onReady?.(video);
1412
- }
1413
- onFrame?.();
1414
- }
1415
- } else {
1416
- const { frame } = imageToAsciiFrame(video, merged, canvas.width, canvas.height);
1417
- if (frame.length > 0) {
1418
- renderFrameToCanvas(ctx, frame, merged, canvas.width, canvas.height, 0, null);
1419
- if (firstFrame) {
1420
- firstFrame = false;
1421
- onReady?.(video);
1422
- }
1423
- onFrame?.();
1414
+ const { frame } = imageToAsciiFrame(video, merged, renderW, renderH);
1415
+ if (frame.length > 0) {
1416
+ renderFrameToCanvas(ctx, frame, merged, renderW, renderH, 0, null);
1417
+ if (firstFrame) {
1418
+ firstFrame = false;
1419
+ onReady?.(video);
1424
1420
  }
1421
+ onFrame?.();
1425
1422
  }
1426
1423
  };
1427
1424
  animId = requestAnimationFrame(tick);