// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Processing; namespace osu.Game.Beatmaps { // Implementation of this class is based off of `MaxDimensionLimitedTextureLoaderStore`. // If issues are found it's worth checking to make sure similar issues exist there. public class BeatmapPanelBackgroundTextureLoaderStore : IResourceStore { // The aspect ratio of SetPanelBackground at its maximum size (very tall window). private const float minimum_display_ratio = 512 / 80f; private readonly IResourceStore? textureStore; public BeatmapPanelBackgroundTextureLoaderStore(IResourceStore? textureStore) { this.textureStore = textureStore; } public void Dispose() { textureStore?.Dispose(); } public TextureUpload Get(string name) { var textureUpload = textureStore?.Get(name); // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. if (textureUpload == null) return null!; return limitTextureUploadSize(textureUpload); } public async Task GetAsync(string name, CancellationToken cancellationToken = new CancellationToken()) { // NRT not enabled on framework side classes (IResourceStore / TextureLoaderStore), welp. if (textureStore == null) return null!; var textureUpload = await textureStore.GetAsync(name, cancellationToken).ConfigureAwait(false); if (textureUpload == null) return null!; return await Task.Run(() => limitTextureUploadSize(textureUpload), cancellationToken).ConfigureAwait(false); } private TextureUpload limitTextureUploadSize(TextureUpload textureUpload) { var image = Image.LoadPixelData(textureUpload.Data.ToArray(), textureUpload.Width, textureUpload.Height); // The original texture upload will no longer be returned or used. textureUpload.Dispose(); Size size = image.Size(); // Assume that panel backgrounds are always displayed using `FillMode.Fill`. // Also assume that all backgrounds are wider than they are tall, so the // fill is always going to be based on width. // // We need to include enough height to make this work for all ratio panels are displayed at. int usableHeight = (int)Math.Ceiling(size.Width * 1 / minimum_display_ratio); usableHeight = Math.Min(size.Height, usableHeight); // Crop the centre region of the background for now. Rectangle cropRectangle = new Rectangle( 0, (size.Height - usableHeight) / 2, size.Width, usableHeight ); image.Mutate(i => i.Crop(cropRectangle)); return new TextureUpload(image); } public Stream? GetStream(string name) => textureStore?.GetStream(name); public IEnumerable GetAvailableResources() => textureStore?.GetAvailableResources() ?? Array.Empty(); } }