clampRectToImageBounds static method

Rect clampRectToImageBounds({
  1. required Rect rect,
  2. required Rect imageBounds,
  3. required double rotationAngle,
  4. required Size canvasSize,
  5. required double scale,
  6. required Image image,
})

将裁剪框限制在旋转后图片的实际显示边界内 考虑旋转后图片边界是斜边的情况

Implementation

static Rect clampRectToImageBounds({
  required Rect rect,
  required Rect imageBounds,
  required double rotationAngle,
  required Size canvasSize,
  required double scale,
  required ui.Image image,
}) {
  // 如果图片没有旋转,使用简单的轴对齐边界检查
  if (rotationAngle == 0.0) {
    double width = rect.width.clamp(minCropSize, imageBounds.width);
    double height = rect.height.clamp(minCropSize, imageBounds.height);
    double left = rect.left.clamp(imageBounds.left, imageBounds.right - rect.width);
    double top = rect.top.clamp(imageBounds.top, imageBounds.bottom - rect.height);

    if (left + width > imageBounds.right) {
      left = imageBounds.right - width;
    }
    if (top + height > imageBounds.bottom) {
      top = imageBounds.bottom - height;
    }
    if (left < imageBounds.left) left = imageBounds.left;
    if (top < imageBounds.top) top = imageBounds.top;

    return Rect.fromLTWH(left, top, width, height);
  }

  // 图片有旋转,需要检查裁剪框的四个角是否都在图片内
  // 辅助函数:检查给定尺寸和位置的裁剪框是否完全在图片内
  bool isCropRectInside(Rect testRect) {
    final List<Offset> corners = [
      Offset(testRect.left, testRect.top),
      Offset(testRect.right, testRect.top),
      Offset(testRect.left, testRect.bottom),
      Offset(testRect.right, testRect.bottom),
    ];
    return corners.every((corner) => CoordinateTransformer.isPointInsideRotatedImage(
      screenPoint: corner,
      canvasSize: canvasSize,
      rotationAngle: rotationAngle,
      scale: scale,
      image: image,
    ));
  }

  // 先尝试直接使用传入的 rect,如果完全在图片内,直接返回
  if (isCropRectInside(rect)) {
    return rect;
  }

  // 如果不在图片内,先尝试限制在 imageBounds 内(轴对齐边界框)
  double left = rect.left.clamp(imageBounds.left, imageBounds.right - rect.width);
  double top = rect.top.clamp(imageBounds.top, imageBounds.bottom - rect.height);
  double width = rect.width.clamp(minCropSize, imageBounds.width);
  double height = rect.height.clamp(minCropSize, imageBounds.height);

  if (left + width > imageBounds.right) {
    left = imageBounds.right - width;
  }
  if (top + height > imageBounds.bottom) {
    top = imageBounds.bottom - height;
  }
  if (left < imageBounds.left) left = imageBounds.left;
  if (top < imageBounds.top) top = imageBounds.top;

  Rect clampedRect = Rect.fromLTWH(left, top, width, height);

  // 检查限制后的矩形是否在图片内
  if (isCropRectInside(clampedRect)) {
    return clampedRect;
  }

  // 如果仍然不在图片内,需要调整位置和尺寸
  // 保持裁剪框中心不变,使用二分法找到最大可用的尺寸
  final double centerX = clampedRect.left + clampedRect.width / 2;
  final double centerY = clampedRect.top + clampedRect.height / 2;

  double minWidth = minCropSize;
  double minHeight = minCropSize;
  double maxWidth = clampedRect.width;
  double maxHeight = clampedRect.height;

  // 二分查找最大可用尺寸
  const int maxIterations = 30;
  for (int i = 0; i < maxIterations; i++) {
    final double testWidth = (minWidth + maxWidth) / 2;
    final double testHeight = (minHeight + maxHeight) / 2;

    final Rect testRect = Rect.fromCenter(
      center: Offset(centerX, centerY),
      width: testWidth,
      height: testHeight,
    );

    if (isCropRectInside(testRect)) {
      // 如果完全在图片内,尝试增大尺寸
      minWidth = testWidth;
      minHeight = testHeight;
    } else {
      // 如果有角在图片外,缩小尺寸
      maxWidth = testWidth;
      maxHeight = testHeight;
    }

    // 如果尺寸差异很小,停止迭代
    if ((maxWidth - minWidth) < 1.0 && (maxHeight - minHeight) < 1.0) {
      break;
    }
  }

  width = minWidth;
  height = minHeight;
  left = centerX - width / 2;
  top = centerY - height / 2;

  // 如果当前尺寸和位置仍然有角在图片外,尝试微调位置
  Rect currentRect = Rect.fromLTWH(left, top, width, height);
  if (!isCropRectInside(currentRect)) {
    // 尝试在图片边界框内搜索最佳位置
    final double searchStep = math.min(width, height) / 5;
    double bestLeft = left;
    double bestTop = top;
    int bestInsideCount = 0;

    for (double dx = -imageBounds.width / 2; dx <= imageBounds.width / 2; dx += searchStep) {
      for (double dy = -imageBounds.height / 2; dy <= imageBounds.height / 2; dy += searchStep) {
        final double testLeft = imageBounds.center.dx - width / 2 + dx;
        final double testTop = imageBounds.center.dy - height / 2 + dy;
        final Rect testRect = Rect.fromLTWH(testLeft, testTop, width, height);

        if (isCropRectInside(testRect)) {
          // 找到完全在图片内的位置
          return testRect;
        }

        // 计算在图片内的角的数量
        final List<Offset> corners = [
          Offset(testLeft, testTop),
          Offset(testLeft + width, testTop),
          Offset(testLeft, testTop + height),
          Offset(testLeft + width, testTop + height),
        ];
        final int insideCount = corners.where((corner) => CoordinateTransformer.isPointInsideRotatedImage(
          screenPoint: corner,
          canvasSize: canvasSize,
          rotationAngle: rotationAngle,
          scale: scale,
          image: image,
        )).length;

        if (insideCount > bestInsideCount) {
          bestInsideCount = insideCount;
          bestLeft = testLeft;
          bestTop = testTop;
        }
      }
    }

    // 如果找到了更好的位置,使用它
    if (bestInsideCount > 0) {
      left = bestLeft;
      top = bestTop;
    } else {
      // 如果找不到合适的位置,进一步缩小尺寸
      width = math.min(width, imageBounds.width * 0.8);
      height = math.min(height, imageBounds.height * 0.8);
      width = width.clamp(minCropSize, imageBounds.width);
      height = height.clamp(minCropSize, imageBounds.height);
      left = imageBounds.center.dx - width / 2;
      top = imageBounds.center.dy - height / 2;
    }
  }

  return Rect.fromLTWH(left, top, width, height);
}