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

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