From 01521661c261ce9b8fa9b54e6a5742be9bd0fbb1 Mon Sep 17 00:00:00 2001 From: Pink Serenity Date: Sat, 22 Nov 2025 12:35:57 +0100 Subject: [PATCH 1/2] Add PatchMode.Mask --- .../Framework/Content/AssetDataForImage.cs | 51 ++++++++++++++----- src/SMAPI/PatchMode.cs | 5 +- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index f59e73bea..f6c1e46a6 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -213,22 +213,47 @@ private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeigh // shortcut transparency if (above.A < AssetDataForImage.MinOpacity) continue; - if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue) - mergedData[targetIndex] = above; - // merge pixels + if (patchMode == PatchMode.Overlay) + { + if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue) + mergedData[targetIndex] = above; + + // merge pixels + else + { + // This performs a conventional alpha blend for the pixels, which are already + // premultiplied by the content pipeline. The formula is derived from + // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. + float alphaBelow = 1 - (above.A / 255f); + mergedData[targetIndex] = new Color( + r: (int)(above.R + (below.R * alphaBelow)), + g: (int)(above.G + (below.G * alphaBelow)), + b: (int)(above.B + (below.B * alphaBelow)), + alpha: Math.Max(above.A, below.A) + ); + } + } else { - // This performs a conventional alpha blend for the pixels, which are already - // premultiplied by the content pipeline. The formula is derived from - // https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/. - float alphaBelow = 1 - (above.A / 255f); - mergedData[targetIndex] = new Color( - r: (int)(above.R + (below.R * alphaBelow)), - g: (int)(above.G + (below.G * alphaBelow)), - b: (int)(above.B + (below.B * alphaBelow)), - alpha: Math.Max(above.A, below.A) - ); + // compute new alpha by subtracting mask alpha + int newAlphaInt = below.A - above.A; + + // fully masked out + if (newAlphaInt <= 0) + mergedData[targetIndex] = new Color((byte)0, (byte)0, (byte)0, (byte)0); + + else + { + // This blends the pixels by removing the alpha defined in the mask. + float scale = (float)newAlphaInt / below.A; + mergedData[targetIndex] = new Color( + r: (int)Math.Clamp(Math.Round(below.R * scale), 0, 255), + g: (int)Math.Clamp(Math.Round(below.G * scale), 0, 255), + b: (int)Math.Clamp(Math.Round(below.B * scale), 0, 255), + alpha: newAlphaInt + ); + } } } diff --git a/src/SMAPI/PatchMode.cs b/src/SMAPI/PatchMode.cs index 8575e5d0b..669a0f035 100644 --- a/src/SMAPI/PatchMode.cs +++ b/src/SMAPI/PatchMode.cs @@ -7,5 +7,8 @@ public enum PatchMode Replace, /// Draw the new content over the original content, so the original content shows through any transparent or semi-transparent pixels. - Overlay + Overlay, + + /// Masks the original content so that the alpha value of every pixel in the new content gets subtracted from the corresponding pixel in the original content. + Mask } From 0d403fcebce07d65b2228b628db8a3610b8e8ab9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 28 Dec 2025 18:26:39 -0500 Subject: [PATCH 2/2] tweak image patching code This optimizes slightly and expands docs. --- .../Framework/Content/AssetDataForImage.cs | 32 +++++++++---------- src/SMAPI/PatchMode.cs | 3 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index f6c1e46a6..e27dc1cab 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -205,21 +205,21 @@ private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeigh for (int i = startIndex; i <= endIndex; i++) { - int targetIndex = i - sourceOffset; - + // get source pixel Color above = sourceData[i]; - Color below = mergedData[targetIndex]; - - // shortcut transparency if (above.A < AssetDataForImage.MinOpacity) continue; + // get target pixel + int targetIndex = i - sourceOffset; + Color below = mergedData[targetIndex]; + + // apply if (patchMode == PatchMode.Overlay) { + // merge pixels if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue) mergedData[targetIndex] = above; - - // merge pixels else { // This performs a conventional alpha blend for the pixels, which are already @@ -236,22 +236,20 @@ private void PatchImageImpl(Color[] sourceData, int sourceWidth, int sourceHeigh } else { - // compute new alpha by subtracting mask alpha - int newAlphaInt = below.A - above.A; - - // fully masked out - if (newAlphaInt <= 0) - mergedData[targetIndex] = new Color((byte)0, (byte)0, (byte)0, (byte)0); - + // subtract mask alpha + int newAlpha = below.A - above.A; + if (newAlpha <= 0) + mergedData[targetIndex] = Color.Transparent; else { - // This blends the pixels by removing the alpha defined in the mask. - float scale = (float)newAlphaInt / below.A; + // Since the pixels are already premultiplied by the pipeline based on the + // alpha, rescale the RGB channels too to match the new alpha. + float scale = (float)newAlpha / below.A; mergedData[targetIndex] = new Color( r: (int)Math.Clamp(Math.Round(below.R * scale), 0, 255), g: (int)Math.Clamp(Math.Round(below.G * scale), 0, 255), b: (int)Math.Clamp(Math.Round(below.B * scale), 0, 255), - alpha: newAlphaInt + alpha: newAlpha ); } } diff --git a/src/SMAPI/PatchMode.cs b/src/SMAPI/PatchMode.cs index 669a0f035..352df15c8 100644 --- a/src/SMAPI/PatchMode.cs +++ b/src/SMAPI/PatchMode.cs @@ -9,6 +9,7 @@ public enum PatchMode /// Draw the new content over the original content, so the original content shows through any transparent or semi-transparent pixels. Overlay, - /// Masks the original content so that the alpha value of every pixel in the new content gets subtracted from the corresponding pixel in the original content. + /// Apply the new content over the original content as a transparency mask. + /// This subtracts the alpha value of each pixel in the new content from the corresponding pixel in the original content. Colors in the new content are ignored. For example, a fully opaque pixel in the new content will result in a fully transparent pixel in the final image. Mask }