- Changed scroll transition on video/screenshot carousel to use RenderTransform instead of margin to cut down on layout passes for each frame. This should result in a significant drop in CPU usage when scrolling. - Pause playback of videos when scrolled out of view or window is no longer active. - Removed additional InlineVideoPlayer controls from being created for each item in the media carousel, and instead only created for videos. This should result in a significant drop in RAM allocation when scrolling. - Rework caching mechanism for covers to have an upper limit of allocated RAM. - Only decode screenshots / covers at Hi DPI size in which they are displayed at. This should ensure crisp display while only allocating the amount of RAM needed for display.
74 lines
3.1 KiB
C#
74 lines
3.1 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using Avalonia.Data.Converters;
|
||
using Avalonia.Media.Imaging;
|
||
|
||
namespace LANCommander.Launcher.Converters;
|
||
|
||
/// <summary>
|
||
/// Converts a file path string to a thumbnail Bitmap.
|
||
/// Decodes at a fixed width so full-resolution source images don't consume
|
||
/// excessive memory, and caches bitmaps via WeakReference so they are freed
|
||
/// by the GC once no control is displaying them (e.g. after virtualization
|
||
/// scrolls them out of view) and reloaded on next access.
|
||
/// </summary>
|
||
public class FilePathToBitmapConverter : IValueConverter
|
||
{
|
||
// Covers are displayed at 140–220 px wide. 320 px gives enough headroom
|
||
// for high-DPI screens while keeping per-image memory around 400 KB
|
||
// instead of several MB for full-resolution sources.
|
||
private const int DecodeWidth = 320;
|
||
|
||
// WeakReference cache: keeps bitmaps alive while on-screen, lets the GC
|
||
// collect them once nothing holds a strong reference (i.e. off-screen).
|
||
// Key is "{path}|{decodeWidth}" so full-res and thumbnail don't collide.
|
||
private static readonly Dictionary<string, WeakReference<Bitmap>> _cache = new();
|
||
|
||
public static readonly FilePathToBitmapConverter Instance = new();
|
||
|
||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||
{
|
||
if (value is not string path || string.IsNullOrEmpty(path))
|
||
return null;
|
||
|
||
// ConverterParameter="full" → full resolution
|
||
// ConverterParameter="h{N}" → DecodeToHeight(N)
|
||
// ConverterParameter="w{N}" → DecodeToWidth(N) (e.g. game detail background)
|
||
// default → DecodeToWidth(320)
|
||
bool fullRes = parameter is string p && p == "full";
|
||
int? decodeHeight = parameter is string hp && hp.StartsWith("h") && int.TryParse(hp.AsSpan(1), out var h) ? h : null;
|
||
int? decodeWidth = parameter is string wp && wp.StartsWith("w") && int.TryParse(wp.AsSpan(1), out var w) ? w : null;
|
||
|
||
var cacheKey = fullRes ? $"{path}|full"
|
||
: decodeHeight.HasValue ? $"{path}|h{decodeHeight}"
|
||
: decodeWidth.HasValue ? $"{path}|w{decodeWidth}"
|
||
: $"{path}|{DecodeWidth}";
|
||
|
||
if (_cache.TryGetValue(cacheKey, out var weakRef) && weakRef.TryGetTarget(out var cached))
|
||
return cached;
|
||
|
||
try
|
||
{
|
||
if (!File.Exists(path))
|
||
return null;
|
||
|
||
using var stream = File.OpenRead(path);
|
||
var bitmap = fullRes
|
||
? new Bitmap(stream)
|
||
: decodeHeight.HasValue
|
||
? Bitmap.DecodeToHeight(stream, decodeHeight.Value, BitmapInterpolationMode.HighQuality)
|
||
: Bitmap.DecodeToWidth(stream, decodeWidth ?? DecodeWidth, BitmapInterpolationMode.HighQuality);
|
||
_cache[cacheKey] = new WeakReference<Bitmap>(bitmap);
|
||
return bitmap;
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
||
throw new NotSupportedException();
|
||
}
|