Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
4cec572
Update NuGet
neon-nyan Oct 4, 2026
5f53bce
Update Usage
neon-nyan Oct 4, 2026
157a9c7
First attempt
neon-nyan Oct 8, 2026
f81fd27
Merge branch 'main' into feat/mt-bg-renderer
neon-nyan Oct 8, 2026
89fbe94
Implement Loaders
neon-nyan Oct 11, 2026
e198da8
Make presenter load work
neon-nyan Oct 13, 2026
b257b0b
Add element elevation animation back
neon-nyan Oct 13, 2026
197f6a7
Add element parallax animation back
neon-nyan Oct 13, 2026
08b9f37
Make controls working
neon-nyan Oct 14, 2026
9d25e6c
Code cleanup + Make volume control working
neon-nyan Oct 14, 2026
05efcc0
Make seeking slider working
neon-nyan Oct 14, 2026
1c0f52e
Fix start-up error on AOT
neon-nyan Oct 14, 2026
e54913d
Replace sprites format with AVIF
neon-nyan Oct 15, 2026
fc9449d
Code cleanup + Fix load for pre-loaded image source
neon-nyan Oct 15, 2026
26743ff
[ImageBackgroundManager] Make some methods public
neon-nyan Oct 18, 2026
6b9b0e2
[StringUtility] Implement TryGetStreamFromUrlDataString
neon-nyan Oct 18, 2026
4bcac90
Update MainPage.xaml.cs
neon-nyan Oct 18, 2026
a039414
Revert "Replace sprites format with AVIF"
neon-nyan Oct 18, 2026
cfe9f3e
Implement ImageDecoderValueConverter
neon-nyan Oct 18, 2026
724fedd
Move ImageDecoderValueConverter method to extension
neon-nyan Oct 18, 2026
2990e5e
Update MainPage.xaml
neon-nyan Oct 18, 2026
3854be3
Fix thread lock while cleaning up logs
neon-nyan Oct 18, 2026
bce5001
Re-optimize big image files
neon-nyan Oct 18, 2026
a7d4437
Refactor ImageBackgroundManager pt. 1
neon-nyan Oct 18, 2026
97079e5
Fix CurrentWindowIsVisible flag reading
neon-nyan Oct 18, 2026
1d8005c
[LayeredBackgroundImage] Fix audio not restored while initially muted.
neon-nyan Oct 18, 2026
2b8f2f2
Fix element state while window is restored from being minimized at st…
neon-nyan Oct 18, 2026
cd1bf6d
Oopsies
neon-nyan Oct 18, 2026
b7f4789
Fix URL Clipboard copied wrong URL
neon-nyan Oct 18, 2026
d26779a
Avoid timer not getting disposed on failure coopToken
neon-nyan Oct 18, 2026
bfd2861
Code cleanup
neon-nyan Oct 18, 2026
331979f
Restore switching to static image mechanism on Play Pause
neon-nyan Oct 18, 2026
1d1191d
Fix Copy Background URL command not working
neon-nyan Oct 18, 2026
03ca80f
Disable Play Pause button while image is loading
neon-nyan Oct 18, 2026
4dd4216
Modify ImageBackgroundManager.IsVideo_Set initializer
neon-nyan Oct 18, 2026
80c6450
Bring back accent color loader
neon-nyan Oct 18, 2026
6a768d7
Run GetMediaAccentColorFromAsync on background thread
neon-nyan Oct 18, 2026
ca2d149
Fix ColorPaletteUtility (again)
neon-nyan Oct 19, 2026
6cee3e2
Fix incomplete dispose due to throw on already disposed objects
neon-nyan Oct 19, 2026
b44c014
Implement AVIF decoding
neon-nyan Oct 19, 2026
04be732
Fix previous image/pause state keep the media duration
neon-nyan Oct 19, 2026
63da649
Ensure MagicScaler decoder initialized synchronously
neon-nyan Oct 19, 2026
89c2bb6
Remove unused remote FileSize check
neon-nyan Oct 19, 2026
bde82d8
Fix same background gate logic
neon-nyan Oct 20, 2026
a6565f5
Fix late OnApplyTemplate race causing crash
neon-nyan Oct 20, 2026
76bc631
Remove IsContextEqual
neon-nyan Oct 20, 2026
94e35cc
Fix ForceReload always gets unnecessarily called.
neon-nyan Oct 20, 2026
834bb30
Fix Catastrophic crash (0x0000FFFF) on element unload
neon-nyan Oct 20, 2026
1c7df42
Use 4:4:4 Chroma Sampling and 4:0:0 for monochrome
neon-nyan Oct 20, 2026
96383a7
Fix placeholder image randomization
neon-nyan Oct 20, 2026
3e5c22c
Fix Locale OOBE text binding and theme adjustment
neon-nyan Oct 20, 2026
87b4d25
Adjust OOBE on custom image with current implementation
neon-nyan Oct 20, 2026
2c08f66
Fix crash on OnUnloaded while element template not initialized
neon-nyan Oct 20, 2026
117f6e9
Use default placeholder if either current or global image is null
neon-nyan Oct 20, 2026
e7063c8
Fix OOBE image loading and externally decoded .avif images
neon-nyan Oct 21, 2026
e3d92f3
Merge branch 'main' into feat/mt-bg-renderer
neon-nyan Oct 21, 2026
2e32a9d
Merge branch 'main' into feat/mt-bg-renderer
neon-nyan Oct 21, 2026
e41427a
Merge branch 'main' into feat/mt-bg-renderer
neon-nyan Oct 21, 2026
6b88409
[skip ci] Update submodule
neon-nyan Oct 21, 2026
add4100
Merge branch 'main' into feat/mt-bg-renderer
neon-nyan Oct 21, 2026
a7c36c1
[skip ci] :facepalm:
neon-nyan Oct 21, 2026
9453936
Fix cached image files not being deleted
neon-nyan Oct 21, 2026
3fd5a3e
Always use Shared CanvasDevice from now on
neon-nyan Oct 21, 2026
859271c
Bring back copy frame feature
neon-nyan Oct 21, 2026
ffecfc3
Avoid GC losing track of disposed WinRT objects
neon-nyan Oct 21, 2026
a222bc9
Simplify HEIF stream loading + remove unused codes
neon-nyan Oct 21, 2026
cedd81b
Revert small sprites back to .png
neon-nyan Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CollapseLauncher.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
<Project Path="Hi3Helper.LocaleSourceGen/Hi3Helper.LocaleSourceGen.csproj">
<Platform Project="x64" />
</Project>
<Project Path="Hi3Helper.MagicScaler.LibheifCL/Hi3Helper.MagicScaler.LibheifCL.csproj">
<BuildType Solution="Publish|*" Project="Release" />
<Platform Project="x64" />
</Project>
<Project Path="Hi3Helper.Plugin.Core/Hi3Helper.Plugin.Core.csproj">
<BuildType Solution="Publish|*" Project="Release" />
<Platform Project="x64" />
Expand Down
6 changes: 0 additions & 6 deletions CollapseLauncher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,6 @@ protected override void OnLaunched(LaunchActivatedEventArgs args)
{
ImageLoaderHelper.InitWaifu2X();
}

// Initialize support for MagicScaler's WebP decoding
CodecManager.Configure(codecs =>
{
codecs.UseLibwebp();
});
}
catch (Exception ex)
{
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
// | Gradient stops | 6 |
// | CompositionVisualSurface | 42 |
// ------------------------------------

using CollapseLauncher.GameManagement.ImageBackground;
using CollapseLauncher.Helper.Image;
using Hi3Helper.Shared.Region;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
Expand All @@ -55,6 +58,7 @@
using System.ComponentModel;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI;

Expand Down Expand Up @@ -156,9 +160,9 @@ void EnsureImageLoadingStarted()
if (!_isImageLoadingStarted)
{
var eventHandler = new TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs>(HandleLoadCompleted);
_image_image_0 = LoadedImageSurface.StartLoadFromStream(File.OpenRead(Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\CollapseLauncherLogo.png")).AsRandomAccessStream());
_image_image_0 = LoadedImageSurface.StartLoadFromStream(File.OpenRead(Path.Combine(LauncherConfig.AppExecutableDir, @"Assets\CollapseLauncherLogo.png")).AsRandomAccessStream());
_image_image_0.LoadCompleted += eventHandler;
_isImageLoadingStarted = true;
_isImageLoadingStarted = true;
}
}

Expand Down
16 changes: 8 additions & 8 deletions CollapseLauncher/Classes/Extension/UIElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ internal static void SetAllControlsCursorRecursive(this UIElement? element, Inpu
}
}

internal static void BindProperty(this FrameworkElement element,
object? objectToBind,
string propertyName,
DependencyProperty dependencyProperty,
BindingMode bindingMode,
IValueConverter? converter = null)
{
element.SetBinding(dependencyProperty, new Binding
internal static void BindProperty(this FrameworkElement? element,
object? objectToBind,
string propertyName,
DependencyProperty dependencyProperty,
BindingMode bindingMode,
IValueConverter? converter = null)
{
element?.SetBinding(dependencyProperty, new Binding
{
Source = objectToBind,
Mode = bindingMode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CollapseLauncher.Extension;
using CollapseLauncher.XAMLs.Theme.CustomControls;
using CollapseLauncher.XAMLs.Theme.CustomControls;
using Hi3Helper;
using Hi3Helper.Win32.ManagedTools;
using Hi3Helper.Win32.Native.Enums;
Expand All @@ -8,13 +7,11 @@
using Hi3Helper.Win32.WinRT.WindowsStream;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.UI.Dispatching;
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Graphics.DirectX;
using Windows.Graphics.DirectX.Direct3D11;
Expand All @@ -30,10 +27,8 @@ public static class ClipboardUtility
{
public static async void CopyCurrentFrameToClipboard(
ImageBackgroundManager? instance,
FrameToCopyType frameToCopyType)
FrameToCopyType frameToCopyType)
{
bool isBackgroundVideo = false;

try
{
if (instance == null)
Expand All @@ -43,157 +38,21 @@ public static async void CopyCurrentFrameToClipboard(

bool hasBackground = frameToCopyType.HasFlag(FrameToCopyType.Background);
bool hasForeground = frameToCopyType.HasFlag(FrameToCopyType.Foreground);
bool hasBoth = frameToCopyType == FrameToCopyType.Both;

// Try to get video frame buffer first (if the background is video).
LayeredBackgroundImage? currentElement = instance.CurrentBackgroundElement;
bool isStaticBackgroundShowing = currentElement is { BackgroundStaticSource: not null, IsVideoPlay: false };

string? usedBackgroundPath = isStaticBackgroundShowing
? instance.CurrentSelectedBackgroundContext?
.OriginBackgroundStaticImagePath
: instance.CurrentSelectedBackgroundContext?
.OriginBackgroundImagePath;

CanvasRenderTarget? backgroundCanvas = hasBackground
? isStaticBackgroundShowing ? null : currentElement?.LockCanvasRenderTarget()
: null;

ICanvasResourceCreator? canvasDevice = backgroundCanvas;
canvasDevice ??= CanvasDevice.GetSharedDevice();

if (backgroundCanvas != null)
{
// Copy bitmap to avoid arbitrary write to video frame buffer.
CanvasRenderTarget oldCanvas = backgroundCanvas;
backgroundCanvas = new CanvasRenderTarget(canvasDevice, backgroundCanvas.Size._width, backgroundCanvas.Size._height, 96f);
backgroundCanvas.CopyPixelsFromBitmap(oldCanvas);

isBackgroundVideo = true; // Mark as video so we can unlock the frame later.
}

// If it's still null (which most likely an image), get the canvas.
backgroundCanvas ??= hasBackground
? await GetImageCanvasTargetBuffer(canvasDevice,
usedBackgroundPath)
: await GetImageCanvasTargetBuffer(canvasDevice,
instance.CurrentSelectedBackgroundContext?
.OriginOverlayImagePath);

// If background canvas is still null, abort.
if (backgroundCanvas == null)
{
if (currentElement == null)
return;
}

// Get foreground canvas if available.
CanvasRenderTarget? foregroundCanvas = hasBoth && hasForeground
? await GetImageCanvasTargetBuffer(canvasDevice,
instance.CurrentSelectedBackgroundContext?
.OriginOverlayImagePath)
: null;

int widthOfForeground = (int)(foregroundCanvas?.SizeInPixels.Width ?? 0u);
int heightOfForeground = (int)(foregroundCanvas?.SizeInPixels.Height ?? 0u);
if (foregroundCanvas != null &&
(backgroundCanvas.SizeInPixels.Width != widthOfForeground ||
backgroundCanvas.SizeInPixels.Height != heightOfForeground))
{
backgroundCanvas = ResizeCanvasToSizeOf(backgroundCanvas, widthOfForeground, heightOfForeground);
}

MergeCanvasTargetWith(backgroundCanvas, foregroundCanvas); // Merge overlay (foreground) to background.
await CopyCanvasTargetBufferToClipboard(backgroundCanvas); // Copy to clipboard
using CanvasRenderTarget? sourceFrameCanvas = await currentElement.CopyCurrentFrame(hasBackground, hasForeground);
await CopyCanvasTargetBufferToClipboard(sourceFrameCanvas); // Copy to clipboard
}
catch (Exception ex)
{
Logger.LogWriteLine($"[ImageBackgroundManager::CopyCurrentFrameToClipboard] An error occurred while trying to copy current background frame to clipboard\r\n{ex}",
LogType.Error,
true);
}
finally
{
if (isBackgroundVideo)
{
instance?.CurrentBackgroundElement?.UnlockCanvasRenderTarget();
}
}
}

private static CanvasRenderTarget ResizeCanvasToSizeOf(CanvasRenderTarget canvasToResize, int width, int height)
{
CanvasRenderTarget resizedCanvas = new(canvasToResize,
width,
height,
canvasToResize.Dpi);
using CanvasDrawingSession ds = resizedCanvas.CreateDrawingSession();
ds.DrawImage(canvasToResize,
new Windows.Foundation.Rect(0, 0, width, height),
new Windows.Foundation.Rect(0, 0,
canvasToResize.SizeInPixels.Width,
canvasToResize.SizeInPixels.Height),
1f,
CanvasImageInterpolation.HighQualityCubic);

canvasToResize.Dispose();
return resizedCanvas;
}

private static async Task<CanvasRenderTarget?> GetImageCanvasTargetBuffer(ICanvasResourceCreator canvasDevice, string? path)
{
if (string.IsNullOrEmpty(path))
{
return null; // Ignore if path is null or empty.
}

// Get source image as native or decoded image (if it requires external codec decoding).
Uri sourceUri = new(path);
(sourceUri, _) = await ImageBackgroundManager
.GetNativeOrDecodedImagePath(sourceUri, CancellationToken.None);

using CanvasBitmap bitmap = sourceUri.IsFile ?
await CanvasBitmap.LoadAsync(canvasDevice,
sourceUri.LocalPath,
96f,
CanvasAlphaMode.Premultiplied) :
await CanvasBitmap.LoadAsync(canvasDevice,
sourceUri,
96f,
CanvasAlphaMode.Premultiplied);

// Load to render target
return await Task.Run(() =>
{
CanvasRenderTarget renderTarget = new(canvasDevice,
bitmap.SizeInPixels.Width,
bitmap.SizeInPixels.Height, bitmap.Dpi);
using CanvasDrawingSession ds = renderTarget.CreateDrawingSession();
ds.DrawImage(bitmap);
return renderTarget;
});
}

private static void MergeCanvasTargetWith(CanvasRenderTarget sourceCanvasTarget,
CanvasRenderTarget? withCanvasTarget)
{
if (withCanvasTarget == null)
{
return; // Abort
}

CanvasDrawingSession? ds = sourceCanvasTarget.CreateDrawingSession();
ds.DrawImage(withCanvasTarget);

// Dispose the overlay canvas
withCanvasTarget.Dispose();

if (DispatcherQueueExtensions.CurrentDispatcherQueue.HasThreadAccess)
{
ds.Dispose();
return;
}

DispatcherQueueExtensions.CurrentDispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, ds.Dispose);
}

private static double GetBitmapBitSize(DirectXPixelFormat pixelFormat)
Expand Down Expand Up @@ -300,9 +159,7 @@ private static async Task<int> WriteCanvasTargetBufferType(
private static async Task CopyCanvasTargetBufferToClipboard(CanvasRenderTarget? sourceCanvasTarget)
{
if (sourceCanvasTarget == null)
{
return;
}

bool isClipboardOpened = true;
byte[]? buffer = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
using System;
using System.IO;
using System.Threading.Tasks;

// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
// ReSharper disable CheckNamespace
#pragma warning disable IDE0130

#nullable enable
namespace CollapseLauncher.GameManagement.ImageBackground;
Expand All @@ -18,72 +19,60 @@ public partial class ImageBackgroundManager
{
#region Codec Checks

private async ValueTask<(bool IsSupported, bool IsVideo, bool ForceFFmpeg)> CheckCodecOrSpawnDialog(Uri? fileUri)
private async ValueTask<(bool IsSupported, bool ForceFFmpeg)> CheckCodecOrSpawnDialog(string? localPath)
{
// -- Cancel if null or URI is not a local file
if (fileUri == null || !fileUri.IsFile)
{
return (false, false, false);
}

string filePath = fileUri.LocalPath;
if (localPath == null)
return (false, false);

// -- Check for supported extension first
if (!IsMediaFileExtensionSupported(filePath))
if (!IsMediaFileExtensionSupported(localPath))
{
await SimpleDialogs.Dialog_SpawnMediaExtensionNotSupportedDialog(filePath);
return (false, false, false);
await SimpleDialogs.Dialog_SpawnMediaExtensionNotSupportedDialog(localPath);
return (false, false);
}

// -- Check for supported image codec
if (IsImageMediaFileExtensionSupported(filePath))
if (IsImageMediaFileExtensionSupported(localPath))
{
if (WindowsCodecHelper.IsFileSupportedImage(filePath))
if (WindowsCodecHelper.IsFileSupportedImage(localPath))
{
return (true, false, false);
return (true, false);
}

await SimpleDialogs.Dialog_SpawnImageNotSupportedDialog(filePath);
return (false, false, false);
await SimpleDialogs.Dialog_SpawnImageNotSupportedDialog(localPath);
return (false, false);
}

// -- Check using FFmpeg if available
MediaSupport codecInfo = FFmpegPInvoke.GetMediaSupport(filePath);
MediaSupport codecInfo = FFmpegPInvoke.GetMediaSupport(localPath);
PixelColorModel pixelFormat = codecInfo.VideoPixelFormatInfo.ColorModel;
if ((GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable) ||
RequiresFFmpegDecoder(codecInfo))
(codecInfo.Video.DecoderAvailable && pixelFormat.HasFlag(PixelColorModel.Rgb)))
{
return (true, true, true);
return (true, true);
}

// -- Check for supported video codec
if (WindowsCodecHelper.IsFileSupportedVideo(filePath,
if (WindowsCodecHelper.IsFileSupportedVideo(localPath,
out bool canPlayVideo,
out bool canPlayAudio,
out Guid videoCodecGuid,
out Guid audioCodecGuid) ||
(GlobalIsUseFFmpeg && GlobalIsFFmpegAvailable))
{
return (true, true, true);
return (true, false);
}

return (await SimpleDialogs
.Dialog_SpawnVideoNotSupportedDialog(filePath,
.Dialog_SpawnVideoNotSupportedDialog(localPath,
canPlayVideo,
canPlayAudio,
videoCodecGuid,
audioCodecGuid), true, false);
audioCodecGuid), false);
}

#endregion

private static bool RequiresFFmpegDecoder(MediaSupport codecInfo)
{
// Windows' H.264 decoder does not support the High 10 profile used by some backgrounds.
return codecInfo.Video.DecoderAvailable &&
(codecInfo.VideoPixelFormatInfo.ColorModel.HasFlag(PixelColorModel.Rgb) ||
codecInfo.VideoPixelFormatInfo.Format is AVPixelFormat.AV_PIX_FMT_YUV420P10LE or AVPixelFormat.AV_PIX_FMT_YUV420P10BE);
}

private static bool IsMediaFileExtensionSupported(ReadOnlySpan<char> filePath)
{
return IsImageMediaFileExtensionSupported(filePath) ||
Expand All @@ -98,7 +87,7 @@ private static bool IsImageMediaFileExtensionSupported(ReadOnlySpan<char> filePa
LayeredBackgroundImage.SupportedImageVectorExtensionsLookup.Contains(extension);
}

private static bool IsVideoMediaFileExtensionSupported(ReadOnlySpan<char> filePath)
public static bool IsVideoMediaFileExtensionSupported(ReadOnlySpan<char> filePath)
{
ReadOnlySpan<char> extension = Path.GetExtension(filePath);
return LayeredBackgroundImage.SupportedVideoExtensionsLookup.Contains(extension);
Expand Down
Loading
Loading