artifactValleysOffsets static method
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
static List<int> artifactValleysOffsets(
final Artifact artifact, {
bool allowSoftValleys = true,
}) {
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);
final List<List<int>> gaps = [];
if (threshold != _invalidThreshold) {
// Find columns where the pixel count is below the threshold
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);
}
}
// Add soft valleys for touching letters (no empty column),
// but only when the valley is significantly lower than nearby peaks.
if (allowSoftValleys) {
final List<int> softValleys = _findSoftValleySplits(peaksAndValleys);
for (final int index in softValleys) {
gaps.add([index]);
}
}
// 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) {
if (gap.first == 0) {
return true;
}
if (gap.last == peaksAndValleys.length - 1) {
return true;
}
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) {
final int splitPoint = gap.first + (gap.length ~/ 2);
offsets.add(splitPoint);
}
}
return _dedupeSplitOffsets(offsets);
}