diff --git a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
index f11ce9648..cb87fcb5a 100644
--- a/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
+++ b/src/SMAPI/Framework/ContentManagers/ModContentManager.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Buffers;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
@@ -45,6 +46,8 @@ internal sealed class ModContentManager : BaseContentManager
     /// <summary>If a map tilesheet's image source has no file extensions, the file extensions to check for in the local mod folder.</summary>
     private static readonly string[] LocalTilesheetExtensions = [".png", ".xnb"];
 
+    private readonly Dictionary<string, RawTextureData> TextureCache = [];
+
 
     /*********
     ** Public methods
@@ -216,10 +219,18 @@ private T LoadImageFile<T>(IAssetName assetName, FileInfo file)
     /// <param name="file">The file whose data to load.</param>
     /// <param name="forRawData">Whether the data is being loaded for an <see cref="IRawTextureData"/> (true) or <see cref="Texture2D"/> (false) instance.</param>
     /// <remarks>This is separate to let framework mods intercept the data before it's loaded, if needed.</remarks>
-    [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")]
-    [SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "The 'forRawData' parameter is only added for mods which may intercept this method.")]
     private IRawTextureData LoadRawImageData(FileInfo file, bool forRawData)
     {
+        if (this.TextureCache.TryGetValue(file.FullName, out var cacheResult))
+        {
+            var cacheColors = cacheResult.Data;
+            if (forRawData)
+            {
+                return new RawTextureData(cacheResult.Width, cacheResult.Height, (Color[])cacheColors.Clone());
+            }
+            return new RawTextureData(cacheResult.Width, cacheResult.Height, cacheColors);
+        }
+
         // load raw data
         int width;
         int height;
@@ -238,13 +249,27 @@ private IRawTextureData LoadRawImageData(FileInfo file, bool forRawData)
 
         // convert to XNA pixel format
         var pixels = GC.AllocateUninitializedArray<Color>(rawPixels.Length);
+        Color[]? pixelCache = null;
+        if (forRawData)
+            pixelCache = GC.AllocateUninitializedArray<Color>(rawPixels.Length);
         for (int i = 0; i < pixels.Length; i++)
         {
             SKPMColor pixel = rawPixels[i];
             pixels[i] = pixel.Alpha == 0
                 ? Color.Transparent
                 : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha);
+
+            if (pixelCache != null)
+            {
+                pixelCache[i] = pixel.Alpha == 0
+                    ? Color.Transparent
+                    : new Color(r: pixel.Red, g: pixel.Green, b: pixel.Blue, alpha: pixel.Alpha);
+            }
         }
+        if (pixelCache != null)
+            this.TextureCache.Add(file.FullName, new RawTextureData(width, height, pixelCache));
+        else
+            this.TextureCache.Add(file.FullName, new RawTextureData(width, height, pixels));
 
         return new RawTextureData(width, height, pixels);
     }