PNG spritesheet support, along with PaletteFromPng.
Cursor palette loader can now be specified via yaml.
This commit is contained in:
@@ -1,206 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public static class PngLoader
|
||||
{
|
||||
public static Bitmap Load(string filename)
|
||||
{
|
||||
using (var s = File.OpenRead(filename))
|
||||
return Load(s);
|
||||
}
|
||||
|
||||
public static Bitmap Load(Stream s)
|
||||
{
|
||||
using (var br = new BinaryReader(s))
|
||||
{
|
||||
var signature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
|
||||
foreach (var b in signature)
|
||||
if (br.ReadByte() != b)
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
Bitmap bitmap = null;
|
||||
Color[] palette = null;
|
||||
var data = new List<byte>();
|
||||
|
||||
try
|
||||
{
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(br.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(br.ReadBytes(4));
|
||||
var content = br.ReadBytes(length);
|
||||
/*var crc = */br.ReadInt32();
|
||||
|
||||
if (bitmap == null && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
using (var cr = new BinaryReader(ms))
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (bitmap != null)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
|
||||
var width = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var height = IPAddress.NetworkToHostOrder(cr.ReadInt32());
|
||||
var bitDepth = cr.ReadByte();
|
||||
var colorType = (PngColorType)cr.ReadByte();
|
||||
var compression = cr.ReadByte();
|
||||
/*var filter = */cr.ReadByte();
|
||||
var interlace = cr.ReadByte();
|
||||
|
||||
if (compression != 0) throw new InvalidDataException("Compression method not supported");
|
||||
if (interlace != 0) throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
bitmap = new Bitmap(width, height, MakePixelFormat(bitDepth, colorType));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = cr.ReadByte(); var g = cr.ReadByte(); var b = cr.ReadByte();
|
||||
palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
palette[i] = Color.FromArgb(cr.ReadByte(), palette[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
var bits = bitmap.LockBits(bitmap.Bounds(),
|
||||
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
||||
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
// 'zlib' flags bytes; confuses the DeflateStream.
|
||||
/*var flags = (byte)*/ns.ReadByte();
|
||||
/*var moreFlags = (byte)*/ns.ReadByte();
|
||||
|
||||
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
|
||||
using (var dr = new BinaryReader(ds))
|
||||
{
|
||||
var prevLine = new byte[bitmap.Width]; // all zero
|
||||
for (var y = 0; y < bitmap.Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)dr.ReadByte();
|
||||
var line = dr.ReadBytes(bitmap.Width);
|
||||
|
||||
for (var i = 0; i < bitmap.Width; i++)
|
||||
line[i] = i > 0
|
||||
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
|
||||
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
|
||||
|
||||
Marshal.Copy(line, 0, new IntPtr(bits.Scan0.ToInt64() + y * bits.Stride), line.Length);
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bits);
|
||||
|
||||
if (palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
using (var temp = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
|
||||
{
|
||||
var cp = temp.Palette;
|
||||
for (var i = 0; i < 256; i++)
|
||||
cp.Entries[i] = palette[i]; // finalize the palette.
|
||||
bitmap.Palette = cp;
|
||||
return bitmap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (bitmap != null)
|
||||
bitmap.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return PixelFormat.Format8bppIndexed;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,6 @@
|
||||
<Compile Include="FieldSaver.cs" />
|
||||
<Compile Include="Manifest.cs" />
|
||||
<Compile Include="Graphics\Vertex.cs" />
|
||||
<Compile Include="FileFormats\PngLoader.cs" />
|
||||
<Compile Include="Primitives\ActionQueue.cs" />
|
||||
<Compile Include="Primitives\BitSet.cs" />
|
||||
<Compile Include="Primitives\Cache.cs" />
|
||||
|
||||
205
OpenRA.Mods.Common/FileFormats/Png.cs
Normal file
205
OpenRA.Mods.Common/FileFormats/Png.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class Png
|
||||
{
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
public PixelFormat PixelFormat { get; set; }
|
||||
public Color[] Palette { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public Dictionary<string, string> EmbeddedData = new Dictionary<string, string>();
|
||||
|
||||
public Png(Stream s)
|
||||
{
|
||||
if (!Verify(s))
|
||||
throw new InvalidDataException("PNG Signature is bogus");
|
||||
|
||||
s.Position += 8;
|
||||
var headerParsed = false;
|
||||
|
||||
var data = new List<byte>();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(s.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(s.ReadBytes(4));
|
||||
var content = s.ReadBytes(length);
|
||||
/*var crc = */s.ReadInt32();
|
||||
|
||||
if (!headerParsed && type != "IHDR")
|
||||
throw new InvalidDataException("Invalid PNG file - header does not appear first.");
|
||||
|
||||
using (var ms = new MemoryStream(content))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "IHDR":
|
||||
{
|
||||
if (headerParsed)
|
||||
throw new InvalidDataException("Invalid PNG file - duplicate header.");
|
||||
Width = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
Height = IPAddress.NetworkToHostOrder(ms.ReadInt32());
|
||||
Data = new byte[Width * Height];
|
||||
|
||||
var bitDepth = ms.ReadUInt8();
|
||||
var colorType = (PngColorType)ms.ReadByte();
|
||||
PixelFormat = MakePixelFormat(bitDepth, colorType);
|
||||
|
||||
var compression = ms.ReadByte();
|
||||
/*var filter = */ms.ReadByte();
|
||||
var interlace = ms.ReadByte();
|
||||
|
||||
if (compression != 0)
|
||||
throw new InvalidDataException("Compression method not supported");
|
||||
|
||||
if (interlace != 0)
|
||||
throw new InvalidDataException("Interlacing not supported");
|
||||
|
||||
headerParsed = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "PLTE":
|
||||
{
|
||||
Palette = new Color[256];
|
||||
for (var i = 0; i < length / 3; i++)
|
||||
{
|
||||
var r = ms.ReadByte(); var g = ms.ReadByte(); var b = ms.ReadByte();
|
||||
Palette[i] = Color.FromArgb(r, g, b);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tRNS":
|
||||
{
|
||||
if (Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
Palette[i] = Color.FromArgb(ms.ReadByte(), Palette[i]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IDAT":
|
||||
{
|
||||
data.AddRange(content);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "tEXt":
|
||||
{
|
||||
var key = ms.ReadASCIIZ();
|
||||
EmbeddedData.Add(key, ms.ReadASCII(length - key.Length - 1));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case "IEND":
|
||||
{
|
||||
using (var ns = new MemoryStream(data.ToArray()))
|
||||
{
|
||||
// 'zlib' flags bytes; confuses the DeflateStream.
|
||||
/*var flags = (byte)*/ns.ReadByte();
|
||||
/*var moreFlags = (byte)*/ns.ReadByte();
|
||||
|
||||
using (var ds = new DeflateStream(ns, CompressionMode.Decompress))
|
||||
{
|
||||
var prevLine = new byte[Width]; // all zero
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
var filter = (PngFilter)ds.ReadByte();
|
||||
var line = ds.ReadBytes(Width);
|
||||
|
||||
for (var i = 0; i < Width; i++)
|
||||
line[i] = i > 0
|
||||
? UnapplyFilter(filter, line[i], line[i - 1], prevLine[i], prevLine[i - 1])
|
||||
: UnapplyFilter(filter, line[i], 0, prevLine[i], 0);
|
||||
|
||||
Array.Copy(line, 0, Data, y * Width, line.Length);
|
||||
prevLine = line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Palette == null)
|
||||
throw new InvalidDataException("Non-Palette indexed PNG are not supported.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Verify(Stream s)
|
||||
{
|
||||
var pos = s.Position;
|
||||
var signature = new[] { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
|
||||
var isPng = signature.Aggregate(true, (current, t) => current && s.ReadUInt8() == t);
|
||||
s.Position = pos;
|
||||
return isPng;
|
||||
}
|
||||
|
||||
static byte UnapplyFilter(PngFilter f, byte x, byte a, byte b, byte c)
|
||||
{
|
||||
switch (f)
|
||||
{
|
||||
case PngFilter.None: return x;
|
||||
case PngFilter.Sub: return (byte)(x + a);
|
||||
case PngFilter.Up: return (byte)(x + b);
|
||||
case PngFilter.Average: return (byte)(x + (a + b) / 2);
|
||||
case PngFilter.Paeth: return (byte)(x + Paeth(a, b, c));
|
||||
default:
|
||||
throw new InvalidOperationException("Unsupported Filter");
|
||||
}
|
||||
}
|
||||
|
||||
static byte Paeth(byte a, byte b, byte c)
|
||||
{
|
||||
var p = a + b - c;
|
||||
var pa = Math.Abs(p - a);
|
||||
var pb = Math.Abs(p - b);
|
||||
var pc = Math.Abs(p - c);
|
||||
|
||||
return (pa <= pb && pa <= pc) ? a :
|
||||
(pb <= pc) ? b : c;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum PngColorType { Indexed = 1, Color = 2, Alpha = 4 }
|
||||
enum PngFilter { None, Sub, Up, Average, Paeth }
|
||||
|
||||
static PixelFormat MakePixelFormat(byte bitDepth, PngColorType colorType)
|
||||
{
|
||||
if (bitDepth == 8 && colorType == (PngColorType.Indexed | PngColorType.Color))
|
||||
return PixelFormat.Format8bppIndexed;
|
||||
|
||||
throw new InvalidDataException("Unknown pixel format");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,7 @@
|
||||
<Compile Include="Effects\MapNotificationEffect.cs" />
|
||||
<Compile Include="Effects\RallyPointIndicator.cs" />
|
||||
<Compile Include="Effects\SpriteEffect.cs" />
|
||||
<Compile Include="FileFormats\Png.cs" />
|
||||
<Compile Include="Graphics\RailgunRenderable.cs" />
|
||||
<Compile Include="Effects\LaunchEffect.cs" />
|
||||
<Compile Include="Lint\CheckNotifications.cs" />
|
||||
@@ -559,6 +560,7 @@
|
||||
<Compile Include="Traits\World\MPStartUnits.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromFile.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromGimpOrJascFile.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromPng.cs" />
|
||||
<Compile Include="Traits\World\PaletteFromRGBA.cs" />
|
||||
<Compile Include="Traits\World\ValidateOrder.cs" />
|
||||
<Compile Include="Pathfinder\CellInfoLayerPool.cs" />
|
||||
@@ -596,6 +598,8 @@
|
||||
<Compile Include="UtilityCommands\ExtractTraitDocsCommand.cs" />
|
||||
<Compile Include="UtilityCommands\ExtractWeaponDocsCommand.cs" />
|
||||
<Compile Include="Warheads\ChangeOwnerWarhead.cs" />
|
||||
<Compile Include="UtilityCommands\PngSheetExportMetadataCommand.cs" />
|
||||
<Compile Include="UtilityCommands\PngSheetImportMetadataCommand.cs" />
|
||||
<Compile Include="Widgets\Logic\MusicHotkeyLogic.cs" />
|
||||
<Compile Include="WorldExtensions.cs" />
|
||||
<Compile Include="UtilityCommands\GetMapHashCommand.cs" />
|
||||
@@ -697,6 +701,7 @@
|
||||
<Compile Include="SpriteLoaders\TmpRALoader.cs" />
|
||||
<Compile Include="SpriteLoaders\TmpTDLoader.cs" />
|
||||
<Compile Include="SpriteLoaders\ShpD2Loader.cs" />
|
||||
<Compile Include="SpriteLoaders\PngSheetLoader.cs" />
|
||||
<Compile Include="LoadScreens\LogoStripeLoadScreen.cs" />
|
||||
<Compile Include="LoadScreens\BlankLoadScreen.cs" />
|
||||
<Compile Include="Widgets\Logic\ReplayUtils.cs" />
|
||||
|
||||
134
OpenRA.Mods.Common/SpriteLoaders/PngSheetLoader.cs
Normal file
134
OpenRA.Mods.Common/SpriteLoaders/PngSheetLoader.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Mods.Common.SpriteLoaders
|
||||
{
|
||||
public class PngSheetLoader : ISpriteLoader
|
||||
{
|
||||
class PngSheetFrame : ISpriteFrame
|
||||
{
|
||||
public Size Size { get; set; }
|
||||
public Size FrameSize { get; set; }
|
||||
public float2 Offset { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
public bool DisableExportPadding { get { return false; } }
|
||||
}
|
||||
|
||||
public bool TryParseSprite(Stream s, out ISpriteFrame[] frames)
|
||||
{
|
||||
if (!Png.Verify(s))
|
||||
{
|
||||
frames = null;
|
||||
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[")))
|
||||
RegionsFromFrames(png, out frameRegions, out frameOffsets);
|
||||
else
|
||||
RegionsFromSlices(png, out frameRegions, out frameOffsets);
|
||||
|
||||
frames = new ISpriteFrame[frameRegions.Count];
|
||||
|
||||
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]
|
||||
};
|
||||
|
||||
for (var y = 0; y < frames[i].Size.Height; y++)
|
||||
Array.Copy(png.Data, frameStart + y * png.Width, frames[i].Data, y * frames[i].Size.Width, frames[i].Size.Width);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegionsFromFrames(Png png, out List<Rectangle> regions, out List<float2> offsets)
|
||||
{
|
||||
regions = new List<Rectangle>();
|
||||
offsets = new List<float2>();
|
||||
|
||||
string frame;
|
||||
for (var i = 0; png.EmbeddedData.TryGetValue("Frame[" + i + "]", out frame); i++)
|
||||
{
|
||||
// Format: x,y,width,height;offsetX,offsetY
|
||||
var coords = frame.Split(';');
|
||||
regions.Add(FieldLoader.GetValue<Rectangle>("Region", coords[0]));
|
||||
offsets.Add(FieldLoader.GetValue<float2>("Offset", coords[1]));
|
||||
}
|
||||
}
|
||||
|
||||
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.ContainsKey("FrameSize"))
|
||||
{
|
||||
// If FrameSize exist, use it and...
|
||||
frameSize = FieldLoader.GetValue<Size>("FrameSize", png.EmbeddedData["FrameSize"]);
|
||||
|
||||
// ... either use FrameAmount or calculate how many times FrameSize fits into the image.
|
||||
if (png.EmbeddedData.ContainsKey("FrameAmount"))
|
||||
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
|
||||
else
|
||||
frameAmount = png.Width / frameSize.Width * png.Height / frameSize.Height;
|
||||
}
|
||||
else if (png.EmbeddedData.ContainsKey("FrameAmount"))
|
||||
{
|
||||
// Otherwise, calculate the number of frames by splitting the image horizontaly by FrameAmount.
|
||||
frameAmount = FieldLoader.GetValue<int>("FrameAmount", png.EmbeddedData["FrameAmount"]);
|
||||
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.ContainsKey("Offset"))
|
||||
offset = FieldLoader.GetValue<float2>("Offset", png.EmbeddedData["Offset"]);
|
||||
else
|
||||
offset = float2.Zero;
|
||||
|
||||
var framesPerRow = png.Width / frameSize.Width;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
OpenRA.Mods.Common/Traits/World/PaletteFromPng.cs
Normal file
75
OpenRA.Mods.Common/Traits/World/PaletteFromPng.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.Collections.Generic;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
[Desc("Load a PNG and use its embedded palette.")]
|
||||
class PaletteFromPngInfo : ITraitInfo
|
||||
{
|
||||
[FieldLoader.Require, PaletteDefinition]
|
||||
[Desc("Internal palette name")]
|
||||
public readonly string Name = null;
|
||||
|
||||
[Desc("If defined, load the palette only for this tileset.")]
|
||||
public readonly string Tileset = null;
|
||||
|
||||
[FieldLoader.Require]
|
||||
[Desc("Filename to load")]
|
||||
public readonly string Filename = null;
|
||||
|
||||
[Desc("Map listed indices to shadow. Ignores previous color.")]
|
||||
public readonly int[] ShadowIndex = { };
|
||||
|
||||
public readonly bool AllowModifiers = true;
|
||||
|
||||
public object Create(ActorInitializer init) { return new PaletteFromPng(init.World, this); }
|
||||
}
|
||||
|
||||
class PaletteFromPng : ILoadsPalettes, IProvidesAssetBrowserPalettes
|
||||
{
|
||||
readonly World world;
|
||||
readonly PaletteFromPngInfo info;
|
||||
public PaletteFromPng(World world, PaletteFromPngInfo info)
|
||||
{
|
||||
this.world = world;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public void LoadPalettes(WorldRenderer wr)
|
||||
{
|
||||
if (info.Tileset != null && info.Tileset.ToLowerInvariant() != world.Map.Tileset.ToLowerInvariant())
|
||||
return;
|
||||
|
||||
var png = new Png(world.Map.Open(info.Filename));
|
||||
var colors = new uint[Palette.Size];
|
||||
|
||||
for (var i = 0; i < png.Palette.Length; i++)
|
||||
colors[i] = (uint)png.Palette[i].ToArgb();
|
||||
|
||||
wr.AddPalette(info.Name, new ImmutablePalette(colors), info.AllowModifiers);
|
||||
}
|
||||
|
||||
public IEnumerable<string> PaletteNames
|
||||
{
|
||||
get
|
||||
{
|
||||
// Only expose the palette if it is available for the shellmap's tileset (which is a requirement for its use).
|
||||
if (info.Tileset == null || info.Tileset == world.Map.Rules.TileSet.Id)
|
||||
yield return info.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,14 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
{
|
||||
var inputFiles = GlobArgs(args).OrderBy(a => a).ToList();
|
||||
var dest = inputFiles[0].Split('-').First() + ".shp";
|
||||
var frames = inputFiles.Select(a => PngLoader.Load(a));
|
||||
|
||||
var size = frames.First().Size;
|
||||
if (frames.Any(f => f.Size != size))
|
||||
var frames = inputFiles.Select(a => new Png(File.OpenRead(a))).ToList();
|
||||
var size = new Size(frames[0].Width, frames[0].Height);
|
||||
if (frames.Any(f => f.Width != size.Width || f.Height != size.Height))
|
||||
throw new InvalidOperationException("All frames must be the same size");
|
||||
|
||||
using (var destStream = File.Create(dest))
|
||||
ShpTDSprite.Write(destStream, size, frames.Select(f => ToBytes(f)));
|
||||
ShpTDSprite.Write(destStream, size, frames.Select(f => f.Data));
|
||||
|
||||
Console.WriteLine(dest + " saved.");
|
||||
}
|
||||
@@ -53,20 +53,5 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
foreach (var path in Glob.Expand(args[i]))
|
||||
yield return path;
|
||||
}
|
||||
|
||||
static byte[] ToBytes(Bitmap bitmap)
|
||||
{
|
||||
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
|
||||
PixelFormat.Format8bppIndexed);
|
||||
|
||||
var bytes = new byte[bitmap.Width * bitmap.Height];
|
||||
for (var i = 0; i < bitmap.Height; i++)
|
||||
Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
|
||||
bytes, i * bitmap.Width, bitmap.Width);
|
||||
|
||||
bitmap.UnlockBits(data);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Mods.Common.UtilityCommands
|
||||
{
|
||||
public class PngSheetExportMetadataCommand : IUtilityCommand
|
||||
{
|
||||
string IUtilityCommand.Name { get { return "--png-sheet-export"; } }
|
||||
|
||||
bool IUtilityCommand.ValidateArguments(string[] args)
|
||||
{
|
||||
return args.Length == 2;
|
||||
}
|
||||
|
||||
[Desc("PNGFILE", "Export png metadata to yaml")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args)
|
||||
{
|
||||
using (var s = File.OpenRead(args[1]))
|
||||
{
|
||||
var png = new Png(s);
|
||||
png.EmbeddedData.Select(m => new MiniYamlNode(m.Key, m.Value))
|
||||
.ToList()
|
||||
.WriteToFile(Path.ChangeExtension(args[1], "yaml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2018 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.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using ICSharpCode.SharpZipLib.Checksums;
|
||||
|
||||
namespace OpenRA.Mods.Common.UtilityCommands
|
||||
{
|
||||
public class PngSheetImportMetadataCommand : IUtilityCommand
|
||||
{
|
||||
string IUtilityCommand.Name { get { return "--png-sheet-import"; } }
|
||||
|
||||
bool IUtilityCommand.ValidateArguments(string[] args)
|
||||
{
|
||||
return args.Length == 2;
|
||||
}
|
||||
|
||||
[Desc("PNGFILE", "Import yaml metadata to png")]
|
||||
void IUtilityCommand.Run(Utility utility, string[] args)
|
||||
{
|
||||
// HACK: This could eventually be merged into the Png class (add a Write method), however this requires complex png writing algorythms.
|
||||
var rs = File.OpenRead(args[1]);
|
||||
var ws = new MemoryStream();
|
||||
|
||||
using (var bw = new BinaryWriter(ws))
|
||||
{
|
||||
bw.Write(rs.ReadBytes(8));
|
||||
var crc32 = new Crc32();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
var length = IPAddress.NetworkToHostOrder(rs.ReadInt32());
|
||||
var type = Encoding.UTF8.GetString(rs.ReadBytes(4));
|
||||
var content = rs.ReadBytes(length);
|
||||
var crc = rs.ReadUInt32();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case "tEXt":
|
||||
break;
|
||||
|
||||
case "IEND":
|
||||
rs.Close();
|
||||
|
||||
foreach (var node in MiniYaml.FromFile(Path.ChangeExtension(args[1], "yaml")))
|
||||
{
|
||||
bw.Write(IPAddress.NetworkToHostOrder(node.Key.Length + 1 + node.Value.Value.Length));
|
||||
bw.Write("tEXt".ToCharArray());
|
||||
bw.Write(node.Key.ToCharArray());
|
||||
bw.Write((byte)0x00);
|
||||
bw.Write(node.Value.Value.ToCharArray());
|
||||
crc32.Reset();
|
||||
crc32.Update(Encoding.ASCII.GetBytes("tEXt"));
|
||||
crc32.Update(Encoding.ASCII.GetBytes(node.Key + (char)0x00 + node.Value.Value));
|
||||
bw.Write((uint)IPAddress.NetworkToHostOrder((int)crc32.Value));
|
||||
}
|
||||
|
||||
bw.Write(0);
|
||||
bw.Write(type.ToCharArray());
|
||||
bw.Write(crc);
|
||||
|
||||
File.WriteAllBytes(args[1], ws.ToArray());
|
||||
ws.Close();
|
||||
return;
|
||||
|
||||
default:
|
||||
bw.Write(IPAddress.NetworkToHostOrder(length));
|
||||
bw.Write(type.ToCharArray());
|
||||
bw.Write(content);
|
||||
bw.Write(crc);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user