TypeScript: a grayscale image class with threshold and flip

A grayscale image is a grid of brightness values. Storing it in one flat Uint8Array of width × height bytes is compact and fast, and the pixel at column x and row y lives at index y × width + x. A class hides this index arithmetic and checks that coordinates are inside the image.

TypeScript
class GrayImage {
  readonly width: number;
  readonly height: number;
  private data: Uint8Array;

  constructor(width: number, height: number, data?: Uint8Array) {
    this.width = width;
    this.height = height;
    this.data = data ?? new Uint8Array(width * height);
  }

  static fromRows(rows: number[][]): GrayImage {
    const image = new GrayImage(rows[0].length, rows.length);
    rows.forEach((row, y) => row.forEach((value, x) => image.set(x, y, value)));
    return image;
  }

  get(x: number, y: number): number {
    if (x < 0 || y < 0 || x >= this.width || y >= this.height) {
      throw new RangeError("pixel (" + x + "," + y + ") is outside the image");
    }
    return this.data[y * this.width + x];
  }

  set(x: number, y: number, value: number): void {
    this.get(x, y);
    this.data[y * this.width + x] = value;
  }

  threshold(cutoff: number): GrayImage {
    return new GrayImage(this.width, this.height, this.data.map((v) => (v > cutoff ? 255 : 0)));
  }

  flipHorizontal(): GrayImage {
    const out = new GrayImage(this.width, this.height);
    for (let y = 0; y < this.height; y++) {
      for (let x = 0; x < this.width; x++) {
        out.set(this.width - 1 - x, y, this.get(x, y));
      }
    }
    return out;
  }

  toText(): string {
    const lines: string[] = [];
    for (let y = 0; y < this.height; y++) {
      let line = "";
      for (let x = 0; x < this.width; x++) {
        line += this.get(x, y) > 127 ? "#" : ".";
      }
      lines.push(line);
    }
    return lines.join("\n");
  }
}

const image = GrayImage.fromRows([
  [10, 200, 30, 220],
  [240, 20, 250, 40],
  [60, 180, 70, 190],
]);
console.log(image.threshold(128).toText());
console.log("--");
console.log(image.threshold(128).flipHorizontal().toText());
try {
  image.get(4, 0);
} catch (error) {
  console.log((error as Error).message);
}
Read the full lesson