Files
OpenRA/OpenRA.Mods.Common/UtilityCommands/ConvertSpriteToPngCommand.cs
Paul Chote ce09b402d0 Fix definition and use of non-indexed sprite color channels.
Our SpriteFrameType names refer to the byte channel order rather than
the bit order, meaning that SpriteFrameType.BGRA corresponds to the
standard Color.ToArgb() etc byte order when the (little-endian) integer
is read as 4 individual bytes.

The previous code did not account for the fact that non-indexed Png
uses big-endian storage for its RGBA colours, and that SheetBuilder
had the color channels incorrectly swapped to match and cancel this out.

New SpriteFrameType enums are introduced to distinguish between BGRA
(little-endian) and RGBA (big-endian) formats, and also for 24bit data
without alpha. The channel swizzling / alpha creation is now handled
when copying into the texture atlas, removing the need for non-png
ISpriteLoader implementations to allocate an additional temporary array
and reorder the channels during load.
2020-12-25 18:51:25 +01:00

89 lines
2.8 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
* 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.IO;
using System.Linq;
using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Primitives;
namespace OpenRA.Mods.Common.UtilityCommands
{
class ConvertSpriteToPngCommand : IUtilityCommand
{
string IUtilityCommand.Name { get { return "--png"; } }
bool IUtilityCommand.ValidateArguments(string[] args)
{
return args.Length >= 3;
}
[Desc("SPRITEFILE PALETTE [--noshadow] [--nopadding]",
"Convert a shp/tmp/R8 to a series of PNGs, optionally removing shadow")]
void IUtilityCommand.Run(Utility utility, string[] args)
{
// HACK: The engine code assumes that Game.modData is set.
var modData = Game.ModData = utility.ModData;
var src = args[1];
var shadowIndex = new int[] { };
if (args.Contains("--noshadow"))
{
Array.Resize(ref shadowIndex, shadowIndex.Length + 3);
shadowIndex[shadowIndex.Length - 1] = 1;
shadowIndex[shadowIndex.Length - 2] = 3;
shadowIndex[shadowIndex.Length - 3] = 4;
}
var palette = new ImmutablePalette(args[2], shadowIndex);
var palColors = new Color[Palette.Size];
for (var i = 0; i < Palette.Size; i++)
palColors[i] = palette.GetColor(i);
var frames = FrameLoader.GetFrames(File.OpenRead(src), modData.SpriteLoaders, out _);
var usePadding = !args.Contains("--nopadding");
var count = 0;
var prefix = Path.GetFileNameWithoutExtension(src);
foreach (var frame in frames)
{
var frameSize = usePadding && !frame.DisableExportPadding ? frame.FrameSize : frame.Size;
var offset = usePadding && !frame.DisableExportPadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
// shp(ts) may define empty frames
if (frameSize.Width == 0 && frameSize.Height == 0)
{
count++;
continue;
}
// TODO: expand frame with zero padding
var pngData = frame.Data;
if (frameSize != frame.Size)
{
pngData = new byte[frameSize.Width * frameSize.Height];
for (var i = 0; i < frame.Size.Height; i++)
Buffer.BlockCopy(frame.Data, i * frame.Size.Width,
pngData, (i + offset.Y) * frameSize.Width + offset.X,
frame.Size.Width);
}
var png = new Png(pngData, SpriteFrameType.Indexed, frameSize.Width, frameSize.Height, palColors);
png.Save("{0}-{1:D4}.png".F(prefix, count++));
}
Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
}
}
}