evaluateTargetZoom method

double evaluateTargetZoom({
  1. required Rect targetBoundingBox,
  2. required Rect roiScanWindow,
  3. required Size frameSize,
})

Calculates desired target zoom level based on target bounding box area relative to scan window ROI.

Enhanced v3.0: Uses smooth interpolation with velocity limiting and hysteresis band to prevent zoom oscillation.

Implementation

double evaluateTargetZoom({
  required Rect targetBoundingBox,
  required Rect roiScanWindow,
  required Size frameSize,
}) {
  if (targetBoundingBox.isEmpty ||
      roiScanWindow.isEmpty ||
      roiScanWindow.width <= 0) {
    // No target detected — slowly zoom back towards minimum
    if (_currentZoom > minZoom + 0.1) {
      _targetZoom = math.max(minZoom, _currentZoom - 0.05);
      _currentZoom = _smoothStep(_currentZoom, _targetZoom, lerpFactor);
      _lastZoomChangeTime = DateTime.now();
      _stableFrameCount = 0;
      _isSettled = false;
    }
    return _currentZoom;
  }

  final targetArea = targetBoundingBox.width * targetBoundingBox.height;
  final roiArea = roiScanWindow.width * roiScanWindow.height;

  if (roiArea <= 0 || targetArea <= 0) return _currentZoom;

  final currentRatio = targetArea / roiArea;

  // Hysteresis: ignore small area changes within the dead zone
  if ((_lastAreaRatio - currentRatio).abs() < hysteresisBand &&
      _stableFrameCount > 0) {
    _stableFrameCount++;
    if (_stableFrameCount >= settlingFrameCount) {
      _isSettled = true;
    }
    return _currentZoom;
  }

  _lastAreaRatio = currentRatio;

  // Calculate desired zoom based on area ratio
  if (currentRatio < targetAreaFraction) {
    // Target is too small — zoom in
    final neededScale =
        math.sqrt(targetAreaFraction / math.max(0.01, currentRatio));
    _targetZoom = (_currentZoom * neededScale).clamp(minZoom, maxZoom);
  } else if (currentRatio > 0.6 && _currentZoom > minZoom) {
    // Target fills > 60% of ROI — zoom out
    _targetZoom = math.max(minZoom, _currentZoom / 1.15);
  } else {
    // Target is well-framed — maintain current zoom
    _stableFrameCount++;
    if (_stableFrameCount >= settlingFrameCount) {
      _isSettled = true;
    }
    return _currentZoom;
  }

  // Apply velocity limiting
  final delta = _targetZoom - _currentZoom;
  final clampedDelta = delta.clamp(-maxZoomVelocity, maxZoomVelocity);
  final velocityLimitedTarget = _currentZoom + clampedDelta;

  // Smooth ease-out interpolation towards target
  _currentZoom = _smoothStep(
    _currentZoom,
    velocityLimitedTarget,
    lerpFactor,
  );

  _lastZoomChangeTime = DateTime.now();
  _stableFrameCount = 0;
  _isSettled = false;

  return _currentZoom;
}