1
0
mirror of https://github.com/XLabsProject/img-format-helper.git synced 2023-08-02 15:02:11 +02:00
img-format-helper/ImgFormatHelper/Reader.cs
2021-04-29 21:55:59 +02:00

389 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IWImgViewer
{
public class Reader
{
public string ImageName => imagePath;
public string ImageFormat { get; private set; }
public byte[] Data { get; set; }
public IImageFileHeader CurrentHeader {get; private set;}
private readonly string imagePath;
public Reader(string imagePath)
{
if (!File.Exists(imagePath))
{
throw new FileNotFoundException();
}
this.imagePath = imagePath;
string ext = System.IO.Path.GetExtension(imagePath).Substring(1).ToUpper();
switch (ext.ToUpper())
{
case "FFIMG":
DecodeFFImage();
break;
case "IW4XIMAGE":
DecodeIW4XImage();
break;
case "IWI":
DecodeIWI4();
break;
}
}
public static event Action<Bitmap> OnBitmapRead;
void DecodeIWI4()
{
using (FileStream fs = new FileStream(imagePath, FileMode.Open))
{
using (BinaryReader br = new BinaryReader(fs))
{
CoD6_GfxImageFileHeader header;
try
{
header = new CoD6_GfxImageFileHeader(Path.GetFileNameWithoutExtension(imagePath));
header.Deserialize(br);
CurrentHeader = header;
}
catch (FormatException)
{
br.Dispose();
fs.Dispose();
// Maybe cod4?
DecodeIWI3();
return;
}
bool hasMipMaps = (header.flags & (byte)IwiFlags.IMG_FLAG_NOMIPMAPS) == 0;
Data = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
CreateBitmap(Data, (short)header.Width, (short)header.Height, header.format, header.MapType, hasMipMaps);
}
}
}
void DecodeIWI3()
{
using (FileStream fs = new FileStream(imagePath, FileMode.Open))
{
using (BinaryReader br = new BinaryReader(fs))
{
CoD4_GfxImageFileHeader header = new CoD4_GfxImageFileHeader(Path.GetFileNameWithoutExtension(imagePath));
header.Deserialize(br);
CurrentHeader = header;
bool hasMipMaps = (header.flags & (byte)IwiFlags.IMG_FLAG_NOMIPMAPS) == 0;
Data = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
CreateBitmap(Data, header.dimensions[0], header.dimensions[1], header.format, header.MapType, hasMipMaps);
}
}
}
void DecodeFFImage()
{
using (FileStream fs = new FileStream(imagePath, FileMode.Open))
{
using (BinaryReader br = new BinaryReader(fs))
{
FFImgHeader header = new FFImgHeader();
header.Deserialize(br);
CurrentHeader = header;
bool hasMipMaps = (header.flags & (byte)IwiFlags.IMG_FLAG_NOMIPMAPS) == 0;
Data = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
if (header.format == _D3DFORMAT.D3DFMT_UNKNOWN)
{
throw new FormatException(header.format.ToString());
}
CreateBitmap(Data, (short)header.Width, (short)header.Height, header.D3dFormat, header.MapType);
}
}
}
void DecodeIW4XImage()
{
using (FileStream fs = new FileStream(imagePath, FileMode.Open))
{
using (BinaryReader br = new BinaryReader(fs))
{
if (new string(br.ReadChars(8)) != "IW4xImg0")
{
throw new FormatException();
}
IW4XImageHeader header = new IW4XImageHeader();
header.Deserialize(br);
CurrentHeader = header;
bool hasMipMaps = (header.flags & (byte)IwiFlags.IMG_FLAG_NOMIPMAPS) == 0;
Data = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
if (header.format == _D3DFORMAT.D3DFMT_UNKNOWN)
{
throw new FormatException(header.format.ToString());
}
CreateBitmap(Data, (short)header.Width, (short)header.Height, header.D3dFormat, header.MapType);
}
}
}
void CreateBitmap(byte[] data, short width, short height, GfxImageFileFormat format, MapType mapType=MapType.MAPTYPE_2D, bool hasMips=false)
{
if (hasMips)
{
CreateBitmapsWithMips(data, width, height, format, mapType);
return;
}
Bitmap bitmap = null;
ImageFormat = format.ToString();
byte channels;
int maxI;
switch (format)
{
case GfxImageFileFormat.IMG_FORMAT_BITMAP_ALPHA:
bitmap = new Bitmap(width, height);
BitmapSetPixels(bitmap, width, height, data, 1);
break;
case GfxImageFileFormat.IMG_FORMAT_DXT1:
case GfxImageFileFormat.IMG_FORMAT_DXT3:
case GfxImageFileFormat.IMG_FORMAT_DXT5:
byte[] pixels;
bitmap = new Bitmap(width, height);
pixels = ManagedSquish.Squish.DecompressImage(data, width, height, FormatHelper.GfxFormatToSquishFormat[format]);
channels = 4;
maxI = channels * width * height;
if (maxI > pixels.Length)
{
if (pixels.Length > (channels - 1) * width * height)
{
System.Windows.Forms.MessageBox.Show($"Too many channels! Expected {channels} channels but there is only data for {channels - 1} channels.");
channels--;
}
else
{
System.Windows.Forms.MessageBox.Show($"Wrong number of channels! For format {format}, {channels} channels is too much.");
}
}
BitmapSetPixels(bitmap, width, height, SwapChannels(pixels, new Dictionary<byte, byte>()
{
{0, 2}, // R -> B
{1, 1}, // G -> G
{2, 0}, // B -> R
{3, 3} // A -> A
}, channels), channels);
break;
//case GfxImageFileFormat.IMG_FORMAT_WAVELET_RGB:
//case GfxImageFileFormat.IMG_FORMAT_WAVELET_RGBA:
// using (OpenJpegDotNet.IO.Reader reader = new OpenJpegDotNet.IO.Reader(data))
// {
// reader.ReadHeader();
// bitmap = reader.ReadData();
// Console.WriteLine("Read data");
// }
// break;
case GfxImageFileFormat.IMG_FORMAT_BITMAP_RGB:
channels = 3;
bitmap = new Bitmap(width, height);
BitmapSetPixels(bitmap, width, height, data, channels);
break;
case GfxImageFileFormat.IMG_FORMAT_BITMAP_RGBA:
channels = 4;
bitmap = new Bitmap(width, height);
BitmapSetPixels(bitmap, width, height, data, channels);
break;
default:
throw new FormatException(format.ToString());
}
OnBitmapRead(bitmap);
}
void CreateBitmapsWithMips(byte[] data, short width, short height, GfxImageFileFormat format, MapType mapType = MapType.MAPTYPE_2D)
{
// Generate mips
List<(int width, int height)> mips = new List<(int width, int height)>();
int sides = mapType == MapType.MAPTYPE_CUBE ? 6 : 1;
short maxDimension = Math.Max(height, width);
int mipmapFactor = 1;
int minBlockSize = FormatHelper.GetBlockSize(format);
while (maxDimension != 0)
{
maxDimension >>= 1;
mipmapFactor *= 2;
mips.Add((Math.Max(width / mipmapFactor, minBlockSize), Math.Max(height / mipmapFactor, minBlockSize)));
}
mips.Reverse();
int compressedLengthToSkip = 0;
for (int i = 0; i <= mips.Count; i++)
{
for (int side = 0; side < sides; side++)
{
short thisWidth, thisHeight;
if (i < mips.Count)
{
var mip = mips[i];
thisWidth = (short)mip.width;
thisHeight = (short)mip.height;
if (thisWidth <= 16 && thisHeight <= 16)
{
// We skip them because at this point the windows form is bigger than the image
continue;
}
}
else
{
thisWidth = width;
thisHeight = height;
}
CreateBitmap(data.Skip(compressedLengthToSkip).ToArray(), thisWidth, thisHeight, format);
if (i < mips.Count)
{
compressedLengthToSkip += ManagedSquish.Squish.GetStorageRequirements(thisWidth, thisHeight, FormatHelper.GfxFormatToSquishFormat[format]);
}
}
}
}
void CreateBitmap(byte[] data, short width, short height, _D3DFORMAT format, MapType mapType = MapType.MAPTYPE_2D)
{
int sides = mapType == MapType.MAPTYPE_CUBE ? 6 : 1;
ImageFormat = format.ToString();
for (int side = 0; side < sides; side++)
{
Bitmap bitmap = new Bitmap(width, height);
byte[] truncatedData;
int sizeOfOneSide;
switch (format)
{
case _D3DFORMAT.D3DFMT_L8:
sizeOfOneSide = width * height * 1;
truncatedData = data.Skip(sizeOfOneSide * side).Take(sizeOfOneSide).ToArray();
BitmapSetPixels(bitmap, width, height, truncatedData, 1);
break;
case _D3DFORMAT.D3DFMT_A8R8G8B8:
sizeOfOneSide = width * height * 4;
truncatedData = data.Skip(sizeOfOneSide * side).Take(sizeOfOneSide).ToArray();
BitmapSetPixels(bitmap, width, height, truncatedData, 4);
break;
default:
throw new FormatException(format.ToString());
}
OnBitmapRead(bitmap);
}
}
byte[] SwapChannels(byte[] data, Dictionary<byte, byte> swapMap, byte channels)
{
byte[] newData = new byte[data.Length];
for (int i = 0; i < data.Length; i += channels)
{
try
{
for (byte channel = 0; channel < channels; channel++)
{
newData[i + channel] = data[i + swapMap[channel]];
}
}
catch (IndexOutOfRangeException)
{
System.Windows.Forms.MessageBox.Show($"Wrong number of channels! {channels} channels is not correct!");
}
}
return newData;
}
void BitmapSetPixels(Bitmap bitmap, short width, short height, byte[] pixels, byte channels)
{
int maxI = channels * width * height;
bool hasWarned = false;
int x, y;
if (pixels.Length != maxI)
{
System.Windows.Forms.MessageBox.Show($"Unexpected number of pixels: Expected {maxI} pixels, found {pixels.Length} (difference of {pixels.Length - maxI})");
}
for (int i = 0; i < pixels.Length - channels; i++)
{
if (i >= maxI)
{
if (!hasWarned)
{
hasWarned = true;
System.Windows.Forms.MessageBox.Show($"Found {pixels.Length - maxI} extra bytes of information at the end of data (?)");
}
}
else
{
if (i % channels == 0)
{
x = (i / channels) % width;
y = i / (width * channels);
bitmap.SetPixel(x, y, Color.FromArgb(
channels > 3 ? pixels[i + 3] : 255,
channels > 2 ? pixels[i + 2] : pixels[i],
channels > 1 ? pixels[i + 1] : pixels[i],
pixels[i])
);
}
}
}
}
}
}