ReadieFur / BSDataPuller

Gathers data about the current map you are playing to then be sent out over a websocket for other software to use, e.g. A web overlay like BSDP-Overlay. This mod works with multi PC setups!
https://github.com/ReadieFur/BeatSaber-Overlay
GNU General Public License v3.0
39 stars 10 forks source link

[Fix/Feature Request] CoverImage for non-BeatSaver songs #28

Open ChrisJAllan opened 1 year ago

ChrisJAllan commented 1 year ago

I have a working build that just copies the texture-reading code from HTTP Status, but I don't think it's pull-request-worthy.

diff --git a/src/Core/MapEvents.cs b/src/Core/MapEvents.cs
index 4dbbc91..55fa3db 100644
--- a/src/Core/MapEvents.cs
+++ b/src/Core/MapEvents.cs
@@ -178,7 +178,7 @@ namespace DataPuller.Core
             MapData.Instance.Send();
         }

-        public void LevelLoaded()
+        public async void LevelLoaded()
         {
             PlayerData playerData = Resources.FindObjectsOfTypeAll<PlayerDataModel>().FirstOrDefault().playerData;
             IBeatmapLevel levelData = gameplayCoreSceneSetupData.difficultyBeatmap.level;
@@ -204,6 +204,40 @@ namespace DataPuller.Core
             MapData.Instance.Difficulty = gameplayCoreSceneSetupData.difficultyBeatmap.difficulty.ToString("g");
             MapData.Instance.NJS = gameplayCoreSceneSetupData.difficultyBeatmap.noteJumpMovementSpeed;
             MapData.Instance.CustomDifficultyLabel = difficultyData?._difficultyLabel ?? null;
+            
+            // From HTTPStatus
+            try {
+                // From https://support.unity3d.com/hc/en-us/articles/206486626-How-can-I-get-pixels-from-unreadable-textures-
+                // Modified to correctly handle texture atlases. Fixes #82.
+                var active = RenderTexture.active;
+
+                var sprite = await levelData.GetCoverImageAsync(System.Threading.CancellationToken.None);
+                var texture = sprite.texture;
+                var temporary = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
+
+                Graphics.Blit(texture, temporary);
+                RenderTexture.active = temporary;
+
+                var spriteRect = sprite.rect;
+                var uv = sprite.uv[0];
+
+                var cover = new Texture2D((int) spriteRect.width, (int) spriteRect.height);
+                // Unity sucks. The coordinates of the sprite on its texture atlas are only accessible through the Sprite.uv property since rect always returns `x=0,y=0`, so we need to convert them back into texture space.
+                cover.ReadPixels(new Rect(
+                    uv.x * texture.width,
+                    texture.height - uv.y * texture.height,
+                    spriteRect.width,
+                    spriteRect.height
+                ), 0, 0);
+                cover.Apply();
+
+                RenderTexture.active = active;
+                RenderTexture.ReleaseTemporary(temporary);
+
+                MapData.Instance.CoverImage = "data:image/png;base64," + System.Convert.ToBase64String(ImageConversion.EncodeToPNG(cover));
+            } catch {
+                MapData.Instance.CoverImage = null;
+            }

             if (isCustomLevel)
             {
@@ -267,7 +301,6 @@ namespace DataPuller.Core
                         else
                         {
                             MapData.Instance.BSRKey = null;
-                            MapData.Instance.CoverImage = null;
                         }
                         MapData.Instance.Send();
                     });
DJDavid98 commented 6 months ago

For what it's worth, I have fixed this as of v2.1.11 in my fork using your solution as a base