Browse Source

Add JPEG XL container prototype & file type box

JxlBoxHeader contains a header for boxes in the JPEG XL container.

JxlFileTypeBox represents the ftyp box.

BinaryUtils contains helper methods to read/write primitives from/to Stream in custom endianness
pull/3153/head
winscripter 2 weeks ago
parent
commit
e03a581fc7
  1. 321
      src/ImageSharp/Formats/Jxl/IO/BinaryUtils.Generated.cs
  2. 86
      src/ImageSharp/Formats/Jxl/IO/BinaryUtils.tt
  3. 132
      src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs
  4. 58
      src/ImageSharp/Formats/Jxl/IO/Container/JxlFileTypeBox.cs

321
src/ImageSharp/Formats/Jxl/IO/BinaryUtils.Generated.cs

@ -0,0 +1,321 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Jxl.IO;
/// <summary>
/// Reads primitives from streams with correct endianness.
/// </summary>
// TODO: move this class into the IO or Common folder?
internal static class BinaryUtils
{
/// <summary>
/// Reads a <see cref="Int16" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int16" /> will be read from.</param>
/// <returns><see cref="Int16" /></returns>
public static Int16 ReadInt16LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int16)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt16LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="Int16" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int16" /> will be read from.</param>
/// <returns><see cref="Int16" /></returns>
public static Int16 ReadInt16BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int16)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt16BigEndian(data);
}
/// <summary>
/// Writes a <see cref="Int16" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int16" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt16LittleEndian(Stream stream, Int16 value)
{
Span<byte> data = stackalloc byte[sizeof(Int16)];
BinaryPrimitives.WriteInt16LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="Int16" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int16" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt16BigEndian(Stream stream, Int16 value)
{
Span<byte> data = stackalloc byte[sizeof(Int16)];
BinaryPrimitives.WriteInt16BigEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Reads a <see cref="UInt16" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt16" /> will be read from.</param>
/// <returns><see cref="UInt16" /></returns>
public static UInt16 ReadUInt16LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt16)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt16LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="UInt16" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt16" /> will be read from.</param>
/// <returns><see cref="UInt16" /></returns>
public static UInt16 ReadUInt16BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt16)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt16BigEndian(data);
}
/// <summary>
/// Writes a <see cref="UInt16" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt16" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt16LittleEndian(Stream stream, UInt16 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt16)];
BinaryPrimitives.WriteUInt16LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="UInt16" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt16" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt16BigEndian(Stream stream, UInt16 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt16)];
BinaryPrimitives.WriteUInt16BigEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Reads a <see cref="Int32" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int32" /> will be read from.</param>
/// <returns><see cref="Int32" /></returns>
public static Int32 ReadInt32LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int32)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt32LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="Int32" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int32" /> will be read from.</param>
/// <returns><see cref="Int32" /></returns>
public static Int32 ReadInt32BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int32)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt32BigEndian(data);
}
/// <summary>
/// Writes a <see cref="Int32" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int32" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt32LittleEndian(Stream stream, Int32 value)
{
Span<byte> data = stackalloc byte[sizeof(Int32)];
BinaryPrimitives.WriteInt32LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="Int32" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int32" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt32BigEndian(Stream stream, Int32 value)
{
Span<byte> data = stackalloc byte[sizeof(Int32)];
BinaryPrimitives.WriteInt32BigEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Reads a <see cref="UInt32" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt32" /> will be read from.</param>
/// <returns><see cref="UInt32" /></returns>
public static UInt32 ReadUInt32LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt32)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt32LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="UInt32" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt32" /> will be read from.</param>
/// <returns><see cref="UInt32" /></returns>
public static UInt32 ReadUInt32BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt32)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt32BigEndian(data);
}
/// <summary>
/// Writes a <see cref="UInt32" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt32" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt32LittleEndian(Stream stream, UInt32 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt32)];
BinaryPrimitives.WriteUInt32LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="UInt32" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt32" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt32BigEndian(Stream stream, UInt32 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt32)];
BinaryPrimitives.WriteUInt32BigEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Reads a <see cref="Int64" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int64" /> will be read from.</param>
/// <returns><see cref="Int64" /></returns>
public static Int64 ReadInt64LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int64)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt64LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="Int64" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int64" /> will be read from.</param>
/// <returns><see cref="Int64" /></returns>
public static Int64 ReadInt64BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(Int64)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadInt64BigEndian(data);
}
/// <summary>
/// Writes a <see cref="Int64" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int64" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt64LittleEndian(Stream stream, Int64 value)
{
Span<byte> data = stackalloc byte[sizeof(Int64)];
BinaryPrimitives.WriteInt64LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="Int64" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="Int64" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteInt64BigEndian(Stream stream, Int64 value)
{
Span<byte> data = stackalloc byte[sizeof(Int64)];
BinaryPrimitives.WriteInt64BigEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Reads a <see cref="UInt64" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt64" /> will be read from.</param>
/// <returns><see cref="UInt64" /></returns>
public static UInt64 ReadUInt64LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt64)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt64LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="UInt64" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt64" /> will be read from.</param>
/// <returns><see cref="UInt64" /></returns>
public static UInt64 ReadUInt64BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(UInt64)];
stream.ReadExactly(data);
return BinaryPrimitives.ReadUInt64BigEndian(data);
}
/// <summary>
/// Writes a <see cref="UInt64" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt64" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt64LittleEndian(Stream stream, UInt64 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt64)];
BinaryPrimitives.WriteUInt64LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="UInt64" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="UInt64" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void WriteUInt64BigEndian(Stream stream, UInt64 value)
{
Span<byte> data = stackalloc byte[sizeof(UInt64)];
BinaryPrimitives.WriteUInt64BigEndian(data, value);
stream.Write(data);
}
}

86
src/ImageSharp/Formats/Jxl/IO/BinaryUtils.tt

@ -0,0 +1,86 @@
<#@ template language="C#" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".Generated.cs" #>
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Jxl.IO;
<#
List<Type> types = [
typeof(short),
typeof(ushort),
typeof(int),
typeof(uint),
typeof(long),
typeof(ulong)
];
#>
/// <summary>
/// Reads primitives from streams with correct endianness.
/// </summary>
// TODO: move this class into the IO or Common folder?
internal static class BinaryUtils
{
<#
foreach (Type type in types)
{
#>
/// <summary>
/// Reads a <see cref="<#= type.Name #>" />
/// from the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be read from.</param>
/// <returns><see cref="<#= type.Name #>" /></returns>
public static <#= type.Name #> Read<#= type.Name#>LittleEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)];
stream.ReadExactly(data);
return BinaryPrimitives.Read<#= type.Name #>LittleEndian(data);
}
/// <summary>
/// Reads a <see cref="<#= type.Name #>" />
/// from the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be read from.</param>
/// <returns><see cref="<#= type.Name #>" /></returns>
public static <#= type.Name #> Read<#= type.Name#>BigEndian(Stream stream)
{
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)];
stream.ReadExactly(data);
return BinaryPrimitives.Read<#= type.Name #>BigEndian(data);
}
/// <summary>
/// Writes a <see cref="<#= type.Name #>" />
/// into the specified stream in little-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void Write<#= type.Name #>LittleEndian(Stream stream, <#= type.Name #> value)
{
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)];
BinaryPrimitives.Write<#= type.Name #>LittleEndian(data, value);
stream.Write(data);
}
/// <summary>
/// Writes a <see cref="<#= type.Name #>" />
/// into the specified stream in big-endian order.
/// </summary>
/// <param name="stream">The stream where the <see cref="<#= type.Name #>" /> will be written to.</param>
/// <param name="value">Value which will be written to the stream.</param>
public static void Write<#= type.Name #>BigEndian(Stream stream, <#= type.Name #> value)
{
Span<byte> data = stackalloc byte[sizeof(<#= type.Name #>)];
BinaryPrimitives.Write<#= type.Name #>BigEndian(data, value);
stream.Write(data);
}
<# } #>
}

132
src/ImageSharp/Formats/Jxl/IO/Container/JxlBoxHeader.cs

@ -0,0 +1,132 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Runtime.InteropServices;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Container;
/// <summary>
/// Header for JPEG XL container format.
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 16)]
internal struct JxlBoxHeader
{
/// <summary>
/// Box size in bytes.
/// </summary>
public ulong Size;
/// <summary>
/// Type of the box.
/// </summary>
public uint Type;
/// <summary>
/// True if the size field extends until the end of the file.
/// </summary>
public bool SizeExtendsTillEnd;
/// <summary>
/// Initializes a new instance of the <see cref="JxlBoxHeader"/> struct.
/// </summary>
/// <param name="size">The size of the box.</param>
/// <param name="type">The type of the box.</param>
/// <param name="sizeExtendsTillEnd">Does the box size extend till the end of the file?</param>
public JxlBoxHeader(ulong size, uint type, bool sizeExtendsTillEnd)
{
this.Size = size;
this.Type = type;
this.SizeExtendsTillEnd = sizeExtendsTillEnd;
}
/// <summary>
/// Converts a 4-character ASCII string (e.g. "jxlc") into a uint type code.
/// </summary>
/// <param name="typeString">Input type string to convert</param>
/// <returns>Unsigned integer representation of the type string</returns>
public static uint TypeFromString(string typeString)
{
if (typeString.Length != 4)
{
throw new ArgumentException("Box type must be exactly 4 characters", nameof(typeString));
}
Span<byte> buffer = stackalloc byte[4];
_ = Encoding.ASCII.GetBytes(typeString, buffer);
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
}
/// <summary>
/// Converts a uint type code back into a 4-character ASCII string.
/// </summary>
/// <param name="typeCode">Unsigned integer representation of the type string</param>
/// <returns>The string representing the type code.</returns>
public static string TypeToString(uint typeCode)
{
Span<byte> buffer = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buffer, typeCode);
return Encoding.ASCII.GetString(buffer);
}
/// <summary>
/// Parses the JPEG XL box header.
/// </summary>
/// <param name="stream">A stream to parse the header from.</param>
/// <returns>The box header.</returns>
/// <exception cref="InvalidOperationException">Thrown when the header is invalid.</exception>
public static JxlBoxHeader ReadHeader(Stream stream)
{
ulong size = BinaryUtils.ReadUInt32BigEndian(stream);
bool haveSize64 = false;
if (size == 1)
{
// When the size value is equal to 1, a new 64-bit
// size field follows.
haveSize64 = true;
size = BinaryUtils.ReadUInt64BigEndian(stream);
}
// Read the 4-byte type field.
uint type = BinaryUtils.ReadUInt32BigEndian(stream);
if (haveSize64)
{
// When the 64-bit largesize was read,
// the size cannot proceed till the end of the file.
if (size is 0 or 1)
{
throw new InvalidOperationException("Large size cannot have another large size or extend till the end of the file");
}
return new JxlBoxHeader(size, type, sizeExtendsTillEnd: false);
}
else
{
return new JxlBoxHeader(size, type, sizeExtendsTillEnd: size == 0);
}
}
/// <summary>
/// Writes the box header to the specified stream.
/// </summary>
/// <param name="writer">The stream to write the box header to.</param>
public readonly void WriteHeader(Stream writer)
{
if (this.Size is > uint.MaxValue or 1)
{
BinaryUtils.WriteUInt32BigEndian(writer, 1); // Indicates a large size is present
BinaryUtils.WriteUInt64BigEndian(writer, this.Size);
}
else
{
BinaryUtils.WriteUInt32BigEndian(writer, (uint)this.Size);
}
BinaryUtils.WriteUInt32BigEndian(writer, this.Type);
}
}

58
src/ImageSharp/Formats/Jxl/IO/Container/JxlFileTypeBox.cs

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Jxl.IO.Container;
/// <summary>
/// A ftyp box payload.
/// </summary>
internal sealed class JxlFileTypeBox(string majorBrand, uint minorVersion)
{
/// <summary>
/// Gets or sets the primary format. Has to be "jxl " for JPEG XL.
/// </summary>
public string MajorBrand { get; set; } = majorBrand;
/// <summary>
/// Gets or sets the revision of the major brand.
/// </summary>
public uint MinorVersion { get; set; } = minorVersion;
/// <summary>
/// Gets or sets the list of other brands the file is compatible with.
/// </summary>
public List<string> CompatibleBrands { get; set; } = [];
public int GetPayloadSize() => 8 + (this.CompatibleBrands.Count * 4);
public static JxlFileTypeBox Parse(Stream stream, ulong boxSize)
{
string majorBrand = JxlBoxHeader.TypeToString(BinaryUtils.ReadUInt32BigEndian(stream));
uint minorVersion = BinaryUtils.ReadUInt32BigEndian(stream);
boxSize -= 8;
List<string> compatibleBrands = [];
for (ulong i = 0; i < boxSize; i += 4)
{
compatibleBrands.Add(JxlBoxHeader.TypeToString(BinaryUtils.ReadUInt32BigEndian(stream)));
}
JxlFileTypeBox ftyp = new(majorBrand, minorVersion)
{
CompatibleBrands = compatibleBrands
};
return ftyp;
}
public void WritePayload(Stream stream)
{
BinaryUtils.WriteUInt32BigEndian(stream, JxlBoxHeader.TypeFromString(this.MajorBrand));
BinaryUtils.WriteUInt32BigEndian(stream, this.MinorVersion);
foreach (string compatibleBrand in this.CompatibleBrands)
{
BinaryUtils.WriteUInt32BigEndian(stream, JxlBoxHeader.TypeFromString(compatibleBrand));
}
}
}
Loading…
Cancel
Save