diff --git a/ArcFormats/ArcFormats.csproj b/ArcFormats/ArcFormats.csproj index 0d2b1d12..2315df22 100644 --- a/ArcFormats/ArcFormats.csproj +++ b/ArcFormats/ArcFormats.csproj @@ -111,6 +111,7 @@ + @@ -1271,6 +1272,9 @@ + + PreserveNewest + PreserveNewest @@ -1280,6 +1284,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/ArcFormats/ImageAVIF.cs b/ArcFormats/ImageAVIF.cs new file mode 100644 index 00000000..bf25e334 --- /dev/null +++ b/ArcFormats/ImageAVIF.cs @@ -0,0 +1,251 @@ +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 () + { + } + } +} diff --git a/ArcFormats/x64/avif.dll b/ArcFormats/x64/avif.dll new file mode 100644 index 00000000..b7dab80e Binary files /dev/null and b/ArcFormats/x64/avif.dll differ diff --git a/ArcFormats/x86/avif.dll b/ArcFormats/x86/avif.dll new file mode 100644 index 00000000..6c3144db Binary files /dev/null and b/ArcFormats/x86/avif.dll differ