feat(scanQRcode): add center crop zoom and bilinear sampling for preview

新增扫码预览的中心裁切放大功能,支持自定义裁切比例,同时使用双线性插值提升画面缩放质量
This commit is contained in:
“虞渠成” 2026-07-23 16:05:02 +08:00
parent 807f940cfb
commit 15c502c959

View File

@ -36,6 +36,11 @@ namespace Kill.UI.Pages
public int scanResolutionWidth = 1080;
[Tooltip("扫描处理线程数")]
public bool useAsyncScan = true;
[Header("中心裁切放大")]
[Tooltip("截取画面中心区域的比例0-1值越小放越大推荐0.55~0.7")]
[Range(0.3f, 1.0f)]
public float centerCropRatio = 0.65f;
[Header("自动对焦")]
[Tooltip("启用自动对焦")]
@ -264,42 +269,67 @@ namespace Kill.UI.Pages
/// <summary>
/// 从相机画面裁切并旋转,生成预览画面
/// 支持中心裁切放大:只截取画面中心区域,放大到预览尺寸
/// </summary>
private void CutAndRotateCameraFrame(int camW, int camH, int targetW, int targetH)
{
// 相机横向 1920x1080需要顺时针旋转90度变成 1080x1920
// 预览框横向 1080x680
// 从旋转后的 1080x1920 中截取中间 1080x680 区域
// 从旋转后的 1080x1920 中截取中间区域并放大
// 计算裁切区域(在原始相机画面上的坐标)
// 旋转后高度是 camW(1920),需要截取 targetH(680) 高度
// 从中间截取起始Y = (1920 - 680) / 2 = 620
int cropY = (camW - targetH) / 2; // 620
// 计算中心裁切区域大小(基于 centerCropRatio
int sourceCropH = Mathf.Max(1, Mathf.RoundToInt(camW * centerCropRatio));
int sourceCropW = Mathf.Max(1, Mathf.RoundToInt(camH * centerCropRatio));
int cropY = (camW - sourceCropH) / 2;
int cropX = (camH - sourceCropW) / 2;
// 计算缩放比例
float scaleY = (float)sourceCropH / targetH;
float scaleX = (float)sourceCropW / targetW;
// 获取相机像素数据
Color32[] cameraPixels = webCamTexture.GetPixels32();
Color32[] targetPixels = new Color32[targetW * targetH];
// 顺时针旋转90度并裁切
// 原始坐标 (x, y) -> 顺时针旋转90度后 (y, camW-1-x)
// 目标 (x, y) 对应源:
// srcX = cropY + y (在裁切区域内纵向移动)
// srcY = x (横向直接对应,不翻转)
// 顺时针旋转90度 + 中心裁切 + 缩放
// 目标像素 (x, y) 对应源坐标:
// srcX = cropY + (targetH-1-y) * scaleY (裁切区域内纵向缩放)
// srcY = cropX + x * scaleX (裁切区域内横向缩放)
for (int y = 0; y < targetH; y++)
{
for (int x = 0; x < targetW; x++)
{
// 目标像素在预览图中的位置 (x, y)
// 对应原始相机中的位置
int srcX = cropY + (targetH - 1 - y); // 从裁切区域底部开始,向上取像素(修复上下颠倒)
int srcY = x; // 横向直接对应
float srcX = cropY + (targetH - 1 - y) * scaleY;
float srcY = cropX + x * scaleX;
if (srcX >= 0 && srcX < camW && srcY >= 0 && srcY < camH)
{
int srcIndex = srcY * camW + srcX;
int dstIndex = y * targetW + x;
targetPixels[dstIndex] = cameraPixels[srcIndex];
}
// 双线性插值采样
int sx = (int)srcX;
int sy = (int)srcY;
float fx = srcX - sx;
float fy = srcY - sy;
int sx1 = Mathf.Min(sx + 1, camW - 1);
int sy1 = Mathf.Min(sy + 1, camH - 1);
int idx00 = sy * camW + sx;
int idx10 = sy * camW + sx1;
int idx01 = sy1 * camW + sx;
int idx11 = sy1 * camW + sx1;
Color32 c00 = cameraPixels[idx00];
Color32 c10 = cameraPixels[idx10];
Color32 c01 = cameraPixels[idx01];
Color32 c11 = cameraPixels[idx11];
float r = (1-fx)*(1-fy)*c00.r + fx*(1-fy)*c10.r + (1-fx)*fy*c01.r + fx*fy*c11.r;
float g = (1-fx)*(1-fy)*c00.g + fx*(1-fy)*c10.g + (1-fx)*fy*c01.g + fx*fy*c11.g;
float b = (1-fx)*(1-fy)*c00.b + fx*(1-fy)*c10.b + (1-fx)*fy*c01.b + fx*fy*c11.b;
targetPixels[y * targetW + x] = new Color32(
(byte)Mathf.Clamp(r, 0, 255),
(byte)Mathf.Clamp(g, 0, 255),
(byte)Mathf.Clamp(b, 0, 255),
255
);
}
}