clampRectToImageBounds static method
将裁剪框限制在旋转后图片的实际显示边界内 考虑旋转后图片边界是斜边的情况
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 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;
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);
}