findContours static method

List<List<Offset>> findContours(
  1. Uint8List edgeMap,
  2. int width,
  3. int height, {
  4. int minContourLength = 20,
})

Detects all contours in a binary edge map.

edgeMap — Binary image (0 or 255 per pixel) from Canny or binarization. width, height — Image dimensions. minContourLength — Minimum number of points in a valid contour.

Returns a list of contours, each represented as a list of Offset points.

Implementation

static List<List<Offset>> findContours(
  Uint8List edgeMap,
  int width,
  int height, {
  int minContourLength = 20,
}) {
  if (edgeMap.length < width * height || width < 3 || height < 3) {
    return [];
  }

  final contours = <List<Offset>>[];
  final visited = Uint8List(width * height);

  // Moore-Neighbor boundary tracing
  // 8-connectivity neighbor offsets (clockwise from East)
  const dx = [1, 1, 0, -1, -1, -1, 0, 1];
  const dy = [0, 1, 1, 1, 0, -1, -1, -1];

  for (int y = 1; y < height - 1; y++) {
    for (int x = 1; x < width - 1; x++) {
      final idx = y * width + x;

      // Look for unvisited edge pixel with non-edge pixel to its left
      // (contour entry point: transition from background to edge)
      if (edgeMap[idx] == 0 || visited[idx] != 0) continue;
      if (edgeMap[idx - 1] != 0) continue; // Must be a boundary pixel

      final contour = <Offset>[];
      int cx = x, cy = y;
      int startDir = 0; // Start searching from East

      // Trace the contour boundary
      int maxSteps = width * height; // Safety limit
      bool firstPixel = true;

      do {
        contour.add(Offset(cx.toDouble(), cy.toDouble()));
        visited[cy * width + cx] = 1;

        // Search 8-connected neighbors for next boundary pixel
        bool found = false;
        final searchStart = (startDir + 5) % 8; // Backtrack direction + 1

        for (int i = 0; i < 8; i++) {
          final dir = (searchStart + i) % 8;
          final nx = cx + dx[dir];
          final ny = cy + dy[dir];

          if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;

          if (edgeMap[ny * width + nx] != 0) {
            cx = nx;
            cy = ny;
            startDir = dir;
            found = true;
            break;
          }
        }

        if (!found) break;
        if (firstPixel) firstPixel = false;
        maxSteps--;
      } while ((cx != x || cy != y) && maxSteps > 0);

      if (contour.length >= minContourLength) {
        contours.add(contour);
      }
    }
  }

  // Sort by contour length (largest first)
  contours.sort((a, b) => b.length.compareTo(a.length));

  return contours;
}