Files
GARbro/ArcFormats/ImageAVIF.cs
2025-10-27 19:24:44 +08:00

252 lines
9.1 KiB
C#

using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats
{
[Export(typeof(ImageFormat))]
public class AvifImageFormat : ImageFormat, IDisposable
{
public override string Tag { get { return "AVIF"; } }
public override string Description { get { return "AV1 Image File Format"; } }
public override uint Signature { get { return 0; } }
public override bool CanWrite { get { return false; } }
public AvifImageFormat()
{
Extensions = new[] { "avif" };
}
public enum avifResult
{
OK = 0,
UnknownError = 1,
InvalidFtyp = 2,
NoContent = 3,
NoYuvFormatSelected = 4,
ReformatFailed = 5,
UnsupportedDepth = 6,
EncodeColorFailed = 7,
EncodeAlphaFailed = 8,
BmffParseFailed = 9,
MissingImageItem = 10,
DecodeColorFailed = 11,
DecodeAlphaFailed = 12,
ColorAlphaSizeMismatch = 13,
IspeSizeMismatch = 14,
NoCodecAvailable = 15,
NoImagesRemaining = 16,
InvalidExifPayload = 17,
InvalidImageGrid = 18,
InvalidCodecSpecificOption = 19,
TruncatedData = 20,
IoNotSet = 21,
IoError = 22,
WaitingOnIo = 23,
InvalidArgument = 24,
NotImplemented = 25,
OutOfMemory = 26,
CannotChangeSetting = 27,
IncompatibleImage = 28,
InternalError = 29,
EncodeGainMapFailed = 30,
DecodeGainMapFailed = 31,
InvalidToneMappedImage = 32,
}
public enum avifRGBFormat
{
RGB = 0,
RGBA,
ARGB,
BGR,
BGRA,
ABGR,
}
[StructLayout(LayoutKind.Sequential)]
public struct avifRWData
{
public IntPtr data;
public UIntPtr size;
}
[StructLayout(LayoutKind.Sequential)]
public struct avifImage
{
public uint width;
public uint height;
public uint depth;
public int yuvFormat;
public int yuvRange;
public int yuvChromaSamplePosition;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public IntPtr[] yuvPlanes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
public uint[] yuvRowBytes;
public int imageOwnsYUVPlanes;
public IntPtr alphaPlane;
public uint alphaRowBytes;
public int imageOwnsAlphaPlane;
public int alphaPremultiplied;
public avifRWData icc;
public ushort colorPrimaries;
public ushort transferCharacteristics;
public ushort matrixCoefficients;
}
[StructLayout(LayoutKind.Sequential)]
public struct avifRGBImage
{
public uint width;
public uint height;
public uint depth;
public avifRGBFormat format;
public int chromaUpsampling;
public int chromaDownsampling;
public int avoidLibYUV;
public int ignoreAlpha;
public int alphaPremultiplied;
public int isFloat;
public int maxThreads;
public IntPtr pixels;
public uint rowBytes;
}
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr avifDecoderCreate();
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void avifDecoderDestroy(IntPtr decoder);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern avifResult avifDecoderReadMemory(IntPtr decoder, IntPtr image, byte[] data, UIntPtr size);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr avifImageCreateEmpty();
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void avifImageDestroy(IntPtr image);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void avifRGBImageSetDefaults(ref avifRGBImage rgb, IntPtr image);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern avifResult avifImageYUVToRGB(IntPtr image, ref avifRGBImage rgb);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern avifResult avifRGBImageAllocatePixels(ref avifRGBImage rgb);
[DllImport("avif.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void avifRGBImageFreePixels(ref avifRGBImage rgb);
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader(12);
if (header.Length < 12 || !header.AsciiEqual(4, "ftyp"))
return null;
if (!header.AsciiEqual(8, "avif") && !header.AsciiEqual(8, "avis"))
return null;
IntPtr decoder = avifDecoderCreate();
if (decoder == IntPtr.Zero)
return null;
IntPtr image = avifImageCreateEmpty();
if (image == IntPtr.Zero)
{
avifDecoderDestroy(decoder);
return null;
}
try
{
file.Position = 0;
var input = file.ReadBytes((int)file.Length);
var result = avifDecoderReadMemory(decoder, image, input, (UIntPtr)input.Length);
if (result != avifResult.OK)
return null;
var img_info = (avifImage)Marshal.PtrToStructure(image, typeof(avifImage));
return new ImageMetaData
{
Width = img_info.width,
Height = img_info.height,
BPP = img_info.alphaPlane != IntPtr.Zero ? 32 : 24,
};
}
finally
{
avifImageDestroy(image);
avifDecoderDestroy(decoder);
}
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
IntPtr decoder = avifDecoderCreate();
if (decoder == IntPtr.Zero)
throw new ApplicationException("avifDecoderCreate failed.");
IntPtr image = avifImageCreateEmpty();
if (image == IntPtr.Zero)
{
avifDecoderDestroy(decoder);
throw new ApplicationException("avifImageCreateEmpty failed.");
}
try
{
file.Position = 0;
var input = file.ReadBytes((int)file.Length);
var result = avifDecoderReadMemory(decoder, image, input, (UIntPtr)input.Length);
if (result != avifResult.OK)
throw new InvalidFormatException();
var img_info = (avifImage)Marshal.PtrToStructure(image, typeof(avifImage));
var rgb = new avifRGBImage();
avifRGBImageSetDefaults(ref rgb, image);
PixelFormat wpf_format;
if (img_info.alphaPlane != IntPtr.Zero)
{
rgb.format = avifRGBFormat.BGRA;
wpf_format = PixelFormats.Bgra32;
}
else
{
rgb.format = avifRGBFormat.BGR;
wpf_format = PixelFormats.Bgr24;
}
rgb.depth = 8;
if (avifResult.OK != avifRGBImageAllocatePixels(ref rgb))
throw new ApplicationException("avifRGBImageAllocatePixels failed.");
try
{
if (avifResult.OK != avifImageYUVToRGB(image, ref rgb))
throw new ApplicationException("avifImageYUVToRGB failed.");
int stride = (int)rgb.rowBytes;
var pixels = new byte[stride * rgb.height];
Marshal.Copy(rgb.pixels, pixels, 0, pixels.Length);
var bitmap = BitmapSource.Create((int)info.Width, (int)info.Height, 96, 96, wpf_format, null, pixels, stride);
bitmap.Freeze();
return new ImageData(bitmap, info);
}
finally
{
avifRGBImageFreePixels(ref rgb);
}
}
finally
{
avifImageDestroy(image);
avifDecoderDestroy(decoder);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("AvifImageFormat.Write not implemented");
}
public void Dispose ()
{
}
}
}