image save/load

This commit is contained in:
Savya Bikram Shah
2026-05-29 14:24:36 +05:45
parent 774a7159d3
commit b78678a6d8
31 changed files with 493 additions and 80 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46449f63247f4179976c276c22d17393
timeCreated: 1780040939

View File

@@ -0,0 +1,9 @@
using System.Threading;
using Cysharp.Threading.Tasks;
namespace Darkmatter.Core.Contracts.Features.Capture;
public interface ICaptureFeature
{
UniTask<byte[]> CapturePngAsync(bool saveToGallery = false, CancellationToken ct = default);
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ccf092b5e4744b9ca769205f0bc49368
timeCreated: 1780040946

View File

@@ -6,7 +6,7 @@ namespace Darkmatter.Core.Contracts.Services.Capture
{ {
public interface ICaptureService public interface ICaptureService
{ {
UniTask<byte[]> CapturePngAsync(GameObject captureObject, float captureSize, UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
} }
} }

View File

@@ -17,16 +17,21 @@ namespace Darkmatter.Features.AppBoot.Flow
private readonly AppBootSceneRefs _sceneRefs; private readonly AppBootSceneRefs _sceneRefs;
private readonly ISceneService _sceneService; private readonly ISceneService _sceneService;
private readonly IEventBus _eventBus; private readonly IEventBus _eventBus;
private readonly IProgressionSystem _progression;
public AppBootFlow(AppBootSceneRefs sceneRefs, ISceneService sceneService, IEventBus eventBus) public AppBootFlow(AppBootSceneRefs sceneRefs, ISceneService sceneService, IEventBus eventBus,
IProgressionSystem progression)
{ {
_sceneRefs = sceneRefs; _sceneRefs = sceneRefs;
_sceneService = sceneService; _sceneService = sceneService;
_eventBus = eventBus; _eventBus = eventBus;
_progression = progression;
} }
public async UniTask StartAsync(CancellationToken cancellation = default) public async UniTask StartAsync(CancellationToken cancellation = default)
{ {
await _progression.LoadAsync();
var tcs = new UniTaskCompletionSource(); var tcs = new UniTaskCompletionSource();
var player = _sceneRefs.IntroVideoPlayer; var player = _sceneRefs.IntroVideoPlayer;

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: d54fa8b2c22014496a84c508d897dcdd guid: eeefc3c8ab31d4ac983deab507c76b1f
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -1,11 +1,11 @@
{ {
"name": "Services.Inputs", "name": "Features.Capture",
"rootNamespace": "Darkmatter.Services.Inputs", "rootNamespace": "Darkmatter.Features.Capture",
"references": [ "references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb", "GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1", "GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:75469ad4d38634e559750d17036d5f7c", "GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc" "GUID:f51ebe6a0ceec4240a699833d6309b23"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 68042734671ce4660bff89e042777454 guid: 80056ede5198b460198933cb79d694ff
AssemblyDefinitionImporter: AssemblyDefinitionImporter:
externalObjects: {} externalObjects: {}
userData: userData:

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 88abeaac3da9d499288f8b5a1830cebc guid: 338d273a95ef0403ca2dd1aca67f97f5
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

View File

@@ -0,0 +1,24 @@
using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Features.Capture.UI;
using Darkmatter.Libs.Installers;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Features.Capture
{
public class CaptureFeatureModule : MonoBehaviour, IModule
{
[SerializeField, Range(0.1f, 2f)] private float captureScale = 1f;
[SerializeField] private CaptureButtonView captureButtonView;
public void Register(IContainerBuilder builder)
{
builder.RegisterInstance(new CaptureConfig(captureScale));
builder.Register<ICaptureFeature, CaptureSystem>(Lifetime.Singleton);
if (captureButtonView != null)
builder.RegisterEntryPoint<CaptureButtonPresenter>().WithParameter(captureButtonView);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9b7df1c2a732341d3b123b5e7ae5d7b7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90fa308dc501c46bdb9488e2d6d3e034
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,15 @@
using System;
namespace Darkmatter.Features.Capture
{
[Serializable]
public struct CaptureConfig
{
public float CaptureScale { get; }
public CaptureConfig(float captureScale)
{
CaptureScale = captureScale;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6f12070b2d795429a9b7f6cc0e4ae894

View File

@@ -0,0 +1,50 @@
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Services.Capture;
using Darkmatter.Core.Contracts.Services.Gallery;
using UnityEngine;
namespace Darkmatter.Features.Capture
{
public class CaptureSystem : ICaptureFeature
{
private readonly ICaptureService _captureService;
private readonly IGalleryService _galleryService;
private readonly IGameplaySceneRefs _refs;
private readonly CaptureConfig _config;
public CaptureSystem(
ICaptureService captureService,
IGalleryService galleryService,
IGameplaySceneRefs refs,
CaptureConfig config)
{
_captureService = captureService;
_galleryService = galleryService;
_refs = refs;
_config = config;
}
public async UniTask<byte[]> CapturePngAsync(bool saveToGallery = false, CancellationToken ct = default)
{
var png = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, _config.CaptureScale, ct);
if (!saveToGallery || png == null || png.Length == 0) return png;
var tex = new Texture2D(2, 2, TextureFormat.RGBA32, mipChain: false);
try
{
if (tex.LoadImage(png))
await _galleryService.SaveImageAsync(tex,
$"colorbook_{DateTime.UtcNow:yyyyMMdd_HHmmss}.png", ct);
}
finally
{
UnityEngine.Object.Destroy(tex);
}
return png;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 76bbd68f070e24d6a86130ec07c237b8

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88715eafa42924498a994a891b15c9dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Capture;
using VContainer.Unity;
namespace Darkmatter.Features.Capture.UI
{
public class CaptureButtonPresenter : IStartable, IDisposable
{
private readonly CaptureButtonView _view;
private readonly ICaptureFeature _capture;
public CaptureButtonPresenter(CaptureButtonView view, ICaptureFeature capture)
{
_view = view;
_capture = capture;
}
public void Start()
{
_view.OnCaptureClicked += HandleCaptureClicked;
}
private void HandleCaptureClicked() => CaptureAsync().Forget();
private async UniTaskVoid CaptureAsync()
{
_view.SetInteractable(false);
try
{
await _capture.CapturePngAsync(saveToGallery: true);
}
finally
{
_view.SetInteractable(true);
}
}
public void Dispose()
{
_view.OnCaptureClicked -= HandleCaptureClicked;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ec4fcbab1dbeb4d1690801eee2cc5940

View File

@@ -0,0 +1,29 @@
using System;
using UnityEngine;
using UnityEngine.UI;
namespace Darkmatter.Features.Capture.UI
{
public class CaptureButtonView : MonoBehaviour
{
[SerializeField] private Button captureButton;
public event Action OnCaptureClicked;
private void Start()
{
if (captureButton != null)
captureButton.onClick.AddListener(() => OnCaptureClicked?.Invoke());
}
public void SetInteractable(bool value)
{
if (captureButton != null) captureButton.interactable = value;
}
private void OnDestroy()
{
if (captureButton != null) captureButton.onClick.RemoveAllListeners();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4f0946b153da9431eb6c66bda432901f

View File

@@ -27,6 +27,7 @@ public class ColoringController : IColoringController, IDisposable
private readonly IAssetProviderService _assetProviderService; private readonly IAssetProviderService _assetProviderService;
private readonly IUndoStack _history; private readonly IUndoStack _history;
private readonly IGameplaySceneRefs _refs; private readonly IGameplaySceneRefs _refs;
private readonly ColorPaletteHolderView _paletteHolder;
private GameObject _colorInstance; private GameObject _colorInstance;
private GameObject _colorButtonPrefab; private GameObject _colorButtonPrefab;
@@ -39,7 +40,8 @@ public class ColoringController : IColoringController, IDisposable
IEventBus bus, IEventBus bus,
IAssetProviderService assetProviderService, IAssetProviderService assetProviderService,
IUndoStack history, IUndoStack history,
IGameplaySceneRefs refs) IGameplaySceneRefs refs,
ColorPaletteHolderView paletteHolder)
{ {
_repository = repository; _repository = repository;
_buttonFactory = buttonFactory; _buttonFactory = buttonFactory;
@@ -47,12 +49,14 @@ public class ColoringController : IColoringController, IDisposable
_assetProviderService = assetProviderService; _assetProviderService = assetProviderService;
_history = history; _history = history;
_refs = refs; _refs = refs;
_paletteHolder = paletteHolder;
} }
public async UniTask InitializeRegionsAsync(IDrawingTemplate template, public async UniTask InitializeRegionsAsync(IDrawingTemplate template,
IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct) IReadOnlyDictionary<string, Color> savedColors, CancellationToken ct)
{ {
Clear(); Clear();
_paletteHolder.Show();
await TryLoadColorButtonPrefabAsync(ct); await TryLoadColorButtonPrefabAsync(ct);
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
await TryLoadColorPaletteAsync(template, ct); await TryLoadColorPaletteAsync(template, ct);

View File

@@ -27,6 +27,7 @@ namespace Darkmatter.Features.Coloring.UI
_color = color; _color = color;
_repository = repository; _repository = repository;
_sfx = sfx; _sfx = sfx;
if (_button == null) _button = GetComponent<Button>();
foreach (var swatch in swatches) foreach (var swatch in swatches)
{ {
swatch.color = color; swatch.color = color;

View File

@@ -34,6 +34,7 @@ namespace Darkmatter.Features.Coloring.UI
public void OnPointerClick(PointerEventData eventData) public void OnPointerClick(PointerEventData eventData)
{ {
Debug.Log($"[ColorRegion] clicked '{RegionId}' on '{gameObject.name}'");
OnRegionClicked?.Invoke(); OnRegionClicked?.Invoke();
} }

View File

@@ -52,11 +52,18 @@ namespace Darkmatter.Features.DrawingTemplates.Systems
_initialized = true; _initialized = true;
} }
public UniTask<Sprite> GetThumbnailAsync(string id) public async UniTask<Sprite> GetThumbnailAsync(string id)
{ {
if (!_byId.TryGetValue(id, out var t)) if (!_byId.TryGetValue(id, out var t))
throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?"); throw new KeyNotFoundException($"Template '{id}' not in catalog. Did InitializeAsync run?");
return UniTask.FromResult(t.DefaultThumbnail);
var savedTex = await _progression.GetCachedThumbnailAsync(id);
if (savedTex != null)
{
var rect = new Rect(0, 0, savedTex.width, savedTex.height);
return Sprite.Create(savedTex, rect, new Vector2(0.5f, 0.5f));
}
return t.DefaultThumbnail;
} }
public UniTask<IDrawingTemplate> LoadAsync(string id) public UniTask<IDrawingTemplate> LoadAsync(string id)

View File

@@ -2,13 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Features.Capture;
using Darkmatter.Core.Contracts.Features.Coloring; using Darkmatter.Core.Contracts.Features.Coloring;
using Darkmatter.Core.Contracts.Features.DrawingCatalog; using Darkmatter.Core.Contracts.Features.DrawingCatalog;
using Darkmatter.Core.Contracts.Features.GameplayFlow; using Darkmatter.Core.Contracts.Features.GameplayFlow;
using Darkmatter.Core.Contracts.Features.Loading; using Darkmatter.Core.Contracts.Features.Loading;
using Darkmatter.Core.Contracts.Features.Progression; using Darkmatter.Core.Contracts.Features.Progression;
using Darkmatter.Core.Contracts.Features.ShapeBuilder; using Darkmatter.Core.Contracts.Features.ShapeBuilder;
using Darkmatter.Core.Contracts.Services.Capture;
using Darkmatter.Core.Contracts.Services.Gallery; using Darkmatter.Core.Contracts.Services.Gallery;
using Darkmatter.Core.Contracts.Services.Scenes; using Darkmatter.Core.Contracts.Services.Scenes;
using Darkmatter.Core.Data.Dynamic.Features.Progression; using Darkmatter.Core.Data.Dynamic.Features.Progression;
@@ -31,8 +31,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
private readonly IShapeBuilderController _shapeBuilder; private readonly IShapeBuilderController _shapeBuilder;
private readonly IColoringController _coloring; private readonly IColoringController _coloring;
private readonly ISceneService _scenes; private readonly ISceneService _scenes;
private readonly ICaptureService _captureService; private readonly ICaptureFeature _capture;
private readonly IGameplaySceneRefs _refs;
private readonly ILoadingScreen _loadingScreen; private readonly ILoadingScreen _loadingScreen;
private readonly IEventBus _bus; private readonly IEventBus _bus;
@@ -51,8 +50,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
IShapeBuilderController shapeBuilder, IShapeBuilderController shapeBuilder,
IColoringController coloring, IColoringController coloring,
ISceneService scenes, ISceneService scenes,
ICaptureService captureService, ICaptureFeature capture,
IGameplaySceneRefs refs,
ILoadingScreen loadingScreen, ILoadingScreen loadingScreen,
IEventBus bus) IEventBus bus)
{ {
@@ -61,9 +59,8 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_shapeBuilder = shapeBuilder; _shapeBuilder = shapeBuilder;
_coloring = coloring; _coloring = coloring;
_scenes = scenes; _scenes = scenes;
_captureService = captureService; _capture = capture;
_loadingScreen = loadingScreen; _loadingScreen = loadingScreen;
_refs = refs;
_bus = bus; _bus = bus;
} }
@@ -89,6 +86,9 @@ namespace Darkmatter.Features.GameplayFlow.Systems
_assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled); _assembledSub = _bus.Subscribe<ShapeAssembledSignal>(OnShapeAssembled);
_colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied); _colorAppliedSub = _bus.Subscribe<ColorAppliedSignal>(OnColorApplied);
Application.quitting += OnAppQuitting;
Application.focusChanged += OnAppFocusChanged;
_loadingScreen.SetProgress(1f); _loadingScreen.SetProgress(1f);
if (_phase == DrawingPhase.Coloring) if (_phase == DrawingPhase.Coloring)
{ {
@@ -105,7 +105,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public async UniTask BackAsync(CancellationToken ct) public async UniTask BackAsync(CancellationToken ct)
{ {
await SaveCurrentAsync(captureThumbnail: true, CancellationToken.None); await SaveCurrentAsync(CancellationToken.None);
_shapeBuilder.Clear(); _shapeBuilder.Clear();
_coloring.Clear(); _coloring.Clear();
await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: ct); await _scenes.LoadSceneAsync(GameScene.Colorbook, progress: null, cancellationToken: ct);
@@ -113,14 +113,13 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public async UniTask SaveAsync(CancellationToken ct) public async UniTask SaveAsync(CancellationToken ct)
{ {
// Explicit user-pressed save — capture thumbnail + (planned) native gallery export. await SaveCurrentAsync(ct);
await SaveCurrentAsync(captureThumbnail: true, ct); await _capture.CapturePngAsync(saveToGallery: true, ct);
// TODO: route through ICaptureService + IGalleryService once those exist.
} }
public async UniTask NextAsync(CancellationToken ct) public async UniTask NextAsync(CancellationToken ct)
{ {
await SaveCurrentAsync(captureThumbnail: true, ct); await SaveCurrentAsync(ct);
_progression.MarkCompleted(_templateId); _progression.MarkCompleted(_templateId);
var nextId = _catalog.GetNextTemplate(_templateId); var nextId = _catalog.GetNextTemplate(_templateId);
@@ -139,7 +138,14 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public void OnApplicationPaused() public void OnApplicationPaused()
{ {
// Fire-and-forget — pause window is small; PlayerPrefs write is sync under the hood. // Fire-and-forget — pause window is small; PlayerPrefs write is sync under the hood.
SaveCurrentAsync(captureThumbnail: false, CancellationToken.None).Forget(); SaveCurrentAsync(CancellationToken.None).Forget();
}
private void OnAppQuitting() => OnApplicationPaused();
private void OnAppFocusChanged(bool focused)
{
if (!focused) OnApplicationPaused();
} }
private void OnShapeAssembled(ShapeAssembledSignal signal) private void OnShapeAssembled(ShapeAssembledSignal signal)
@@ -157,7 +163,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
await _coloring.InitializeRegionsAsync(_template, savedColors, _scopeCts.Token); await _coloring.InitializeRegionsAsync(_template, savedColors, _scopeCts.Token);
_shapeBuilder.DespawnDrawing(); _shapeBuilder.DespawnDrawing();
await SaveCurrentAsync(captureThumbnail: true, ct); await SaveCurrentAsync(ct);
} }
private void OnColorApplied(ColorAppliedSignal _) private void OnColorApplied(ColorAppliedSignal _)
@@ -174,7 +180,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
try try
{ {
await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct); await UniTask.Delay(AutosaveDebounceMs, cancellationToken: ct);
await SaveCurrentAsync(captureThumbnail: false, ct); await SaveCurrentAsync(ct);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -182,7 +188,7 @@ namespace Darkmatter.Features.GameplayFlow.Systems
} }
} }
private async UniTask SaveCurrentAsync(bool captureThumbnail, CancellationToken ct) private async UniTask SaveCurrentAsync(CancellationToken ct)
{ {
if (string.IsNullOrEmpty(_templateId)) return; if (string.IsNullOrEmpty(_templateId)) return;
@@ -198,18 +204,14 @@ namespace Darkmatter.Features.GameplayFlow.Systems
phase = _phase, phase = _phase,
snappedPieces = snappedIds, snappedPieces = snappedIds,
regionColors = regionEntries, regionColors = regionEntries,
hasThumbnail = captureThumbnail || (existing?.hasThumbnail ?? false), hasThumbnail = true,
hasBeenCompleted = existing?.hasBeenCompleted ?? false, hasBeenCompleted = existing?.hasBeenCompleted ?? false,
completionCount = existing?.completionCount ?? 0, completionCount = existing?.completionCount ?? 0,
UpdatedUtc = DateTime.UtcNow, UpdatedUtc = DateTime.UtcNow,
FirstCompletedUtc = existing?.FirstCompletedUtc, FirstCompletedUtc = existing?.FirstCompletedUtc,
}; };
byte[] thumbnailPng = null; var thumbnailPng = await _capture.CapturePngAsync(saveToGallery: false, ct);
if (captureThumbnail)
{
thumbnailPng = await _captureService.CapturePngAsync(_refs.PaperRoot.gameObject, 10, ct);
}
await _progression.SaveProgressAsync(progress, thumbnailPng); await _progression.SaveProgressAsync(progress, thumbnailPng);
} }
@@ -226,6 +228,9 @@ namespace Darkmatter.Features.GameplayFlow.Systems
public void Dispose() public void Dispose()
{ {
Application.quitting -= OnAppQuitting;
Application.focusChanged -= OnAppFocusChanged;
_assembledSub?.Dispose(); _assembledSub?.Dispose();
_colorAppliedSub?.Dispose(); _colorAppliedSub?.Dispose();
_autosaveCts?.Cancel(); _autosaveCts?.Cancel();

View File

@@ -1,52 +1,88 @@
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Services.Camera;
using Darkmatter.Core.Contracts.Services.Capture; using Darkmatter.Core.Contracts.Services.Capture;
using UnityEngine; using UnityEngine;
using CameraType = Darkmatter.Core.Enums.Services.Camera.CameraType;
namespace Darkmatter.Services.Capture namespace Darkmatter.Services.Capture
{ {
public class CaptureService : ICaptureService public class CaptureService : ICaptureService
{ {
private readonly ICameraService _cameraService; public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float scale,
public CaptureService(ICameraService cameraService)
{
_cameraService = cameraService;
}
public async UniTask<byte[]> CapturePngAsync(GameObject captureObject, float captureSize,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
var captureCamera = _cameraService.GetCamera(CameraType.CaptureCamera); await UniTask.WaitForEndOfFrame(cancellationToken);
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken); var fullScreen = ScreenCapture.CaptureScreenshotAsTexture();
try
{
Rect crop = ComputeCropRect(captureObject, fullScreen.width, fullScreen.height);
int cropW = Mathf.Max(1, (int)crop.width);
int cropH = Mathf.Max(1, (int)crop.height);
int size = Mathf.RoundToInt(captureSize); var cropped = new Texture2D(cropW, cropH, TextureFormat.RGBA32, mipChain: false);
var renderTexture = RenderTexture.GetTemporary(size, size, 24); try
var previousTarget = captureCamera.targetTexture; {
var previousActive = RenderTexture.active; cropped.SetPixels(fullScreen.GetPixels((int)crop.x, (int)crop.y, cropW, cropH));
cropped.Apply();
int targetW = Mathf.Max(1, Mathf.RoundToInt(cropW * scale));
int targetH = Mathf.Max(1, Mathf.RoundToInt(cropH * scale));
Texture2D output = cropped;
if (output.width != targetW || output.height != targetH)
output = Resize(cropped, targetW, targetH);
try try
{ {
captureCamera.targetTexture = renderTexture; return output.EncodeToPNG();
captureCamera.Render();
RenderTexture.active = renderTexture;
var texture = new Texture2D(size, size, TextureFormat.RGBA32, false);
texture.ReadPixels(new Rect(0, 0, size, size), 0, 0);
texture.Apply();
return texture.EncodeToPNG();
} }
finally finally
{ {
captureCamera.targetTexture = previousTarget; if (output != cropped) Object.Destroy(output);
RenderTexture.active = previousActive; }
RenderTexture.ReleaseTemporary(renderTexture); }
finally
{
Object.Destroy(cropped);
}
}
finally
{
Object.Destroy(fullScreen);
}
}
private static Rect ComputeCropRect(GameObject target, int screenW, int screenH)
{
if (target == null || target.transform is not RectTransform rt)
return new Rect(0, 0, screenW, screenH);
var corners = new Vector3[4];
rt.GetWorldCorners(corners);
// ScreenSpaceOverlay canvas: world corners are already in screen pixels.
float minX = Mathf.Clamp(corners[0].x, 0, screenW);
float minY = Mathf.Clamp(corners[0].y, 0, screenH);
float maxX = Mathf.Clamp(corners[2].x, 0, screenW);
float maxY = Mathf.Clamp(corners[2].y, 0, screenH);
return Rect.MinMaxRect(minX, minY, maxX, maxY);
}
private static Texture2D Resize(Texture2D src, int width, int height)
{
var rt = RenderTexture.GetTemporary(width, height);
var prev = RenderTexture.active;
try
{
Graphics.Blit(src, rt);
RenderTexture.active = rt;
var dst = new Texture2D(width, height, TextureFormat.RGBA32, mipChain: false);
dst.ReadPixels(new Rect(0, 0, width, height), 0, 0);
dst.Apply();
return dst;
}
finally
{
RenderTexture.active = prev;
RenderTexture.ReleaseTemporary(rt);
} }
} }
} }

View File

@@ -27,12 +27,12 @@ RectTransform:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1002787829030344513} m_GameObject: {fileID: 1002787829030344513}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: m_Children:
- {fileID: 1848291166980498350} - {fileID: 311360238347084570}
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0} m_AnchorMin: {x: 0, y: 0}
@@ -70,7 +70,7 @@ MonoBehaviour:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: 21300000, guid: c32e75a5c9f8a654f816db8016b43f4a, type: 3} m_Sprite: {fileID: 21300000, guid: c32e75a5c9f8a654f816db8016b43f4a, type: 3}
m_Type: 0 m_Type: 0
m_PreserveAspect: 0 m_PreserveAspect: 1
m_FillCenter: 1 m_FillCenter: 1
m_FillMethod: 4 m_FillMethod: 4
m_FillAmount: 1 m_FillAmount: 1
@@ -136,6 +136,96 @@ MonoBehaviour:
m_EditorClassIdentifier: Features.DrawingCatalog::Darkmatter.Features.DrawingCatalog.DrawingCatalogButton m_EditorClassIdentifier: Features.DrawingCatalog::Darkmatter.Features.DrawingCatalog.DrawingCatalogButton
thumbnail: {fileID: 3052182449713955122} thumbnail: {fileID: 3052182449713955122}
button: {fileID: 1375717659554091244} button: {fileID: 1375717659554091244}
--- !u!1 &6863047044953543080
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 311360238347084570}
- component: {fileID: 2291871093933523869}
- component: {fileID: 7872328643013237936}
- component: {fileID: 7829581227321367687}
m_Layer: 5
m_Name: Mask
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &311360238347084570
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6863047044953543080}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1848291166980498350}
m_Father: {fileID: 7165608992005527048}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 2.1714, y: 3.3525}
m_SizeDelta: {x: 294.6573, y: 224.4221}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2291871093933523869
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6863047044953543080}
m_CullTransparentMesh: 1
--- !u!114 &7872328643013237936
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6863047044953543080}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 0.28235295}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &7829581227321367687
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6863047044953543080}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Mask
m_ShowMaskGraphic: 0
--- !u!1 &7483212002805005001 --- !u!1 &7483212002805005001
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -161,17 +251,17 @@ RectTransform:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7483212002805005001} m_GameObject: {fileID: 7483212002805005001}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1.2, y: 1.2, z: 1.2}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 1
m_Children: [] m_Children: []
m_Father: {fileID: 7165608992005527048} m_Father: {fileID: 311360238347084570}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0.5, y: 0.5} m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 1.6053, y: 7.2107} m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 178.8338, y: 199.2152} m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5} m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4252515989583821464 --- !u!222 &4252515989583821464
CanvasRenderer: CanvasRenderer:
@@ -203,7 +293,7 @@ MonoBehaviour:
m_Calls: [] m_Calls: []
m_Sprite: {fileID: 21300000, guid: f6db270b00fab4f4b9c4f2002451b690, type: 3} m_Sprite: {fileID: 21300000, guid: f6db270b00fab4f4b9c4f2002451b690, type: 3}
m_Type: 0 m_Type: 0
m_PreserveAspect: 0 m_PreserveAspect: 1
m_FillCenter: 1 m_FillCenter: 1
m_FillMethod: 4 m_FillMethod: 4
m_FillAmount: 1 m_FillAmount: 1

View File

@@ -418,8 +418,7 @@ MonoBehaviour:
m_RequiresDepthTextureOption: 2 m_RequiresDepthTextureOption: 2
m_RequiresOpaqueTextureOption: 2 m_RequiresOpaqueTextureOption: 2
m_CameraType: 0 m_CameraType: 0
m_Cameras: m_Cameras: []
- {fileID: 82022334}
m_RendererIndex: -1 m_RendererIndex: -1
m_VolumeLayerMask: m_VolumeLayerMask:
serializedVersion: 2 serializedVersion: 2
@@ -481,12 +480,12 @@ Camera:
near clip plane: 0.3 near clip plane: 0.3
far clip plane: 1000 far clip plane: 1000
field of view: 34 field of view: 34
orthographic: 1 orthographic: 0
orthographic size: 1 orthographic size: 1
m_Depth: -1 m_Depth: -1
m_CullingMask: m_CullingMask:
serializedVersion: 2 serializedVersion: 2
m_Bits: 0 m_Bits: 4294967295
m_RenderingPath: -1 m_RenderingPath: -1
m_TargetTexture: {fileID: 0} m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0 m_TargetDisplay: 0

View File

@@ -119,6 +119,52 @@ NavMeshSettings:
debug: debug:
m_Flags: 0 m_Flags: 0
m_NavMeshData: {fileID: 0} m_NavMeshData: {fileID: 0}
--- !u!1 &26318078
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 26318079}
- component: {fileID: 26318080}
m_Layer: 0
m_Name: CaptureFeatureModule
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &26318079
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 26318078}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1965442263}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &26318080
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 26318078}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9b7df1c2a732341d3b123b5e7ae5d7b7, type: 3}
m_Name:
m_EditorClassIdentifier: Features.Capture::Darkmatter.Features.Capture.CaptureFeatureModule
captureSize: 1
captureButtonView: {fileID: 376589371}
--- !u!1 &64614225 --- !u!1 &64614225
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -698,6 +744,7 @@ GameObject:
- component: {fileID: 376589370} - component: {fileID: 376589370}
- component: {fileID: 376589369} - component: {fileID: 376589369}
- component: {fileID: 376589368} - component: {fileID: 376589368}
- component: {fileID: 376589371}
m_Layer: 5 m_Layer: 5
m_Name: CaptureButton m_Name: CaptureButton
m_TagString: Untagged m_TagString: Untagged
@@ -806,6 +853,19 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 376589366} m_GameObject: {fileID: 376589366}
m_CullTransparentMesh: 1 m_CullTransparentMesh: 1
--- !u!114 &376589371
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 376589366}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4f0946b153da9431eb6c66bda432901f, type: 3}
m_Name:
m_EditorClassIdentifier: Features.Capture::Darkmatter.Features.Capture.UI.CaptureButtonView
captureButton: {fileID: 376589368}
--- !u!1 &396806865 --- !u!1 &396806865
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1860,6 +1920,7 @@ MonoBehaviour:
- {fileID: 1551649429} - {fileID: 1551649429}
- {fileID: 1991184380} - {fileID: 1991184380}
- {fileID: 1867428030} - {fileID: 1867428030}
- {fileID: 26318080}
--- !u!1 &1281354567 --- !u!1 &1281354567
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -2341,6 +2402,7 @@ Transform:
- {fileID: 1551649428} - {fileID: 1551649428}
- {fileID: 1991184379} - {fileID: 1991184379}
- {fileID: 1867428029} - {fileID: 1867428029}
- {fileID: 26318079}
m_Father: {fileID: 1224714932} m_Father: {fileID: 1224714932}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1982732271 --- !u!1 &1982732271