artifactValleysOffsets function

List<int> artifactValleysOffsets(
  1. Artifact artifact
)

Returns a list of column indices where the artifact should be split

This method analyzes the horizontal histogram of the artifact to identify valleys (columns with fewer pixels) that are good candidates for splitting.

If all columns have identical values (no valleys or peaks), returns an empty list indicating no splitting is needed.

Returns: A list of column indices where splits should occur.

Implementation

List<int> artifactValleysOffsets(final Artifact artifact) {
  final List<int> peaksAndValleys = artifact.getHistogramHorizontal();

  // Check if all columns have identical values
  final bool allIdentical = peaksAndValleys.every(
    (value) => value == peaksAndValleys[0],
  );
  if (allIdentical) {
    // no valleys
    return [];
  }

  final List<int> offsets = [];

  // Calculate a more appropriate threshold for large artifacts
  final int threshold = calculateThreshold(peaksAndValleys);

  if (threshold >= 0) {
    // Find columns where the pixel count is below the threshold
    final List<List<int>> gaps = [];
    List<int> currentGap = [];

    // Identify gaps (consecutive columns below threshold)
    for (int i = 0; i < peaksAndValleys.length; i++) {
      if (peaksAndValleys[i] <= threshold) {
        currentGap.add(i);
      } else if (currentGap.isNotEmpty) {
        gaps.add(List.from(currentGap));
        currentGap = [];
      }
    }

    // Add the last gap if it exists
    if (currentGap.isNotEmpty) {
      gaps.add(currentGap);
    }

    // Filter out gaps that are at the edges of the artifact
    // These are likely serifs or other character features, not actual gaps between characters
    gaps.removeWhere((gap) {
      // Remove gaps that start at column 0 (left edge)
      if (gap.first == 0) {
        return true;
      }

      // Remove gaps that end at the last column (right edge)
      if (gap.last == peaksAndValleys.length - 1) {
        return true;
      }

      // Keep all other gaps
      return false;
    });

    // Sort the gaps by position (ascending) to maintain left-to-right order
    gaps.sort((a, b) => a[0].compareTo(b[0]));

    // For each gap, use the middle of the gap as the split column

    for (final List<int> gap in gaps) {
      if (gap.isNotEmpty) {
        // Calculate the middle point of the gap
        final int splitPoint = gap.first + (gap.length ~/ 2);
        offsets.add(splitPoint);
      }
    }
  }

  return offsets;
}