597 lines
20 KiB
C#
597 lines
20 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.EventSystems;
|
||
using System.Collections;
|
||
using System.IO;
|
||
namespace Kill.UI
|
||
{
|
||
|
||
public class AvatarSelection : MonoBehaviour
|
||
{
|
||
[Header("UI Elements")]
|
||
public Button selectPhotoButton;
|
||
public RawImage avatarPreview;
|
||
public GameObject cropCanvas; // 用于裁剪的全屏Canvas
|
||
public RawImage cropImagePreview;
|
||
public RectTransform cropArea;
|
||
public Button confirmCropButton;
|
||
public Button cancelCropButton;
|
||
|
||
[Header("Avatar Settings")]
|
||
[Tooltip("生成头像的尺寸(宽高相等)")]
|
||
public int avatarSize = 100;
|
||
|
||
private Texture2D selectedImage;
|
||
private Vector2 startPoint;
|
||
private Vector2 endPoint;
|
||
private bool isSelecting = false;
|
||
private RectTransform cropImageRectTransform;
|
||
private CropAreaHandler cropAreaHandler;
|
||
|
||
void Start()
|
||
{
|
||
// 初始化按钮事件
|
||
if (selectPhotoButton != null)
|
||
selectPhotoButton.onClick.AddListener(SelectPhotoFromGallery);
|
||
|
||
if (confirmCropButton != null)
|
||
confirmCropButton.onClick.AddListener(ConfirmCrop);
|
||
|
||
if (cancelCropButton != null)
|
||
cancelCropButton.onClick.AddListener(CancelCrop);
|
||
|
||
// 隐藏裁剪界面
|
||
if (cropCanvas != null)
|
||
cropCanvas.SetActive(false);
|
||
|
||
// 获取裁剪图像的RectTransform
|
||
if (cropImagePreview != null)
|
||
cropImageRectTransform = cropImagePreview.GetComponent<RectTransform>();
|
||
|
||
// 为裁剪区域添加处理组件
|
||
if (cropArea != null)
|
||
{
|
||
cropAreaHandler = cropArea.gameObject.AddComponent<CropAreaHandler>();
|
||
cropAreaHandler.Initialize(this, cropImageRectTransform);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从相册选择照片
|
||
/// </summary>
|
||
public void SelectPhotoFromGallery()
|
||
{
|
||
#if UNITY_EDITOR
|
||
// 在编辑器中测试时,加载一个默认图片
|
||
string testImagePath = UnityEditor.EditorUtility.OpenFilePanel("Select Image", "", "jpg,png,jpeg");
|
||
if (!string.IsNullOrEmpty(testImagePath))
|
||
{
|
||
byte[] fileData = File.ReadAllBytes(testImagePath);
|
||
selectedImage = new Texture2D(2, 2);
|
||
selectedImage.LoadImage(fileData);
|
||
ShowCropInterface(selectedImage);
|
||
}
|
||
|
||
#else
|
||
// 使用NativeGallery插件从相册选择图片
|
||
bool permission = NativeGallery.CheckPermission(NativeGallery.PermissionType.Read, NativeGallery.MediaType.Image);
|
||
if (permission)
|
||
{
|
||
PickImage();
|
||
}
|
||
else
|
||
{
|
||
NativeGallery.RequestPermissionAsync((perm) =>
|
||
{
|
||
if (perm == NativeGallery.Permission.Granted)
|
||
{
|
||
PickImage();
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("没有获得相册访问权限");
|
||
}
|
||
}, NativeGallery.PermissionType.Read, NativeGallery.MediaType.Image);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
private void PickImage()
|
||
{
|
||
NativeGallery.GetImageFromGallery((path) =>
|
||
{
|
||
if (path != null)
|
||
{
|
||
// 加载选中的图片,并确保纹理是可读的
|
||
selectedImage = NativeGallery.LoadImageAtPath(path, -1, false); // 设置 markTextureNonReadable 为 false
|
||
if (selectedImage != null)
|
||
{
|
||
ShowCropInterface(selectedImage);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError("无法加载选定的图片");
|
||
}
|
||
}
|
||
}, "选择一张图片");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 显示裁剪界面
|
||
/// </summary>
|
||
/// <param name="image">要裁剪的图片</param>
|
||
private void ShowCropInterface(Texture2D image)
|
||
{
|
||
if (cropCanvas != null && cropImagePreview != null)
|
||
{
|
||
cropImagePreview.texture = image;
|
||
cropCanvas.SetActive(true);
|
||
|
||
// 调整图片显示大小以适应屏幕并保持原图比例
|
||
FitImageToScreen(image);
|
||
|
||
// 重置裁剪区域
|
||
if (cropAreaHandler != null)
|
||
{
|
||
cropAreaHandler.ResetCropArea();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调整图片大小以适应屏幕并保持原图比例,同时确保不超过cropCanvas的大小
|
||
/// </summary>
|
||
/// <param name="image">要调整的图片</param>
|
||
private void FitImageToScreen(Texture2D image)
|
||
{
|
||
if (cropImageRectTransform == null) return;
|
||
|
||
// 获取cropCanvas的RectTransform
|
||
RectTransform canvasRect = cropCanvas.GetComponent<RectTransform>();
|
||
if (canvasRect == null) return;
|
||
|
||
// 计算可用空间(考虑到一些边距)
|
||
float maxWidth = canvasRect.rect.width * 0.9f;
|
||
float maxHeight = canvasRect.rect.height * 0.7f;
|
||
|
||
// 计算图片比例
|
||
float imageRatio = (float)image.width / image.height;
|
||
|
||
// 根据图片比例调整尺寸
|
||
float width, height;
|
||
if (imageRatio > 1) // 宽图
|
||
{
|
||
width = Mathf.Min(maxWidth, image.width);
|
||
height = width / imageRatio;
|
||
|
||
// 检查高度是否超出限制
|
||
if (height > maxHeight)
|
||
{
|
||
height = maxHeight;
|
||
width = height * imageRatio;
|
||
}
|
||
}
|
||
else // 高图或正方形
|
||
{
|
||
height = Mathf.Min(maxHeight, image.height);
|
||
width = height * imageRatio;
|
||
|
||
// 检查宽度是否超出限制
|
||
if (width > maxWidth)
|
||
{
|
||
width = maxWidth;
|
||
height = width / imageRatio;
|
||
}
|
||
}
|
||
|
||
// 设置尺寸
|
||
cropImageRectTransform.sizeDelta = new Vector2(width, height);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确认裁剪
|
||
/// </summary>
|
||
public void ConfirmCrop()
|
||
{
|
||
if (selectedImage == null || cropArea == null || cropImageRectTransform == null)
|
||
{
|
||
CancelCrop();
|
||
return;
|
||
}
|
||
|
||
// 计算在原图中的裁剪坐标
|
||
Rect cropRect = CalculateCropRect();
|
||
|
||
if (cropRect.width <= 0 || cropRect.height <= 0)
|
||
{
|
||
CancelCrop();
|
||
return;
|
||
}
|
||
|
||
// 创建新的指定尺寸的纹理
|
||
Texture2D croppedTexture = CropTexture(selectedImage, cropRect);
|
||
|
||
// 检查裁剪纹理是否小于目标尺寸,并决定使用哪种缩放算法
|
||
Texture2D resizedTexture;
|
||
if (croppedTexture.width < avatarSize || croppedTexture.height < avatarSize)
|
||
{
|
||
// 如果裁剪区域小于目标尺寸,使用高质量的双线性插值算法
|
||
resizedTexture = ResizeTextureWithBilinear(croppedTexture, avatarSize, avatarSize);
|
||
}
|
||
else
|
||
{
|
||
// 否则使用普通缩放
|
||
resizedTexture = ResizeTexture(croppedTexture, avatarSize, avatarSize);
|
||
}
|
||
|
||
// 显示结果
|
||
if (avatarPreview != null)
|
||
{
|
||
avatarPreview.texture = resizedTexture;
|
||
}
|
||
|
||
// 隐藏裁剪界面
|
||
if (cropCanvas != null)
|
||
{
|
||
cropCanvas.SetActive(false);
|
||
}
|
||
|
||
Debug.Log("头像裁剪完成,尺寸: " + avatarSize + "x" + avatarSize);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算在原图中的裁剪区域
|
||
/// </summary>
|
||
/// <returns>裁剪区域</returns>
|
||
private Rect CalculateCropRect()
|
||
{
|
||
if (cropArea == null || cropImageRectTransform == null || selectedImage == null)
|
||
return new Rect(0, 0, 0, 0);
|
||
|
||
// 获取裁剪区域在图片中的相对位置
|
||
Rect cropAreaRect = cropAreaHandler.GetCropRect();
|
||
|
||
// 获取图片显示区域的世界角点
|
||
Vector3[] imageCorners = new Vector3[4];
|
||
cropImageRectTransform.GetWorldCorners(imageCorners);
|
||
|
||
// 获取裁剪区域的世界角点
|
||
Vector3[] cropAreaCorners = new Vector3[4];
|
||
cropArea.GetWorldCorners(cropAreaCorners);
|
||
|
||
// 计算裁剪区域在图片中的UV坐标
|
||
float uMin = Mathf.InverseLerp(imageCorners[0].x, imageCorners[2].x, cropAreaCorners[0].x);
|
||
float uMax = Mathf.InverseLerp(imageCorners[0].x, imageCorners[2].x, cropAreaCorners[2].x);
|
||
float vMin = Mathf.InverseLerp(imageCorners[0].y, imageCorners[2].y, cropAreaCorners[0].y);
|
||
float vMax = Mathf.InverseLerp(imageCorners[0].y, imageCorners[2].y, cropAreaCorners[2].y);
|
||
|
||
// 转换为像素坐标
|
||
int x = Mathf.RoundToInt(uMin * selectedImage.width);
|
||
int y = Mathf.RoundToInt(vMin * selectedImage.height);
|
||
int width = Mathf.RoundToInt((uMax - uMin) * selectedImage.width);
|
||
int height = Mathf.RoundToInt((vMax - vMin) * selectedImage.height);
|
||
|
||
// 确保不超过图片边界
|
||
x = Mathf.Clamp(x, 0, selectedImage.width);
|
||
y = Mathf.Clamp(y, 0, selectedImage.height);
|
||
width = Mathf.Clamp(width, 0, selectedImage.width - x);
|
||
height = Mathf.Clamp(height, 0, selectedImage.height - y);
|
||
|
||
return new Rect(x, y, width, height);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 裁剪纹理
|
||
/// </summary>
|
||
/// <param name="texture">原纹理</param>
|
||
/// <param name="rect">裁剪区域</param>
|
||
/// <returns>裁剪后的纹理</returns>
|
||
private Texture2D CropTexture(Texture2D texture, Rect rect)
|
||
{
|
||
int x = Mathf.RoundToInt(rect.x);
|
||
int y = Mathf.RoundToInt(rect.y);
|
||
int width = Mathf.RoundToInt(rect.width);
|
||
int height = Mathf.RoundToInt(rect.height);
|
||
|
||
// 确保是正方形
|
||
int size = Mathf.Min(width, height);
|
||
|
||
Color[] pixels = texture.GetPixels(x, y, size, size);
|
||
Texture2D croppedTexture = new Texture2D(size, size, TextureFormat.RGBA32, false);
|
||
croppedTexture.SetPixels(pixels);
|
||
croppedTexture.Apply();
|
||
|
||
return croppedTexture;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 调整纹理大小(普通质量)
|
||
/// </summary>
|
||
/// <param name="texture">原纹理</param>
|
||
/// <param name="targetWidth">目标宽度</param>
|
||
/// <param name="targetHeight">目标高度</param>
|
||
/// <returns>调整大小后的纹理</returns>
|
||
private Texture2D ResizeTexture(Texture2D texture, int targetWidth, int targetHeight)
|
||
{
|
||
// 创建渲染纹理
|
||
RenderTexture renderTexture = new RenderTexture(targetWidth, targetHeight, 24);
|
||
RenderTexture.active = renderTexture;
|
||
|
||
// 绘制原纹理到渲染纹理
|
||
Graphics.Blit(texture, renderTexture);
|
||
|
||
// 从渲染纹理创建新的纹理
|
||
Texture2D resizedTexture = new Texture2D(targetWidth, targetHeight, TextureFormat.RGBA32, false);
|
||
resizedTexture.ReadPixels(new Rect(0, 0, targetWidth, targetHeight), 0, 0);
|
||
resizedTexture.Apply();
|
||
|
||
// 清理
|
||
RenderTexture.active = null;
|
||
|
||
return resizedTexture;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用双线性插值算法调整纹理大小(高质量)
|
||
/// 当源纹理小于目标尺寸时使用,以获得更好的图像质量
|
||
/// </summary>
|
||
/// <param name="texture">原纹理</param>
|
||
/// <param name="targetWidth">目标宽度</param>
|
||
/// <param name="targetHeight">目标高度</param>
|
||
/// <returns>调整大小后的纹理</returns>
|
||
private Texture2D ResizeTextureWithBilinear(Texture2D texture, int targetWidth, int targetHeight)
|
||
{
|
||
Color[] originalPixels = texture.GetPixels();
|
||
int originalWidth = texture.width;
|
||
int originalHeight = texture.height;
|
||
|
||
Color[] newPixels = new Color[targetWidth * targetHeight];
|
||
|
||
float xRatio = (float)originalWidth / targetWidth;
|
||
float yRatio = (float)originalHeight / targetHeight;
|
||
|
||
for (int y = 0; y < targetHeight; y++)
|
||
{
|
||
for (int x = 0; x < targetWidth; x++)
|
||
{
|
||
// 计算在原始图像中的位置
|
||
float px = x * xRatio;
|
||
float py = y * yRatio;
|
||
|
||
// 获取相邻的四个像素
|
||
int x1 = (int)Mathf.Floor(px);
|
||
int y1 = (int)Mathf.Floor(py);
|
||
int x2 = Mathf.Min(x1 + 1, originalWidth - 1);
|
||
int y2 = Mathf.Min(y1 + 1, originalHeight - 1);
|
||
|
||
// 计算权重
|
||
float fx = px - x1;
|
||
float fy = py - y1;
|
||
float w1 = (1 - fx) * (1 - fy);
|
||
float w2 = fx * (1 - fy);
|
||
float w3 = (1 - fx) * fy;
|
||
float w4 = fx * fy;
|
||
|
||
// 获取四个像素的颜色
|
||
Color c1 = originalPixels[y1 * originalWidth + x1];
|
||
Color c2 = originalPixels[y1 * originalWidth + x2];
|
||
Color c3 = originalPixels[y2 * originalWidth + x1];
|
||
Color c4 = originalPixels[y2 * originalWidth + x2];
|
||
|
||
// 双线性插值
|
||
Color finalColor = c1 * w1 + c2 * w2 + c3 * w3 + c4 * w4;
|
||
newPixels[y * targetWidth + x] = finalColor;
|
||
}
|
||
}
|
||
|
||
Texture2D resizedTexture = new Texture2D(targetWidth, targetHeight, TextureFormat.RGBA32, false);
|
||
resizedTexture.SetPixels(newPixels);
|
||
resizedTexture.Apply();
|
||
|
||
return resizedTexture;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消裁剪
|
||
/// </summary>
|
||
public void CancelCrop()
|
||
{
|
||
if (cropCanvas != null)
|
||
{
|
||
cropCanvas.SetActive(false);
|
||
}
|
||
|
||
Debug.Log("取消裁剪");
|
||
}
|
||
|
||
public RectTransform GetCropImageRectTransform()
|
||
{
|
||
return cropImageRectTransform;
|
||
}
|
||
|
||
public RectTransform GetCropCanvasRectTransform()
|
||
{
|
||
if (cropCanvas != null)
|
||
{
|
||
return cropCanvas.GetComponent<RectTransform>();
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 裁剪区域处理组件
|
||
/// </summary>
|
||
public class CropAreaHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler
|
||
{
|
||
private AvatarSelection avatarSelection;
|
||
private RectTransform cropArea;
|
||
private RectTransform cropImageRectTransform;
|
||
private Vector2 lastPointerPosition;
|
||
private Vector2 dragStartPosition;
|
||
private float MIN_CROP_SIZE = 50f;
|
||
private bool isScaling = false; // 添加标志位,用于在缩放时屏蔽移动操作
|
||
|
||
public void Initialize(AvatarSelection avatarSelection, RectTransform cropImageRectTransform)
|
||
{
|
||
this.avatarSelection = avatarSelection;
|
||
this.cropImageRectTransform = cropImageRectTransform;
|
||
cropArea = GetComponent<RectTransform>();
|
||
}
|
||
|
||
public void ResetCropArea()
|
||
{
|
||
if (cropArea == null || cropImageRectTransform == null) return;
|
||
|
||
// 设置默认裁剪区域为图片显示区域的正方形,并确保不小于图片尺寸的一半
|
||
float defaultSize = Mathf.Min(cropImageRectTransform.rect.width, cropImageRectTransform.rect.height);
|
||
MIN_CROP_SIZE =defaultSize/2.0f;
|
||
cropArea.sizeDelta = new Vector2(defaultSize, defaultSize);
|
||
cropArea.anchoredPosition = Vector2.zero;
|
||
}
|
||
|
||
public Rect GetCropRect()
|
||
{
|
||
if (cropArea == null) return new Rect(0, 0, 0, 0);
|
||
|
||
// 获取世界角点以获得准确的边界
|
||
Vector3[] corners = new Vector3[4];
|
||
cropArea.GetWorldCorners(corners);
|
||
|
||
// 返回基于屏幕坐标的矩形
|
||
return new Rect(
|
||
corners[0].x,
|
||
corners[0].y,
|
||
corners[2].x - corners[0].x,
|
||
corners[2].y - corners[0].y
|
||
);
|
||
}
|
||
|
||
public void OnPointerDown(PointerEventData eventData)
|
||
{
|
||
// 只有在非缩放状态下才允许移动
|
||
if (!isScaling)
|
||
{
|
||
lastPointerPosition = eventData.position;
|
||
dragStartPosition = cropArea.anchoredPosition;
|
||
}
|
||
}
|
||
|
||
public void OnBeginDrag(PointerEventData eventData)
|
||
{
|
||
// 只有在非缩放状态下才允许移动
|
||
if (!isScaling)
|
||
{
|
||
lastPointerPosition = eventData.position;
|
||
}
|
||
}
|
||
|
||
public void OnDrag(PointerEventData eventData)
|
||
{
|
||
// 只有在非缩放状态下才允许移动
|
||
if (cropArea == null || cropImageRectTransform == null || isScaling) return;
|
||
|
||
Vector2 currentPointerPosition = eventData.position;
|
||
Vector2 deltaPosition = currentPointerPosition - lastPointerPosition;
|
||
|
||
// 移动裁剪区域
|
||
cropArea.anchoredPosition += deltaPosition;
|
||
|
||
// 限制裁剪区域在图片范围内
|
||
ConstrainCropArea();
|
||
|
||
lastPointerPosition = currentPointerPosition;
|
||
}
|
||
|
||
public void OnEndDrag(PointerEventData eventData)
|
||
{
|
||
// 只有在非缩放状态下才处理拖拽结束
|
||
if (!isScaling)
|
||
{
|
||
// 拖拽结束时确保裁剪区域仍在有效范围内
|
||
ConstrainCropArea();
|
||
}
|
||
}
|
||
|
||
private void ConstrainCropArea()
|
||
{
|
||
if (cropArea == null || cropImageRectTransform == null) return;
|
||
|
||
// 获取图片显示区域的边界
|
||
Rect imageRect = cropImageRectTransform.rect;
|
||
Vector2 imageSize = new Vector2(imageRect.width, imageRect.height);
|
||
|
||
// 获取裁剪区域边界
|
||
Vector2 cropSize = cropArea.sizeDelta;
|
||
|
||
// 计算最大偏移量,确保裁剪区域不会超出图片边界
|
||
float maxXOffset = (imageSize.x - cropSize.x) / 2;
|
||
float maxYOffset = (imageSize.y - cropSize.y) / 2;
|
||
|
||
// 限制裁剪区域位置
|
||
Vector2 newPosition = cropArea.anchoredPosition;
|
||
newPosition.x = Mathf.Clamp(newPosition.x, -maxXOffset, maxXOffset);
|
||
newPosition.y = Mathf.Clamp(newPosition.y, -maxYOffset, maxYOffset);
|
||
|
||
cropArea.anchoredPosition = newPosition;
|
||
}
|
||
|
||
// 添加缩放功能
|
||
void Update()
|
||
{
|
||
if (cropArea == null) return;
|
||
|
||
// 处理双指缩放(移动端)
|
||
if (Input.touchCount == 2)
|
||
{
|
||
isScaling = true; // 设置缩放标志
|
||
|
||
Touch touchZero = Input.GetTouch(0);
|
||
Touch touchOne = Input.GetTouch(1);
|
||
|
||
Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
|
||
Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
|
||
|
||
float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
|
||
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
|
||
|
||
float deltaMagnitudeDiff = touchDeltaMag - prevTouchDeltaMag;
|
||
|
||
ResizeCropArea(deltaMagnitudeDiff * 0.5f);
|
||
}
|
||
// 处理鼠标滚轮缩放(PC端)
|
||
else if (Input.mouseScrollDelta.y != 0)
|
||
{
|
||
isScaling = true; // 设置缩放标志
|
||
ResizeCropArea(Input.mouseScrollDelta.y * 10f);
|
||
}
|
||
else
|
||
{
|
||
// 没有缩放操作时,重置标志
|
||
isScaling = false;
|
||
}
|
||
}
|
||
|
||
private void ResizeCropArea(float deltaSize)
|
||
{
|
||
if (cropArea == null || cropImageRectTransform == null) return;
|
||
|
||
// 获取当前尺寸
|
||
Vector2 currentSize = cropArea.sizeDelta;
|
||
|
||
// 计算新尺寸
|
||
Vector2 newSize = new Vector2(
|
||
currentSize.x + deltaSize,
|
||
currentSize.y + deltaSize
|
||
);
|
||
|
||
// 保持正方形并确保在有效范围内
|
||
float maxSize = Mathf.Min(cropImageRectTransform.rect.width, cropImageRectTransform.rect.height);
|
||
float finalSize = Mathf.Clamp(newSize.x, MIN_CROP_SIZE, maxSize);
|
||
cropArea.sizeDelta = new Vector2(finalSize, finalSize);
|
||
|
||
// 调整位置以保持在图片范围内
|
||
ConstrainCropArea();
|
||
}
|
||
}
|
||
} |