diff --git a/src/SMAPI/Framework/Content/AssetDataForImage.cs b/src/SMAPI/Framework/Content/AssetDataForImage.cs index f59e73bea..e27dc1cab 100644 --- a/src/SMAPI/Framework/Content/AssetDataForImage.cs +++ b/src/SMAPI/Framework/Content/AssetDataForImage.cs @@ -205,30 +205,53 @@ 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; - if (below.A < AssetDataForImage.MinOpacity || above.A == byte.MaxValue) - mergedData[targetIndex] = above; - // merge pixels + // 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; + 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) - ); + // subtract mask alpha + int newAlpha = below.A - above.A; + if (newAlpha <= 0) + mergedData[targetIndex] = Color.Transparent; + else + { + // 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: newAlpha + ); + } } } diff --git a/src/SMAPI/PatchMode.cs b/src/SMAPI/PatchMode.cs index 8575e5d0b..352df15c8 100644 --- a/src/SMAPI/PatchMode.cs +++ b/src/SMAPI/PatchMode.cs @@ -7,5 +7,9 @@ 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, + + /// 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 }