Search code examples
javascriptimage-processingcanvasescpos

Trimming the bottom of canvas, converting to escpos command and appending the a cut command results in premature cut


I have written a function that takes a canvas scans the horizontal pixel rows from bottom to top to find all the empty lines and trims the canvas height. After trimming I scan through the image data and read pixels into packed bytes representing monochrome image where each bit is a pixel. Afterwards I make a large buffer and at the start put some prefix bytes found in the escpos docs. and at the end of the large buffer I am appending 3 bytes for a cut command.

If I remove the epmtyLines calculation part, everything works smoothly but as soon as I enable the line trimming logic. The printer actually prints the whole image and but the cut happens 70 to 80 horizontal rows before the end of image. I have reread the trimming logic dozens of times and nothing seems to be wrong.

Note: I am considering the last line of canvas to be empty by default hence starting the loop from height - 2.

const canvasToEscpos = (canvas) => {
  var context = canvas.getContext("2d");
  const { data } = context.getImageData(0, 0, canvas.width, canvas.height);

  let width = canvas.width;
  let height = canvas.height;

  let emptyLines = 0;
  for (let y = height - 2; y >= 0; y--) {
    let empty = true;
    for (let x = 0; x < width; x++) {
      empty &&= data[(width * y + x) * 4] > 248;
      if (!empty) break;
    }
    if (empty) {
      emptyLines++;
    } else {
      break;
    }
  }
  console.log({ emptyLines });
  height -= emptyLines + 1;

  const bytes = new Uint8Array((width * height) >> 3);
  const readPixel = (x, y) => {
    return data[(width * y + x) * 4] < 200 ? 1 : 0;
  };
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x += 8) {
      for (let b = 0; b < 8; b++) {
        bytes[y * (width >> 3) + (x >> 3)] |= readPixel(x + b, y) << (7 - b);
      }
    }
  }
  const buffer = new Uint8Array(10 + bytes.length + 3);
  // image command
  buffer.set(
    [
      0x0a,
      0x0d,
      0x1d,
      0x76,
      0x30,
      0x00,
      (width >> 3) & 0xff,
      ((width >> 3) >> 8) & 0xff,
      height & 0xff,
      (height >> 8) & 0xff,
    ],
    0
  );
  // image bits
  buffer.set(bytes, 10);
  // cut
  buffer.set([0x1d, 0x56, 0x00], buffer.length - 3);
  return buffer;
};

Solution

  • Since there is a gap between the print head of the printer and the paper cutter, if the paper is cut immediately after printing, the printed result will remain on the cut paper for that gap.
    GS V

    [Name]
      Select cut mode and cut paper
    [Format]
      <Function A> ASCII   GS  V m
                   Hex     1D 56 m
                   Decimal 29 86 m
      <Function B> ASCII   GS  V m n
                   Hex     1D 56 m n
                   Decimal 29 86 m n
    
      <Function A> Cuts the paper                 0, 48 Full cut
                                                  1, 49 Partial cut
      <Function B> Feeds paper and cuts the paper 65    Full cut
                                                  66    Partial cut
    
      <Function A> Executes paper cut
      <Function B> Feeds paper to [cutting position + (n × vertical motion unit)] and executes paper cut
    

    Your program uses the Function A command on this page to cut the paper, but in order to include all the print results on the cut side, use the Function B command to set the paper feed amount before cutting to the printer. must be adjusted accordingly.

    The distance between the printer's print head and the paper cutter may be different for each printer model, so check the specifications of your printer or adjust it by experiment.

    For example, here is an example of paper cutting using EPSON's print data send tool.
    Select Cut Mode and Cut Paper