
Created 2025-09-21
/**
 * ============================================================================
 * =                     ASCII Hands with Ml5.js HandPose                     =
 * ============================================================================
 *
 * Wave your hands to see the effect!
 *
 * This example shows how to use **Ml5** HandPose to detect hands and draw them
 * on the screen. Also, it shows how to use **p5** to create a capture video
 * element and remove it after the video is no longer needed.
 *
 * Note that we use ml5 **async constructor** to create the handPose object,
 * which is what I worked on this summer as a ml5 researcher to support p5 2.0
 * async setup. The async constructor is a new feature in ml5 1.0.0.
 *
 * You can check out the original pull request here:
 * https://github.com/ml5js/ml5-next-gen/pull/258
 *
 * You can check out the original issue here:
 * https://github.com/ml5js/ml5-next-gen/issues/244
 */
const width = 96;
const height = 30;
const scaleX = d3.scaleLinear([0, 640], [0, width]);
const scaleY = d3.scaleLinear([0, 480], [0, height]);
const video = await createCapture(640, 480);
const handPose = await ml5.handPose(video); // Async constructor!!!
//➜ ................................................................................................
//➜ .............@..................................................................................
//➜ ....................@...........................................................................
//➜ ....@.......@...................................................................................
//➜ ....@..............@.........@..........................@.......................................
//➜ ...........@..................................................@.................................
//➜ ....@.............@........@...................@.........@....@.................................
//➜ ........................@.............................................@.........................
//➜ ................................................@........@....@.................................
//➜ ....@....@.....@.................................@...................@..........................
//➜ ...................@................................................@...........................
//➜ ..................................@......................@......................................
//➜ .....................................................@.......@..................................
//➜ .............................@...................................@..............................
//➜ ................................................................................................
//➜ ......................@................@...@....................................................
//➜ ..............@.................................................................................
//➜ ....@............................................@..............................................
//➜ ......................................................@.........................................
//➜ .............................................................@..................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
//➜ ................................................................................................
handPose.detectStart(video, (hands) => {
  const buffer = d3.range(width * height).map(() => ".");
  for (let i = 0; i < hands.length; i++) {
    const hand = hands[i];
    for (let j = 0; j < hand.keypoints.length; j++) {
      const points = hand.keypoints[j];
      const x = ~~scaleX(points.x);
      const y = ~~scaleY(points.y);
      if (x > 0 && x < width && y > 0 && y < height) buffer[y * width + x] = "@";
    }
  }
  let output = "";
  for (let i = 0; i < height; ++i) {
    for (let j = 0; j < width; ++j) output += buffer[i * width + j];
    output += i === height - 1 ? "" : "\n";
  }
  clear();
  echo(output);
});
{
  invalidation.then(() => removeCapture(video));
}
// Use p5 to create a capture video element.
function createCapture(width, height) {
  return new Promise((resolve) => {
    new p5((p) => {
      p.setup = () => {
        p.noCanvas();
        p.noLoop();
        const video = p.createCapture(p.VIDEO);
        video.size(width, height);
        video.hide();
        resolve(video);
      };
    });
  });
}
// Remove the capture video element.
function removeCapture(video) {
  if (video) {
    if (video.elt && video.elt.srcObject) {
      video.elt.srcObject.getTracks().forEach((track) => track.stop());
    }
    video.remove();
  }
}
const ml5 = await recho.require("https://unpkg.com/ml5@1/dist/ml5.js");
const p5 = await recho.require("https://unpkg.com/p5@1.2.0/lib/p5.js");
const d3 = await recho.require("d3");