convertToBinary function

BitMatrix convertToBinary(
  1. Uint8List data,
  2. int width,
  3. int height, {
  4. bool returnInverted = false,
})

Convert a List of image bytes into a 2D matrix of bits representing black and white pixels

Implementation

BitMatrix convertToBinary(
  Uint8List data,
  int width,
  int height, {
  bool returnInverted = false,
}) {
  if (data.length != width * height * 4) {
    throw Exception("Malformed data passed to convertToBinary.");
  }

  // Convert image to grey scale
  final greyScalePixels = _GrayScaleMatrix(width, height);
  for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
      final r = data[((y * width + x) * 4) + 0];
      final g = data[((y * width + x) * 4) + 1];
      final b = data[((y * width + x) * 4) + 2];
      greyScalePixels.set(x, y, (0.2126 * r + 0.7152 * g + 0.0722 * b).toInt());
    }
  }
  final horizontalRegionCount = (width / REGION_SIZE).floor();
  final verticalRegionCount = (height / REGION_SIZE).floor();

  final blackPoints =
      _GrayScaleMatrix(horizontalRegionCount, verticalRegionCount);
  for (int verticalRegion = 0;
      verticalRegion < verticalRegionCount;
      verticalRegion++) {
    for (int hortizontalRegion = 0;
        hortizontalRegion < horizontalRegionCount;
        hortizontalRegion++) {
      double sum = 0;
      double min = double.infinity;
      double max = 0;
      for (int y = 0; y < REGION_SIZE; y++) {
        for (int x = 0; x < REGION_SIZE; x++) {
          final pixelLumosity = greyScalePixels
              .get(hortizontalRegion * REGION_SIZE + x,
                  verticalRegion * REGION_SIZE + y)
              .toDouble();
          sum += pixelLumosity;
          min = math.min(min, pixelLumosity);
          max = math.max(max, pixelLumosity);
        }
      }

      double average = sum / math.pow(REGION_SIZE, 2);
      if (max - min <= MIN_DYNAMIC_RANGE) {
        // If variation within the block is low, assume this is a block with only light or only
        // dark pixels. In that case we do not want to use the average, as it would divide this
        // low contrast area into black and white pixels, essentially creating data out of noise.
        //
        // Default the blackpoint for these blocks to be half the min - effectively white them out
        average = min / 2;

        if (verticalRegion > 0 && hortizontalRegion > 0) {
          // Correct the "white background" assumption for blocks that have neighbors by comparing
          // the pixels in this block to the previously calculated black points. This is based on
          // the fact that dark barcode symbology is always surrounded by some amount of light
          // background for which reasonable black point estimates were made. The bp estimated at
          // the boundaries is used for the interior.

          // The (min < bp) is arbitrary but works better than other heuristics that were tried.
          final averageNeighborBlackPoint = (blackPoints.get(
                      hortizontalRegion, verticalRegion - 1) +
                  (2 * blackPoints.get(hortizontalRegion - 1, verticalRegion)) +
                  blackPoints.get(hortizontalRegion - 1, verticalRegion - 1)) /
              4;
          if (min < averageNeighborBlackPoint) {
            average = averageNeighborBlackPoint;
          }
        }
      }
      blackPoints.set(hortizontalRegion, verticalRegion, average.toInt());
    }
  }

  final binarized = BitMatrix.createEmpty(width, height);

  for (int verticalRegion = 0;
      verticalRegion < verticalRegionCount;
      verticalRegion++) {
    for (int hortizontalRegion = 0;
        hortizontalRegion < horizontalRegionCount;
        hortizontalRegion++) {
      final left = _isBetween(hortizontalRegion, 2, horizontalRegionCount - 3);
      final top = _isBetween(verticalRegion, 2, verticalRegionCount - 3);
      int sum = 0;
      for (int xRegion = -2; xRegion <= 2; xRegion++) {
        for (int yRegion = -2; yRegion <= 2; yRegion++) {
          sum += blackPoints.get(left + xRegion, top + yRegion);
        }
      }
      final threshold = sum / 25;
      for (int xRegion = 0; xRegion < REGION_SIZE; xRegion++) {
        for (int yRegion = 0; yRegion < REGION_SIZE; yRegion++) {
          final x = hortizontalRegion * REGION_SIZE + xRegion;
          final y = verticalRegion * REGION_SIZE + yRegion;
          final lum = greyScalePixels.get(x, y);
          if (returnInverted) {
            binarized.set(x, y, !(lum <= threshold));
          } else {
            binarized.set(x, y, lum <= threshold);
          }
        }
      }
    }
  }
  return binarized;
}