Skip to content

Commit

Permalink
Support loading sprites with pre-multiplied alpha.
Browse files Browse the repository at this point in the history
  • Loading branch information
pchote authored and PunkPun committed Oct 27, 2023
1 parent 37ce5e4 commit 8503678
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 18 deletions.
8 changes: 4 additions & 4 deletions OpenRA.Game/Graphics/SheetBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,16 @@ public SheetBuilder(SheetType t, Func<Sheet> allocateSheet, int margin = 1)
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;
}
Expand Down
30 changes: 18 additions & 12 deletions OpenRA.Game/Graphics/SpriteCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public sealed class SpriteCache : IDisposable
readonly ISpriteLoader[] loaders;
readonly IReadOnlyFileSystem fileSystem;

readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> spriteReservations = new();
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location, bool Premultiplied)> spriteReservations = new();
readonly Dictionary<int, (int[] Frames, MiniYamlNode.SourceLocation Location)> frameReservations = new();
readonly Dictionary<string, List<int>> reservationsByFilename = new();

Expand All @@ -47,10 +47,10 @@ public SpriteCache(IReadOnlyFileSystem fileSystem, ISpriteLoader[] loaders, int
this.loaders = loaders;
}

public int ReserveSprites(string filename, IEnumerable<int> frames, MiniYamlNode.SourceLocation location)
public int ReserveSprites(string filename, IEnumerable<int> 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<int>()).Add(token);
return token;
}
Expand Down Expand Up @@ -91,14 +91,14 @@ public void LoadReservations(ModData modData)
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;
}
Expand All @@ -108,26 +108,32 @@ public void LoadReservations(ModData modData)
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);
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions OpenRA.Game/Graphics/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public static uint[] CreateQuadIndices(int quads)
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;
Expand Down Expand Up @@ -154,7 +154,10 @@ public static void FastCopyIntoChannel(Sprite dest, byte[] src, SpriteFrameType
}

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();
}
}
}
Expand Down

0 comments on commit 8503678

Please sign in to comment.