This is a more natural representation than int that allows removal of casts in many places that require uint. Additionally, we can change the internal representation from long to uint, making the Color struct smaller. Since arrays of colors are common, this can save on memory.
164 lines
5.3 KiB
C#
164 lines
5.3 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using OpenRA.FileFormats;
|
|
using OpenRA.Graphics;
|
|
using OpenRA.Primitives;
|
|
|
|
namespace OpenRA.Mods.Common.SpriteLoaders
|
|
{
|
|
public class PngSheetMetadata
|
|
{
|
|
public readonly ReadOnlyDictionary<string, string> Metadata;
|
|
|
|
public PngSheetMetadata(Dictionary<string, string> metadata)
|
|
{
|
|
Metadata = new ReadOnlyDictionary<string, string>(metadata);
|
|
}
|
|
}
|
|
|
|
public class PngSheetLoader : ISpriteLoader
|
|
{
|
|
sealed class PngSheetFrame : ISpriteFrame
|
|
{
|
|
public SpriteFrameType Type { get; set; }
|
|
public Size Size { get; set; }
|
|
public Size FrameSize { get; set; }
|
|
public float2 Offset { get; set; }
|
|
public byte[] Data { get; set; }
|
|
public bool DisableExportPadding => false;
|
|
}
|
|
|
|
public bool TryParseSprite(Stream s, string filename, out ISpriteFrame[] frames, out TypeDictionary metadata)
|
|
{
|
|
metadata = null;
|
|
frames = null;
|
|
if (!Png.Verify(s))
|
|
return false;
|
|
|
|
var png = new Png(s);
|
|
|
|
List<Rectangle> frameRegions;
|
|
List<float2> frameOffsets;
|
|
|
|
// Prefer manual defined regions over auto sliced regions.
|
|
if (png.EmbeddedData.Any(meta => meta.Key.StartsWith("Frame[", StringComparison.Ordinal)))
|
|
RegionsFromFrames(png, out frameRegions, out frameOffsets);
|
|
else
|
|
RegionsFromSlices(png, out frameRegions, out frameOffsets);
|
|
|
|
frames = new ISpriteFrame[frameRegions.Count];
|
|
var stride = png.PixelStride;
|
|
for (var i = 0; i < frames.Length; i++)
|
|
{
|
|
var frameStart = frameRegions[i].X + frameRegions[i].Y * png.Width;
|
|
var frameSize = new Size(frameRegions[i].Width, frameRegions[i].Height);
|
|
frames[i] = new PngSheetFrame()
|
|
{
|
|
Size = frameSize,
|
|
FrameSize = frameSize,
|
|
Offset = frameOffsets[i],
|
|
Data = new byte[frameRegions[i].Width * frameRegions[i].Height * stride],
|
|
Type = png.Type
|
|
};
|
|
|
|
for (var y = 0; y < frames[i].Size.Height; y++)
|
|
Array.Copy(png.Data, (frameStart + y * png.Width) * stride, frames[i].Data, y * frames[i].Size.Width * stride, frames[i].Size.Width * stride);
|
|
}
|
|
|
|
metadata = new TypeDictionary
|
|
{
|
|
new PngSheetMetadata(png.EmbeddedData),
|
|
};
|
|
|
|
if (png.Palette != null)
|
|
metadata.Add(new EmbeddedSpritePalette(png.Palette.Select(x => x.ToArgb()).ToArray()));
|
|
|
|
return true;
|
|
}
|
|
|
|
static void RegionsFromFrames(Png png, out List<Rectangle> regions, out List<float2> offsets)
|
|
{
|
|
regions = new List<Rectangle>();
|
|
offsets = new List<float2>();
|
|
var pngRectangle = new Rectangle(0, 0, png.Width, png.Height);
|
|
|
|
for (var i = 0; png.EmbeddedData.TryGetValue("Frame[" + i + "]", out var frame); i++)
|
|
{
|
|
// Format: x,y,width,height;offsetX,offsetY
|
|
var coords = frame.Split(';');
|
|
var region = FieldLoader.GetValue<Rectangle>("Region", coords[0]);
|
|
if (!pngRectangle.Contains(region))
|
|
throw new InvalidDataException($"Invalid frame regions {region} defined.");
|
|
|
|
regions.Add(region);
|
|
offsets.Add(FieldLoader.GetValue<float2>("Offset", coords[1]));
|
|
}
|
|
}
|
|
|
|
static void RegionsFromSlices(Png png, out List<Rectangle> regions, out List<float2> offsets)
|
|
{
|
|
// Default: whole image is 1 frame.
|
|
var frameSize = new Size(png.Width, png.Height);
|
|
var frameAmount = 1;
|
|
|
|
if (png.EmbeddedData.TryGetValue("FrameSize", out var value))
|
|
{
|
|
// If FrameSize exist, use it and...
|
|
frameSize = FieldLoader.GetValue<Size>("FrameSize", value);
|
|
|
|
// ... either use FrameAmount or calculate how many times FrameSize fits into the image.
|
|
if (png.EmbeddedData.TryGetValue("FrameAmount", out value))
|
|
frameAmount = FieldLoader.GetValue<int>("FrameAmount", value);
|
|
else
|
|
frameAmount = png.Width / frameSize.Width * (png.Height / frameSize.Height);
|
|
}
|
|
else if (png.EmbeddedData.TryGetValue("FrameAmount", out value))
|
|
{
|
|
// Otherwise, calculate the number of frames by splitting the image horizontally by FrameAmount.
|
|
frameAmount = FieldLoader.GetValue<int>("FrameAmount", value);
|
|
frameSize = new Size(png.Width / frameAmount, png.Height);
|
|
}
|
|
|
|
float2 offset;
|
|
|
|
// If Offset property exists, use its value. Otherwise assume the frame is centered.
|
|
if (png.EmbeddedData.TryGetValue("Offset", out value))
|
|
offset = FieldLoader.GetValue<float2>("Offset", value);
|
|
else
|
|
offset = float2.Zero;
|
|
|
|
var framesPerRow = png.Width / frameSize.Width;
|
|
|
|
var rows = (frameAmount + framesPerRow - 1) / framesPerRow;
|
|
if (png.Width < frameSize.Width * frameAmount / rows || png.Height < frameSize.Height * rows)
|
|
throw new InvalidDataException($"Invalid frame size {frameSize} and frame amount {frameAmount} defined.");
|
|
|
|
regions = new List<Rectangle>();
|
|
offsets = new List<float2>();
|
|
|
|
for (var i = 0; i < frameAmount; i++)
|
|
{
|
|
var x = i % framesPerRow * frameSize.Width;
|
|
var y = i / framesPerRow * frameSize.Height;
|
|
|
|
regions.Add(new Rectangle(x, y, frameSize.Width, frameSize.Height));
|
|
offsets.Add(offset);
|
|
}
|
|
}
|
|
}
|
|
}
|