Initial commit
This commit is contained in:
commit
0f86b0434b
38 changed files with 2370 additions and 0 deletions
|
@ -0,0 +1,17 @@
|
|||
using LibDgf.Aqualead.Image;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Aqualead.Image.Conversion
|
||||
{
|
||||
public interface IAlImageConverter
|
||||
{
|
||||
string FileExtension { get; }
|
||||
bool HasAlternativeFile(AlImage image);
|
||||
bool CanConvert(string pixelFormat);
|
||||
void ConvertFromAl(AlImage image, Stream destStream);
|
||||
void ConvertFromAlAlt(AlImage image, Stream destStream);
|
||||
}
|
||||
}
|
70
LibDgf.Graphics/Aqualead/Image/Conversion/KtxConverter.cs
Normal file
70
LibDgf.Graphics/Aqualead/Image/Conversion/KtxConverter.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
using LibDgf.Aqualead.Image;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using KtxSharp;
|
||||
|
||||
namespace LibDgf.Aqualead.Image.Conversion
|
||||
{
|
||||
public class KtxConverter : IAlImageConverter
|
||||
{
|
||||
public string FileExtension => ".ktx";
|
||||
|
||||
public void ConvertFromAl(AlImage image, Stream destStream)
|
||||
{
|
||||
List<byte[]> mips;
|
||||
if (image.PixelFormat == "ETC1")
|
||||
{
|
||||
mips = image.Mipmaps;
|
||||
}
|
||||
else if (image.PixelFormat == "EC1A")
|
||||
{
|
||||
// Give first half of the mips
|
||||
mips = new List<byte[]>();
|
||||
foreach (var mip in image.Mipmaps)
|
||||
{
|
||||
byte[] newMip = new byte[mip.Length / 2];
|
||||
Buffer.BlockCopy(mip, 0, newMip, 0, newMip.Length);
|
||||
mips.Add(newMip);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Pixel format not supported.", nameof(image));
|
||||
}
|
||||
var ktx = KtxCreator.Create(GlDataType.Compressed, GlPixelFormat.GL_RGB, GlInternalFormat.GL_ETC1_RGB8_OES,
|
||||
image.Width, image.Height, mips, new Dictionary<string, MetadataValue>());
|
||||
KtxWriter.WriteTo(ktx, destStream);
|
||||
}
|
||||
|
||||
public void ConvertFromAlAlt(AlImage image, Stream destStream)
|
||||
{
|
||||
if (image.PixelFormat != "EC1A")
|
||||
throw new ArgumentException("Pixel format does not have alternate representation.", nameof(image));
|
||||
|
||||
// Give second half of the mips
|
||||
var mips = new List<byte[]>();
|
||||
foreach (var mip in image.Mipmaps)
|
||||
{
|
||||
byte[] newMip = new byte[mip.Length / 2];
|
||||
Buffer.BlockCopy(mip, newMip.Length, newMip, 0, newMip.Length);
|
||||
mips.Add(newMip);
|
||||
}
|
||||
|
||||
var ktx = KtxCreator.Create(GlDataType.Compressed, GlPixelFormat.GL_RGB, GlInternalFormat.GL_ETC1_RGB8_OES,
|
||||
image.Width, image.Height, mips, new Dictionary<string, MetadataValue>());
|
||||
KtxWriter.WriteTo(ktx, destStream);
|
||||
}
|
||||
|
||||
public bool CanConvert(string pixelFormat)
|
||||
{
|
||||
return pixelFormat == "EC1A" || pixelFormat == "ETC1";
|
||||
}
|
||||
|
||||
public bool HasAlternativeFile(AlImage image)
|
||||
{
|
||||
return image.PixelFormat == "EC1A";
|
||||
}
|
||||
}
|
||||
}
|
78
LibDgf.Graphics/Aqualead/Image/Conversion/PkmConverter.cs
Normal file
78
LibDgf.Graphics/Aqualead/Image/Conversion/PkmConverter.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using LibDgf.Aqualead.Image;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Aqualead.Image.Conversion
|
||||
{
|
||||
public class PkmConverter : IAlImageConverter
|
||||
{
|
||||
public string FileExtension => ".pkm";
|
||||
|
||||
public void ConvertFromAl(AlImage image, Stream destStream)
|
||||
{
|
||||
List<byte[]> mips;
|
||||
if (image.PixelFormat == "ETC1")
|
||||
{
|
||||
mips = image.Mipmaps;
|
||||
}
|
||||
else if (image.PixelFormat == "EC1A")
|
||||
{
|
||||
// Give first half of the mips
|
||||
mips = new List<byte[]>();
|
||||
byte[] newMip = new byte[image.Mipmaps[0].Length / 2];
|
||||
Buffer.BlockCopy(image.Mipmaps[0], 0, newMip, 0, newMip.Length);
|
||||
mips.Add(newMip);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Pixel format not supported.", nameof(image));
|
||||
}
|
||||
|
||||
WritePkm(destStream, mips[0], image.Width, image.Height);
|
||||
}
|
||||
|
||||
public void ConvertFromAlAlt(AlImage image, Stream destStream)
|
||||
{
|
||||
if (image.PixelFormat != "EC1A")
|
||||
throw new ArgumentException("Pixel format does not have alternate representation.", nameof(image));
|
||||
|
||||
// Give second half of the mips
|
||||
byte[] newMip = new byte[image.Mipmaps[0].Length / 2];
|
||||
Buffer.BlockCopy(image.Mipmaps[0], newMip.Length, newMip, 0, newMip.Length);
|
||||
|
||||
WritePkm(destStream, newMip, image.Width, image.Height);
|
||||
}
|
||||
|
||||
public bool CanConvert(string pixelFormat)
|
||||
{
|
||||
return pixelFormat == "EC1A" || pixelFormat == "ETC1";
|
||||
}
|
||||
|
||||
public bool HasAlternativeFile(AlImage image)
|
||||
{
|
||||
return image.PixelFormat == "EC1A";
|
||||
}
|
||||
|
||||
const ushort ETC1_RGB_NO_MIPMAPS = 0;
|
||||
|
||||
static void WritePkm(Stream stream, byte[] data, uint width, uint height)
|
||||
{
|
||||
BinaryWriter bw = new BinaryWriter(stream);
|
||||
bw.Write("PKM 10".ToCharArray());
|
||||
WriteBeUInt16(bw, ETC1_RGB_NO_MIPMAPS);
|
||||
WriteBeUInt16(bw, (ushort)((width + 3) & ~3));
|
||||
WriteBeUInt16(bw, (ushort)((height + 3) & ~3));
|
||||
WriteBeUInt16(bw, (ushort)width);
|
||||
WriteBeUInt16(bw, (ushort)height);
|
||||
bw.Write(data);
|
||||
}
|
||||
|
||||
static void WriteBeUInt16(BinaryWriter bw, ushort value)
|
||||
{
|
||||
bw.Write((byte)(value >> 8));
|
||||
bw.Write((byte)value);
|
||||
}
|
||||
}
|
||||
}
|
62
LibDgf.Graphics/Aqualead/Image/Conversion/PngConverter.cs
Normal file
62
LibDgf.Graphics/Aqualead/Image/Conversion/PngConverter.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using LibDgf.Aqualead.Image;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Aqualead.Image.Conversion
|
||||
{
|
||||
public class PngConverter : IAlImageConverter
|
||||
{
|
||||
public string FileExtension => ".png";
|
||||
|
||||
public void ConvertFromAl(AlImage image, Stream destStream)
|
||||
{
|
||||
// Only grab the first mip
|
||||
var pixels = image.Mipmaps[0];
|
||||
using (MemoryStream ms = new MemoryStream(pixels))
|
||||
{
|
||||
BinaryReader br = new BinaryReader(ms);
|
||||
using (var img = ConvertBgra32(br, (int)image.Width, (int)image.Height))
|
||||
{
|
||||
img.SaveAsPng(destStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanConvert(string pixelFormat)
|
||||
{
|
||||
return pixelFormat == "BGRA";
|
||||
}
|
||||
|
||||
public static Image<Bgra32> ConvertBgra32(BinaryReader br, int width, int height)
|
||||
{
|
||||
Image<Bgra32> img = new Image<Bgra32>(width, height);
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
var row = img.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
byte b = br.ReadByte();
|
||||
byte g = br.ReadByte();
|
||||
byte r = br.ReadByte();
|
||||
byte a = br.ReadByte();
|
||||
row[x] = new Bgra32(r, g, b, a);
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public void ConvertFromAlAlt(AlImage image, Stream destStream)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool HasAlternativeFile(AlImage image)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
16
LibDgf.Graphics/LibDgf.Graphics.csproj
Normal file
16
LibDgf.Graphics/LibDgf.Graphics.csproj
Normal file
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LibDgf\LibDgf.csproj" />
|
||||
<ProjectReference Include="..\libktxsharp\lib\KtxSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
310
LibDgf.Graphics/TxmConversion.cs
Normal file
310
LibDgf.Graphics/TxmConversion.cs
Normal file
|
@ -0,0 +1,310 @@
|
|||
using LibDgf.Txm;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace LibDgf.Graphics
|
||||
{
|
||||
public static class TxmConversion
|
||||
{
|
||||
public static void ConvertImageToTxm(string inPath, Stream outStream, byte level = 1, short bufferBase = 0, short paletteBufferBase = 0)
|
||||
{
|
||||
using (var image = Image.Load<Rgba32>(inPath))
|
||||
{
|
||||
// Gather all colors to see if it would fit in PSMT8
|
||||
TxmPixelFormat pixelFormat = TxmPixelFormat.None;
|
||||
HashSet<Rgba32> colorSet = new HashSet<Rgba32>();
|
||||
List<Rgba32> palette = null;
|
||||
for (int y = 0; y < image.Height; ++y)
|
||||
{
|
||||
var row = image.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < image.Width; ++x)
|
||||
{
|
||||
colorSet.Add(row[x]);
|
||||
if (colorSet.Count > 256)
|
||||
{
|
||||
pixelFormat = TxmPixelFormat.PSMCT32;
|
||||
y = image.Height;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
short paletteWidth = 0;
|
||||
short paletteHeight = 0;
|
||||
if (pixelFormat == TxmPixelFormat.None)
|
||||
{
|
||||
// Palette check passed, assign palettized pixel format
|
||||
if (colorSet.Count > 16)
|
||||
{
|
||||
pixelFormat = TxmPixelFormat.PSMT8;
|
||||
paletteWidth = 16;
|
||||
paletteHeight = 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
pixelFormat = TxmPixelFormat.PSMT4;
|
||||
paletteWidth = 8;
|
||||
paletteHeight = 2;
|
||||
}
|
||||
palette = new List<Rgba32>(colorSet);
|
||||
}
|
||||
|
||||
// Write header
|
||||
BinaryWriter bw = new BinaryWriter(outStream);
|
||||
TxmHeader txmHeader = new TxmHeader
|
||||
{
|
||||
ImageSourcePixelFormat = pixelFormat,
|
||||
ImageVideoPixelFormat = pixelFormat,
|
||||
ImageWidth = (short)image.Width,
|
||||
ImageHeight = (short)image.Height,
|
||||
ImageBufferBase = bufferBase,
|
||||
ClutPixelFormat = palette != null ? TxmPixelFormat.PSMCT32 : TxmPixelFormat.None,
|
||||
Misc = (byte)(level & 0x0f),
|
||||
ClutWidth = paletteWidth,
|
||||
ClutHeight = paletteHeight,
|
||||
ClutBufferBase = paletteBufferBase
|
||||
};
|
||||
txmHeader.Write(bw);
|
||||
|
||||
// Write palette
|
||||
int palettePixelsWritten = 0;
|
||||
if (pixelFormat == TxmPixelFormat.PSMT4)
|
||||
{
|
||||
foreach (var color in palette)
|
||||
{
|
||||
bw.Write(color.R);
|
||||
bw.Write(color.G);
|
||||
bw.Write(color.B);
|
||||
bw.Write((byte)((color.A + 1) >> 1));
|
||||
++palettePixelsWritten;
|
||||
}
|
||||
}
|
||||
else if (pixelFormat == TxmPixelFormat.PSMT8)
|
||||
{
|
||||
int baseOffset = 0;
|
||||
Rgba32 black = new Rgba32();
|
||||
|
||||
int[] order = new int[]
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
};
|
||||
|
||||
while (palettePixelsWritten < palette.Count)
|
||||
{
|
||||
foreach (var offset in order)
|
||||
{
|
||||
var palOffset = baseOffset + offset;
|
||||
var color = palOffset < palette.Count ? palette[palOffset] : black;
|
||||
bw.Write(color.R);
|
||||
bw.Write(color.G);
|
||||
bw.Write(color.B);
|
||||
bw.Write((byte)((color.A + 1) >> 1));
|
||||
++palettePixelsWritten;
|
||||
}
|
||||
|
||||
baseOffset += order.Length;
|
||||
}
|
||||
}
|
||||
|
||||
// Pad out rest of palette
|
||||
int targetOffset = 16 + (txmHeader.GetClutByteSize() + 15) / 16 * 16;
|
||||
while (outStream.Position < targetOffset)
|
||||
{
|
||||
bw.Write((byte)0);
|
||||
}
|
||||
|
||||
// Write main image data
|
||||
byte pal4BppBuffer = 0;
|
||||
bool odd = false;
|
||||
for (int y = 0; y < image.Height; ++y)
|
||||
{
|
||||
var row = image.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < image.Width; ++x)
|
||||
{
|
||||
var pixel = row[x];
|
||||
if (pixelFormat == TxmPixelFormat.PSMCT32)
|
||||
{
|
||||
bw.Write(pixel.R);
|
||||
bw.Write(pixel.G);
|
||||
bw.Write(pixel.B);
|
||||
bw.Write((byte)((pixel.A + 1) >> 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
var palIndex = palette.IndexOf(pixel);
|
||||
if (pixelFormat == TxmPixelFormat.PSMT4)
|
||||
{
|
||||
pal4BppBuffer <<= 4;
|
||||
pal4BppBuffer |= (byte)(palIndex & 0x0f);
|
||||
odd = !odd;
|
||||
if (!odd)
|
||||
{
|
||||
bw.Write(pal4BppBuffer);
|
||||
pal4BppBuffer = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bw.Write((byte)palIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConvertTxmToPng(Stream stream, string outPath)
|
||||
{
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
TxmHeader imageHeader = new TxmHeader();
|
||||
imageHeader.Read(br);
|
||||
|
||||
Console.WriteLine(imageHeader);
|
||||
if (imageHeader.Misc != 1)
|
||||
Console.WriteLine("Different level!");
|
||||
|
||||
Image<Rgba32> image;
|
||||
if (imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMT8 || imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMT4)
|
||||
{
|
||||
Rgba32[] palette = null;
|
||||
if (imageHeader.ClutPixelFormat == TxmPixelFormat.PSMCT32)
|
||||
{
|
||||
stream.Seek(16, SeekOrigin.Begin);
|
||||
palette = GetRgba32Palette(br, imageHeader.ClutWidth, imageHeader.ClutHeight);
|
||||
//fs.Seek(16, SeekOrigin.Begin);
|
||||
//using (var palImage = ConvertTxmRgba32(br, imageHeader.ClutWidth, imageHeader.ClutHeight))
|
||||
//{
|
||||
// palImage.SaveAsPng(Path.ChangeExtension(outPath, ".pal.png"));
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unsupported pixel format from second texture");
|
||||
}
|
||||
|
||||
stream.Seek(16 + (imageHeader.GetClutByteSize() + 15) / 16 * 16, SeekOrigin.Begin);
|
||||
if (imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMT8)
|
||||
{
|
||||
image = ConvertTxmIndexed8bpp(br, imageHeader.ImageWidth, imageHeader.ImageHeight, palette);
|
||||
}
|
||||
else
|
||||
{
|
||||
image = ConvertTxmIndexed4bpp(br, imageHeader.ImageWidth, imageHeader.ImageHeight, palette);
|
||||
}
|
||||
}
|
||||
else if (imageHeader.ImageSourcePixelFormat == TxmPixelFormat.PSMCT32)
|
||||
{
|
||||
stream.Seek(16, SeekOrigin.Begin);
|
||||
image = ConvertTxmRgba32(br, imageHeader.ImageWidth, imageHeader.ImageHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unsupported pixel format");
|
||||
}
|
||||
|
||||
image.SaveAsPng(outPath);
|
||||
image.Dispose();
|
||||
}
|
||||
|
||||
public static Image<Rgba32> ConvertTxmIndexed8bpp(BinaryReader br, int width, int height, Rgba32[] palette)
|
||||
{
|
||||
Image<Rgba32> img = new Image<Rgba32>(width, height);
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
var row = img.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
byte index = br.ReadByte();
|
||||
row[x] = palette[index];
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public static Image<Rgba32> ConvertTxmRgba32(BinaryReader br, int width, int height)
|
||||
{
|
||||
Image<Rgba32> img = new Image<Rgba32>(width, height);
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
var row = img.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
byte r = br.ReadByte();
|
||||
byte g = br.ReadByte();
|
||||
byte b = br.ReadByte();
|
||||
int a = br.ReadByte() * 2;
|
||||
if (a > 255) a = 255;
|
||||
row[x] = new Rgba32(r, g, b, (byte)a);
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
|
||||
public static Image<Rgba32> ConvertTxmIndexed4bpp(BinaryReader br, int width, int height, Rgba32[] palette)
|
||||
{
|
||||
Image<Rgba32> img = new Image<Rgba32>(width, height);
|
||||
bool odd = false;
|
||||
byte index = 0;
|
||||
for (int y = 0; y < height; ++y)
|
||||
{
|
||||
var row = img.GetPixelRowSpan(y);
|
||||
for (int x = 0; x < width; ++x)
|
||||
{
|
||||
if (!odd)
|
||||
{
|
||||
index = br.ReadByte();
|
||||
}
|
||||
row[x] = palette[index & 0xf];
|
||||
index >>= 4;
|
||||
odd = !odd;
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
static Rgba32[] GetRgba32Palette(BinaryReader br, int width, int height)
|
||||
{
|
||||
int count = width * height;
|
||||
Rgba32[] colors = new Rgba32[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
byte r = br.ReadByte();
|
||||
byte g = br.ReadByte();
|
||||
byte b = br.ReadByte();
|
||||
int a = br.ReadByte() * 2;
|
||||
if (a > 255) a = 255;
|
||||
colors[i] = new Rgba32(r, g, b, (byte)a);
|
||||
}
|
||||
|
||||
if (width == 8 && height == 2) return colors;
|
||||
|
||||
// Reorder by column, left to right and up to down
|
||||
Rgba32[] reorderedColors = new Rgba32[count];
|
||||
int j = 0;
|
||||
for (int y = 0; y < height; y += 2)
|
||||
{
|
||||
for (int x = 0; x < width; x += 8)
|
||||
{
|
||||
for (int iy = 0; iy < 2; ++iy)
|
||||
{
|
||||
int offset = (y + iy) * width + x;
|
||||
for (int ix = 0; ix < 8; ++ix)
|
||||
{
|
||||
reorderedColors[j++] = colors[offset + ix];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return reorderedColors;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue