394 lines
14 KiB
C#
394 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using Core;
|
|
using UnityEngine;
|
|
using SDev;
|
|
using ScreenUtils = Utils.ScreenSpaceUtility;
|
|
|
|
namespace Minigames.StatueDressup.Utils
|
|
{
|
|
/// <summary>
|
|
/// Manages statue photo capture, storage, and retrieval.
|
|
/// Supports area-limited screenshots, persistent file storage (mobile + PC),
|
|
/// and optimized gallery loading with pagination.
|
|
/// </summary>
|
|
public static class StatuePhotoManager
|
|
{
|
|
private const string PhotoFolder = "StatuePhotos";
|
|
private const string PhotoPrefix = "MrCementStatue_";
|
|
private const string MetadataKeyPrefix = "StatuePhoto_Meta_";
|
|
private const string PhotoIndexKey = "StatuePhoto_Index";
|
|
|
|
/// <summary>
|
|
/// Photo metadata stored in PlayerPrefs
|
|
/// </summary>
|
|
[Serializable]
|
|
public class PhotoMetadata
|
|
{
|
|
public string photoId;
|
|
public string timestamp;
|
|
public int decorationCount;
|
|
public long fileSizeBytes;
|
|
}
|
|
|
|
#region Capture
|
|
|
|
/// <summary>
|
|
/// Capture a specific area of the screen using Screenshot Helper
|
|
/// </summary>
|
|
/// <param name="captureArea">RectTransform defining the capture region</param>
|
|
/// <param name="onComplete">Callback with captured Texture2D</param>
|
|
/// <param name="mainCamera">Camera used for coordinate conversion (null = Camera.main)</param>
|
|
/// <param name="clampToScreenBounds">If true, clamps capture area to visible screen bounds</param>
|
|
public static void CaptureAreaPhoto(
|
|
RectTransform captureArea,
|
|
Action<Texture2D> onComplete,
|
|
Camera mainCamera = null,
|
|
bool clampToScreenBounds = true)
|
|
{
|
|
if (captureArea == null)
|
|
{
|
|
Logging.Error("[StatuePhotoManager] CaptureArea RectTransform is null!");
|
|
onComplete?.Invoke(null);
|
|
return;
|
|
}
|
|
|
|
if (mainCamera == null) mainCamera = Camera.main;
|
|
|
|
// Use ScreenSpaceUtility to convert RectTransform to screen rect
|
|
// returnCenterPosition = true because ScreenshotHelper expects center position
|
|
Rect screenRect = ScreenUtils.RectTransformToScreenRect(
|
|
captureArea,
|
|
mainCamera,
|
|
clampToScreenBounds,
|
|
returnCenterPosition: true
|
|
);
|
|
|
|
Logging.Debug($"[StatuePhotoManager] Capturing area: pos={screenRect.position}, size={screenRect.size}");
|
|
|
|
// Use Screenshot Helper's Capture method
|
|
ScreenshotHelper.Instance.Capture(
|
|
screenRect.position,
|
|
screenRect.size,
|
|
(texture) =>
|
|
{
|
|
if (texture != null)
|
|
{
|
|
Logging.Debug($"[StatuePhotoManager] Photo captured: {texture.width}x{texture.height}");
|
|
onComplete?.Invoke(texture);
|
|
}
|
|
else
|
|
{
|
|
Logging.Error("[StatuePhotoManager] Screenshot Helper returned null texture!");
|
|
onComplete?.Invoke(null);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Save/Load
|
|
|
|
/// <summary>
|
|
/// Save photo to persistent storage with metadata
|
|
/// </summary>
|
|
/// <param name="photo">Texture2D to save</param>
|
|
/// <param name="decorationCount">Number of decorations placed (for metadata)</param>
|
|
/// <returns>Photo ID if successful, null if failed</returns>
|
|
public static string SavePhoto(Texture2D photo, int decorationCount = 0)
|
|
{
|
|
if (photo == null)
|
|
{
|
|
Logging.Error("[StatuePhotoManager] Cannot save null photo");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Generate unique photo ID
|
|
string photoId = $"{PhotoPrefix}{DateTime.Now.Ticks}";
|
|
|
|
// Save texture using FileSaveUtil
|
|
string savedPath = FileSaveUtil.Instance.SaveTextureAsPNG(
|
|
photo,
|
|
FileSaveUtil.AppPath.PersistentDataPath,
|
|
PhotoFolder,
|
|
photoId
|
|
);
|
|
|
|
// Calculate file size
|
|
FileInfo fileInfo = new FileInfo(savedPath);
|
|
long fileSize = fileInfo.Exists ? fileInfo.Length : 0;
|
|
|
|
// Save metadata
|
|
PhotoMetadata metadata = new PhotoMetadata
|
|
{
|
|
photoId = photoId,
|
|
timestamp = DateTime.Now.ToString("o"),
|
|
decorationCount = decorationCount,
|
|
fileSizeBytes = fileSize
|
|
};
|
|
|
|
SaveMetadata(metadata);
|
|
AddToPhotoIndex(photoId);
|
|
|
|
Logging.Debug($"[StatuePhotoManager] Photo saved: {savedPath} ({fileSize} bytes)");
|
|
return photoId;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($"[StatuePhotoManager] Failed to save photo: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load photo texture from storage
|
|
/// </summary>
|
|
public static Texture2D LoadPhoto(string photoId)
|
|
{
|
|
if (string.IsNullOrEmpty(photoId))
|
|
{
|
|
Logging.Warning("[StatuePhotoManager] PhotoId is null or empty");
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
string filePath = GetPhotoFilePath(photoId);
|
|
|
|
if (!File.Exists(filePath))
|
|
{
|
|
Logging.Warning($"[StatuePhotoManager] Photo not found: {filePath}");
|
|
return null;
|
|
}
|
|
|
|
byte[] fileData = File.ReadAllBytes(filePath);
|
|
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32, false);
|
|
|
|
if (texture.LoadImage(fileData))
|
|
{
|
|
Logging.Debug($"[StatuePhotoManager] Photo loaded: {photoId} ({texture.width}x{texture.height})");
|
|
return texture;
|
|
}
|
|
else
|
|
{
|
|
Logging.Error($"[StatuePhotoManager] Failed to decode image: {photoId}");
|
|
UnityEngine.Object.Destroy(texture);
|
|
return null;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($"[StatuePhotoManager] Failed to load photo {photoId}: {e.Message}");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete photo and its metadata
|
|
/// </summary>
|
|
public static bool DeletePhoto(string photoId)
|
|
{
|
|
if (string.IsNullOrEmpty(photoId)) return false;
|
|
|
|
try
|
|
{
|
|
string filePath = GetPhotoFilePath(photoId);
|
|
|
|
if (File.Exists(filePath))
|
|
{
|
|
File.Delete(filePath);
|
|
}
|
|
|
|
DeleteMetadata(photoId);
|
|
RemoveFromPhotoIndex(photoId);
|
|
|
|
Logging.Debug($"[StatuePhotoManager] Photo deleted: {photoId}");
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Logging.Error($"[StatuePhotoManager] Failed to delete photo {photoId}: {e.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Gallery Support
|
|
|
|
/// <summary>
|
|
/// Get all photo IDs sorted by timestamp (newest first)
|
|
/// </summary>
|
|
public static List<string> GetAllPhotoIds()
|
|
{
|
|
string indexJson = PlayerPrefs.GetString(PhotoIndexKey, "[]");
|
|
List<string> photoIds = JsonUtility.FromJson<PhotoIdList>(WrapJsonArray(indexJson))?.ids ?? new List<string>();
|
|
|
|
// Sort by timestamp descending (newest first)
|
|
photoIds.Sort((a, b) =>
|
|
{
|
|
PhotoMetadata metaA = LoadMetadata(a);
|
|
PhotoMetadata metaB = LoadMetadata(b);
|
|
|
|
DateTime dateA = DateTime.Parse(metaA?.timestamp ?? DateTime.MinValue.ToString("o"));
|
|
DateTime dateB = DateTime.Parse(metaB?.timestamp ?? DateTime.MinValue.ToString("o"));
|
|
|
|
return dateB.CompareTo(dateA);
|
|
});
|
|
|
|
return photoIds;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get paginated photo IDs for optimized gallery loading
|
|
/// </summary>
|
|
/// <param name="page">Page number (0-indexed)</param>
|
|
/// <param name="pageSize">Number of items per page</param>
|
|
public static List<string> GetPhotoIdsPage(int page, int pageSize)
|
|
{
|
|
List<string> allIds = GetAllPhotoIds();
|
|
int startIndex = page * pageSize;
|
|
|
|
if (startIndex >= allIds.Count) return new List<string>();
|
|
|
|
int count = Mathf.Min(pageSize, allIds.Count - startIndex);
|
|
return allIds.GetRange(startIndex, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get total number of saved photos
|
|
/// </summary>
|
|
public static int GetPhotoCount()
|
|
{
|
|
return GetAllPhotoIds().Count;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get latest photo ID (most recent)
|
|
/// </summary>
|
|
public static string GetLatestPhotoId()
|
|
{
|
|
List<string> allIds = GetAllPhotoIds();
|
|
return allIds.Count > 0 ? allIds[0] : null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load photo metadata
|
|
/// </summary>
|
|
public static PhotoMetadata GetPhotoMetadata(string photoId)
|
|
{
|
|
return LoadMetadata(photoId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create thumbnail from full-size photo (for gallery preview)
|
|
/// </summary>
|
|
public static Texture2D CreateThumbnail(Texture2D fullSizePhoto, int maxSize = 256)
|
|
{
|
|
if (fullSizePhoto == null) return null;
|
|
|
|
int width = fullSizePhoto.width;
|
|
int height = fullSizePhoto.height;
|
|
|
|
// Calculate thumbnail size maintaining aspect ratio
|
|
float scale = Mathf.Min((float)maxSize / width, (float)maxSize / height);
|
|
int thumbWidth = Mathf.RoundToInt(width * scale);
|
|
int thumbHeight = Mathf.RoundToInt(height * scale);
|
|
|
|
// Create thumbnail using bilinear filtering
|
|
RenderTexture rt = RenderTexture.GetTemporary(thumbWidth, thumbHeight, 0, RenderTextureFormat.ARGB32);
|
|
RenderTexture.active = rt;
|
|
|
|
Graphics.Blit(fullSizePhoto, rt);
|
|
|
|
Texture2D thumbnail = new Texture2D(thumbWidth, thumbHeight, TextureFormat.RGB24, false);
|
|
thumbnail.ReadPixels(new Rect(0, 0, thumbWidth, thumbHeight), 0, 0);
|
|
thumbnail.Apply();
|
|
|
|
RenderTexture.active = null;
|
|
RenderTexture.ReleaseTemporary(rt);
|
|
|
|
return thumbnail;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Helpers
|
|
|
|
public static string GetPhotoDirectory()
|
|
{
|
|
return Path.Combine(Application.persistentDataPath, PhotoFolder);
|
|
}
|
|
|
|
private static string GetPhotoFilePath(string photoId)
|
|
{
|
|
return Path.Combine(GetPhotoDirectory(), $"{photoId}.png");
|
|
}
|
|
|
|
private static void SaveMetadata(PhotoMetadata metadata)
|
|
{
|
|
string json = JsonUtility.ToJson(metadata);
|
|
PlayerPrefs.SetString(MetadataKeyPrefix + metadata.photoId, json);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
private static PhotoMetadata LoadMetadata(string photoId)
|
|
{
|
|
string json = PlayerPrefs.GetString(MetadataKeyPrefix + photoId, null);
|
|
return string.IsNullOrEmpty(json) ? null : JsonUtility.FromJson<PhotoMetadata>(json);
|
|
}
|
|
|
|
private static void DeleteMetadata(string photoId)
|
|
{
|
|
PlayerPrefs.DeleteKey(MetadataKeyPrefix + photoId);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
private static void AddToPhotoIndex(string photoId)
|
|
{
|
|
List<string> photoIds = GetAllPhotoIds();
|
|
if (!photoIds.Contains(photoId))
|
|
{
|
|
photoIds.Add(photoId);
|
|
SavePhotoIndex(photoIds);
|
|
}
|
|
}
|
|
|
|
private static void RemoveFromPhotoIndex(string photoId)
|
|
{
|
|
List<string> photoIds = GetAllPhotoIds();
|
|
if (photoIds.Remove(photoId))
|
|
{
|
|
SavePhotoIndex(photoIds);
|
|
}
|
|
}
|
|
|
|
private static void SavePhotoIndex(List<string> photoIds)
|
|
{
|
|
string json = JsonUtility.ToJson(new PhotoIdList { ids = photoIds });
|
|
PlayerPrefs.SetString(PhotoIndexKey, json);
|
|
PlayerPrefs.Save();
|
|
}
|
|
|
|
private static string WrapJsonArray(string json)
|
|
{
|
|
if (json.StartsWith("[")) return "{\"ids\":" + json + "}";
|
|
return json;
|
|
}
|
|
|
|
[Serializable]
|
|
private class PhotoIdList
|
|
{
|
|
public List<string> ids = new List<string>();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|