Files
GARbro/ArcFormats/ImageJXL.cs
2025-10-14 15:04:32 +08:00

257 lines
9.9 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 JxlImageFormat : ImageFormat, IDisposable
{
public override string Tag { get { return "JXL"; } }
public override string Description { get { return "JPEG XL image format"; } }
public override uint Signature { get { return 0; } } // JXL signature starts with 0xFF 0x0A
public override bool CanWrite { get { return false; } }
public JxlImageFormat()
{
Extensions = new[] { "jxl" };
}
public enum JxlDec {
Success = 0,
Error = 1,
NeedMoreInput = 2,
NeedPreviewOutBuffer = 3,
NeedImageOutBuffer = 5,
JpegNeedMoreOutput = 6,
BoxNeedMoreOutput = 7,
BasicInfo = 0x40,
ColorEncoding = 0x100,
PreviewImage = 0x200,
Frame = 0x400,
FullImage = 0x1000,
JpegReconstruction = 0x2000,
Box = 0x4000,
FrameProgression = 0x8000,
Complete = 0x10000,
}
public enum JxlOrientation {
Identity = 1,
FlipHorizontal = 2,
Rotate = 3,
FlipVertical = 4,
Transpose = 5,
Rotate90CW = 6,
AntiTranspose = 7,
Rotate90CCW = 8,
}
public enum JxlDataType {
Float = 0,
Uint8 = 2,
Uint16 = 3,
Float16 = 5,
}
public enum JxlEndianness {
Native = 0,
Little = 1,
Big = 2,
}
[StructLayout(LayoutKind.Sequential)]
public struct JxlPixelFormat {
public uint num_channels;
public JxlDataType data_type;
public JxlEndianness endianness;
public UIntPtr align;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct JxlBasicInfo {
public int have_container;
public uint xsize;
public uint ysize;
public uint bits_per_sample;
public uint exponent_bits_per_sample;
public float intensity_target;
public float min_nits;
public int relative_to_max_display;
public float linear_below;
public int uses_original_profile;
public int have_preview;
public int have_animation;
public JxlOrientation orientation;
public uint num_color_channels;
public uint num_extra_channels;
public uint alpha_bits;
public uint alpha_exponent_bits;
public int alpha_premultiplied;
public uint preview_xsize;
public uint preview_ysize;
public uint animation_tps_numerator;
public uint animation_tps_denominator;
public uint animation_num_loops;
public int animation_have_timecodes;
public uint intrinsic_xsize;
public uint intrinsic_ysize;
fixed byte padding[100];
}
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr JxlDecoderCreate(IntPtr memoryManager);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void JxlDecoderDestroy(IntPtr dec);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderSubscribeEvents(IntPtr dec, uint events);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderSetInput(IntPtr dec, byte[] data, UIntPtr size);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderProcessInput(IntPtr dec);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderGetBasicInfo(IntPtr dec, ref JxlBasicInfo info);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderImageOutBufferSize(IntPtr dec, ref JxlPixelFormat format, ref UIntPtr size);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern JxlDec JxlDecoderSetImageOutBuffer(IntPtr dec, ref JxlPixelFormat format, IntPtr buffer, UIntPtr size);
[DllImport("jxl_dec.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void JxlDecoderCloseInput(IntPtr dec);
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
IntPtr dec = JxlDecoderCreate(IntPtr.Zero);
if (dec == IntPtr.Zero)
return null;
try
{
if (JxlDec.Success != JxlDecoderSubscribeEvents(dec, (uint)JxlDec.BasicInfo))
return null;
file.Position = 0;
var input = file.ReadBytes((int)file.Length);
JxlDecoderSetInput(dec, input, (UIntPtr)input.Length);
JxlDecoderCloseInput(dec);
var status = JxlDecoderProcessInput(dec);
if (status != JxlDec.BasicInfo)
return null;
var info = new JxlBasicInfo();
if (JxlDec.Success != JxlDecoderGetBasicInfo(dec, ref info))
return null;
return new ImageMetaData
{
Width = info.xsize,
Height = info.ysize,
BPP = (int)(info.bits_per_sample * (info.num_color_channels + info.num_extra_channels)),
};
}
finally
{
JxlDecoderDestroy(dec);
}
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
IntPtr dec = JxlDecoderCreate(IntPtr.Zero);
if (dec == IntPtr.Zero)
throw new ApplicationException("JxlDecoderCreate failed.");
try
{
uint events = (uint)(JxlDec.BasicInfo | JxlDec.ColorEncoding | JxlDec.FullImage);
if (JxlDec.Success != JxlDecoderSubscribeEvents(dec, events))
throw new ApplicationException("JxlDecoderSubscribeEvents failed.");
file.Position = 0;
var input = file.ReadBytes((int)file.Length);
JxlDecoderSetInput(dec, input, (UIntPtr)input.Length);
JxlDecoderCloseInput(dec);
var basic_info = new JxlBasicInfo();
JxlPixelFormat format;
PixelFormat wpf_format;
int bpp;
var status = JxlDecoderProcessInput(dec);
if (status != JxlDec.BasicInfo)
throw new InvalidFormatException();
if (JxlDec.Success != JxlDecoderGetBasicInfo(dec, ref basic_info))
throw new ApplicationException("JxlDecoderGetBasicInfo failed.");
if (basic_info.alpha_bits > 0)
{
format = new JxlPixelFormat { num_channels = 4, data_type = JxlDataType.Uint8, endianness = JxlEndianness.Native };
wpf_format = PixelFormats.Bgra32;
bpp = 32;
}
else
{
format = new JxlPixelFormat { num_channels = 3, data_type = JxlDataType.Uint8, endianness = JxlEndianness.Native };
wpf_format = PixelFormats.Rgb24;
bpp = 24;
}
UIntPtr buffer_size = UIntPtr.Zero;
if (JxlDec.Success != JxlDecoderImageOutBufferSize(dec, ref format, ref buffer_size))
throw new ApplicationException("JxlDecoderImageOutBufferSize failed.");
var pixels = new byte[(uint)buffer_size];
var handle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
try
{
IntPtr bufferPtr = handle.AddrOfPinnedObject();
if (JxlDec.Success != JxlDecoderSetImageOutBuffer(dec, ref format, bufferPtr, buffer_size))
throw new ApplicationException("JxlDecoderSetImageOutBuffer failed.");
status = JxlDecoderProcessInput(dec);
while (status != JxlDec.FullImage && status != JxlDec.Error && status != JxlDec.Success)
{
status = JxlDecoderProcessInput(dec);
}
if (status != JxlDec.FullImage && status != JxlDec.Success)
throw new InvalidFormatException();
int stride = (int)info.Width * (bpp / 8);
if (wpf_format == PixelFormats.Bgra32) {
// RGBA -> BGRA
for (int i = 0; i < pixels.Length; i += 4)
{
byte b = pixels[i];
pixels[i] = pixels[i + 2];
pixels[i + 2] = b;
}
}
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
{
handle.Free();
}
}
finally
{
JxlDecoderDestroy(dec);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("JxlImageFormat.Write not implemented");
}
public void Dispose ()
{
}
}
}