diff --git a/OpenRA.Game/Graphics/SheetBuilder.cs b/OpenRA.Game/Graphics/SheetBuilder.cs index c96bddaf0f..f1389aefb8 100644 --- a/OpenRA.Game/Graphics/SheetBuilder.cs +++ b/OpenRA.Game/Graphics/SheetBuilder.cs @@ -82,16 +82,16 @@ namespace OpenRA.Graphics this.margin = margin; } - public Sprite Add(ISpriteFrame frame) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset); } - public Sprite Add(byte[] src, SpriteFrameType type, Size size) { return Add(src, type, size, 0, float3.Zero); } - public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset) + public Sprite Add(ISpriteFrame frame, bool premultiplied = false) { return Add(frame.Data, frame.Type, frame.Size, 0, frame.Offset, premultiplied); } + public Sprite Add(byte[] src, SpriteFrameType type, Size size, bool premultiplied = false) { return Add(src, type, size, 0, float3.Zero, premultiplied); } + public Sprite Add(byte[] src, SpriteFrameType type, Size size, float zRamp, in float3 spriteOffset, bool premultiplied = false) { // Don't bother allocating empty sprites if (size.Width == 0 || size.Height == 0) return new Sprite(Current, Rectangle.Empty, 0, spriteOffset, CurrentChannel, BlendMode.Alpha); var rect = Allocate(size, zRamp, spriteOffset); - Util.FastCopyIntoChannel(rect, src, type); + Util.FastCopyIntoChannel(rect, src, type, premultiplied); Current.CommitBufferedData(); return rect; } diff --git a/OpenRA.Game/Graphics/SpriteCache.cs b/OpenRA.Game/Graphics/SpriteCache.cs index c981b5e342..e43d375446 100644 --- a/OpenRA.Game/Graphics/SpriteCache.cs +++ b/OpenRA.Game/Graphics/SpriteCache.cs @@ -24,7 +24,7 @@ namespace OpenRA.Graphics readonly ISpriteLoader[] loaders; readonly IReadOnlyFileSystem fileSystem; - readonly Dictionary spriteReservations = new(); + readonly Dictionary spriteReservations = new(); readonly Dictionary frameReservations = new(); readonly Dictionary> reservationsByFilename = new(); @@ -47,10 +47,10 @@ namespace OpenRA.Graphics this.loaders = loaders; } - public int ReserveSprites(string filename, IEnumerable frames, MiniYamlNode.SourceLocation location) + public int ReserveSprites(string filename, IEnumerable frames, MiniYamlNode.SourceLocation location, bool premultiplied = false) { var token = nextReservationToken++; - spriteReservations[token] = (frames?.ToArray(), location); + spriteReservations[token] = (frames?.ToArray(), location, premultiplied); reservationsByFilename.GetOrAdd(filename, _ => new List()).Add(token); return token; } @@ -91,14 +91,14 @@ namespace OpenRA.Graphics var loadedFrames = GetFrames(fileSystem, filename, loaders, out _); foreach (var token in tokens) { - if (frameReservations.TryGetValue(token, out var r)) + if (frameReservations.TryGetValue(token, out var rf)) { if (loadedFrames != null) { - if (r.Frames != null) + if (rf.Frames != null) { var resolved = new ISpriteFrame[loadedFrames.Length]; - foreach (var i in r.Frames) + foreach (var i in rf.Frames) resolved[i] = loadedFrames[i]; resolvedFrames[token] = resolved; } @@ -108,26 +108,32 @@ namespace OpenRA.Graphics else { resolvedFrames[token] = null; - missingFiles[token] = (filename, r.Location); + missingFiles[token] = (filename, rf.Location); } } - if (spriteReservations.TryGetValue(token, out r)) + if (spriteReservations.TryGetValue(token, out var rs)) { if (loadedFrames != null) { var resolved = new Sprite[loadedFrames.Length]; - var frames = r.Frames ?? Enumerable.Range(0, loadedFrames.Length); + var frames = rs.Frames ?? Enumerable.Range(0, loadedFrames.Length); + + // Premultiplied and non-premultiplied sprites must be cached separately + // to cover the case where the same image is requested in both versions. + // The premultiplied sprites are stored with an index offset for efficiency + // rather than allocating a second dictionary. + var di = rs.Premultiplied ? loadedFrames.Length : 0; foreach (var i in frames) - resolved[i] = spriteCache.GetOrAdd(i, - f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f].Type)].Add(loadedFrames[f])); + resolved[i] = spriteCache.GetOrAdd(i + di, + f => SheetBuilders[SheetBuilder.FrameTypeToSheetType(loadedFrames[f - di].Type)].Add(loadedFrames[f - di], rs.Premultiplied)); resolvedSprites[token] = resolved; } else { resolvedSprites[token] = null; - missingFiles[token] = (filename, r.Location); + missingFiles[token] = (filename, rs.Location); } } } diff --git a/OpenRA.Game/Graphics/Util.cs b/OpenRA.Game/Graphics/Util.cs index a5669913fe..4df6d26c7c 100644 --- a/OpenRA.Game/Graphics/Util.cs +++ b/OpenRA.Game/Graphics/Util.cs @@ -103,7 +103,7 @@ namespace OpenRA.Graphics vertices[nv + 3] = new Vertex(d, r.Left, r.Bottom, sl, sb, uAttribC, tint, alpha); } - public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType) + public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType srcType, bool premultiplied = false) { var destData = dest.Sheet.GetData(); var width = dest.Bounds.Width; @@ -154,7 +154,10 @@ namespace OpenRA.Graphics } var cc = Color.FromArgb(a, r, g, b); - data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); + if (premultiplied) + data[(y + j) * destStride + x + i] = cc.ToArgb(); + else + data[(y + j) * destStride + x + i] = PremultiplyAlpha(cc).ToArgb(); } } }