From a37f8a67deab8be31e64cf46b20fba5bea02bd42 Mon Sep 17 00:00:00 2001
From: MultiMote <contact@mmote.ru>
Date: Sun, 20 Oct 2024 17:27:19 +0300
Subject: [PATCH] Attempt to reproduce the format of the original bitmap packet

---
 src/packets/packet_generator.ts | 54 ++++++++++++++++++++-------------
 src/utils.ts                    | 50 +++++++++++++++++++++---------
 2 files changed, 69 insertions(+), 35 deletions(-)

diff --git a/src/packets/packet_generator.ts b/src/packets/packet_generator.ts
index ade938c..b05713e 100644
--- a/src/packets/packet_generator.ts
+++ b/src/packets/packet_generator.ts
@@ -153,9 +153,7 @@ export class PacketGenerator {
   }
 
   public static setPrintQuantity(quantity: number): NiimbotPacket {
-    return new NiimbotPacket(RequestCommandId.PrintQuantity, [
-      ...Utils.u16ToBytes(quantity)
-    ]);
+    return new NiimbotPacket(RequestCommandId.PrintQuantity, [...Utils.u16ToBytes(quantity)]);
   }
 
   public static printStatus(): NiimbotPacket {
@@ -226,37 +224,51 @@ export class PacketGenerator {
     return packet;
   }
 
-  public static printBitmapRow(pos: number, repeats: number, data: Uint8Array): NiimbotPacket {
-    const blackPixelCount: number = Utils.countSetBits(data);
+  public static printBitmapRow(
+    pos: number,
+    repeats: number,
+    data: Uint8Array,
+    printheadPixels?: number
+  ): NiimbotPacket {
+    const { total, a, b, c } = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0);
+    // Black pixel count. Not sure what role it plays in printing.
+    // There is two formats of this part
+    // 1. <count> <count> <count> (sum must equals number of pixels, every number calculated by algorithm based on printhead resolution)
+    // 2. <0> <countH> <countL> (big endian)
+    let header: number[] = [0, ...Utils.u16ToBytes(total)];
+
+    if (printheadPixels !== undefined) {
+      header = [a, b, c];
+    }
 
     const packet = new NiimbotPacket(RequestCommandId.PrintBitmapRow, [
       ...Utils.u16ToBytes(pos),
-      // Black pixel count. Not sure what role it plays in printing.
-      // There is two formats of this part
-      // 1. <count> <count> <count> (sum must equals number of pixels, every number calculated by algorithm based on printhead resolution)
-      // 2. <0> <countH> <countL> (big endian)
-      0,
-      ...Utils.u16ToBytes(blackPixelCount),
+      ...header,
       repeats,
       ...data,
     ]);
     packet.oneWay = true;
     return packet;
   }
-
   /** Printer powers off if black pixel count > 6 */
-  public static printBitmapRowIndexed(pos: number, repeats: number, data: Uint8Array): NiimbotPacket {
-    const blackPixelCount: number = Utils.countSetBits(data);
+  // 5555 83 0e 007e 000400 01 0027 0028 0029 002a fa aaaa
+  public static printBitmapRowIndexed(pos: number, repeats: number, data: Uint8Array, printheadPixels?: number): NiimbotPacket {
+    const { total, a, b, c } = Utils.countPixelsForBitmapPacket(data, printheadPixels ?? 0);
     const indexes: Uint8Array = ImageEncoder.indexPixels(data);
 
-    if (blackPixelCount > 6) {
-      throw new Error(`Black pixel count > 6 (${blackPixelCount})`);
+    if (total > 6) {
+      throw new Error(`Black pixel count > 6 (${total})`);
+    }
+
+    let header: number[] = [0, ...Utils.u16ToBytes(total)];
+
+    if (printheadPixels !== undefined) {
+      header = [a, b, c];
     }
 
     const packet = new NiimbotPacket(RequestCommandId.PrintBitmapRowIndexed, [
       ...Utils.u16ToBytes(pos),
-      0,
-      ...Utils.u16ToBytes(blackPixelCount),
+      ...header,
       repeats,
       ...indexes,
     ]);
@@ -273,13 +285,13 @@ export class PacketGenerator {
     return new NiimbotPacket(RequestCommandId.WriteRFID, data);
   }
 
-  public static writeImageData(image: EncodedImage): NiimbotPacket[] {
+  public static writeImageData(image: EncodedImage, printheadPixels?: number): NiimbotPacket[] {
     return image.rowsData.map((p: ImageRow) => {
       if (p.dataType === "pixels") {
         if (p.blackPixelsCount > 6) {
-          return this.printBitmapRow(p.rowNumber, p.repeat, p.rowData!);
+          return this.printBitmapRow(p.rowNumber, p.repeat, p.rowData!, printheadPixels);
         } else {
-          return this.printBitmapRowIndexed(p.rowNumber, p.repeat, p.rowData!);
+          return this.printBitmapRowIndexed(p.rowNumber, p.repeat, p.rowData!, printheadPixels);
         }
       } else {
         return this.printEmptySpace(p.rowNumber, p.repeat);
diff --git a/src/utils.ts b/src/utils.ts
index 4f05c3e..0d45a5e 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -15,7 +15,7 @@ export class Utils {
     if (!match) {
       return new Uint8Array();
     }
-    
+
     return new Uint8Array(
       match.map((h) => {
         return parseInt(h, 16);
@@ -39,23 +39,45 @@ export class Utils {
     return new TextDecoder().decode(arr);
   }
 
-  /** Count non-zero bits in the byte array */
-  public static countSetBits(arr: Uint8Array): number {
-    // not so efficient, but readable
-    let count: number = 0;
+  /**
+   * Count non-zero bits in the byte array
+   *
+   * Not efficient, but readable.
+   *
+   * The algorithm is obtained by reverse engineering and I don't understand what's going on here.
+   * 
+   * Sometimes these values match original packets, sometimes not.
+   **/
+  public static countPixelsForBitmapPacket(
+    arr: Uint8Array,
+    printheadSize: number
+  ): { total: number; a: number; b: number; c: number } {
+    let total: number = 0;
+    let a: number = 0;
+    let b: number = 0;
+    let c: number = 0;
+    let xPos: number = 0;
 
-    arr.forEach((value) => {
-      // shift until value becomes zero
-      while (value > 0) {
-        // check last bit
-        if ((value & 1) === 1) {
-          count++;
+    const printheadSizeDiv3: number = printheadSize / 3;
+
+    arr.forEach((value: number) => {
+      //for (let bitN = 0; bitN < 8; bitN++) {
+      for (let bitN: number = 7; bitN >= 0; bitN--) {
+        const isBlack: boolean = (value & (1 << bitN)) !== 0;
+        if (isBlack) {
+          if (xPos < printheadSizeDiv3) {
+            a++;
+          } else if (xPos < printheadSizeDiv3 * 2) {
+            b++;
+          } else {
+            c++;
+          }
+          total++;
         }
-        value >>= 1;
+        xPos++;
       }
     });
-
-    return count;
+    return { total, a, b, c };
   }
 
   /** Big endian  */