Browse Source

Initial encoder

pull/2633/head
Ynse Hoornenborg 3 years ago
parent
commit
0bfa0fed2b
  1. 55
      src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs
  2. 7
      src/ImageSharp/Formats/Heic/Heic4CharCode.cs
  3. 3
      src/ImageSharp/Formats/Heic/Heic4CharCode.tt
  4. 1
      src/ImageSharp/Formats/Heic/HeicDecoderCore.cs
  5. 285
      src/ImageSharp/Formats/Heic/HeicEncoderCore.cs

55
src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs

@ -0,0 +1,55 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Memory class that will expand dynamically when full.
/// </summary>
internal sealed class AutoExpandingMemory<T> : IDisposable
where T : unmanaged
{
private readonly Configuration configuration;
private IMemoryOwner<T> allocation;
public AutoExpandingMemory(Configuration configuration, int initialSize)
{
Guard.MustBeGreaterThan(initialSize, 0, nameof(initialSize));
this.configuration = configuration;
this.allocation = this.configuration.MemoryAllocator.Allocate<T>(initialSize);
}
public Span<T> GetSpan(int requestedSize)
{
Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize));
this.EnsureCapacity(requestedSize);
return this.allocation.Memory.Span[..requestedSize];
}
public Span<T> GetSpan(int offset, int requestedSize)
{
Guard.MustBeGreaterThan(offset, 0, nameof(offset));
Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize));
this.EnsureCapacity(offset + requestedSize);
return this.allocation.Memory.Span.Slice(offset, requestedSize);
}
public void Dispose() => this.allocation.Dispose();
private void EnsureCapacity(int requestedSize)
{
if (requestedSize > this.allocation.Memory.Length)
{
int newSize = requestedSize + (requestedSize / 5);
IMemoryOwner<T> newAllocation = this.configuration.MemoryAllocator.Allocate<T>(newSize);
this.allocation.Memory.CopyTo(newAllocation.Memory);
this.allocation.Dispose();
this.allocation = newAllocation;
}
}
}

7
src/ImageSharp/Formats/Heic/Heic4CharCode.cs

@ -113,6 +113,11 @@ public enum Heic4CharCode : uint
/// </summary>
udes = 0x75646573U,
/// <summary>
/// IPMP Control Box.
/// </summary>
ipmc = 0x69706D63U,
/// <summary>
/// Item Property Container.
/// </summary>
@ -136,7 +141,7 @@ public enum Heic4CharCode : uint
/// <summary>
/// High Efficient File brand.
/// </summary>
hif1 = 0x68696631U,
mif1 = 0x6D696631U,
/// <summary>
/// High Efficiency Coding tile.

3
src/ImageSharp/Formats/Heic/Heic4CharCode.tt

@ -27,11 +27,12 @@
"pixi", "Pixel Information",
"rloc", "Reference Location",
"udes", "User Description",
"ipmc", "IPMP Control Box",
"ipco", "Item Property Container",
"ipma", "Item Property Association",
"heic", "High Efficient Image Coding brand",
"heix", "High Efficient Image Coding brand (legacy name)",
"hif1", "High Efficient File brand",
"mif1", "High Efficient File brand",
"hvc1", "High Efficiency Coding tile",
"jpeg", "Legacy JPEG coded tile",
"dinf", "Data Information",

1
src/ImageSharp/Formats/Heic/HeicDecoderCore.cs

@ -219,6 +219,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
case Heic4CharCode.grpl:
case Heic4CharCode.ipro:
case Heic4CharCode.uuid:
case Heic4CharCode.ipmc:
// Silently skip these boxes.
SkipBox(stream, length);
break;

285
src/ImageSharp/Formats/Heic/HeicEncoderCore.cs

@ -1,8 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Text;
using SixLabors.ImageSharp.Advanced;
using System;
using System.Buffers.Binary;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heic;
@ -40,13 +42,288 @@ internal sealed class HeicEncoderCore : IImageEncoderInternals
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
public async void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// TODO: Implement
byte[] pixels = await CompressPixels(image, cancellationToken);
List<HeicItem> items = new();
List<HeicItemLink> links = new();
GenerateItems(image, pixels, items, links);
// Write out the generated header and pixels.
this.WriteFileTypeBox(stream);
this.WriteMetadataBox(items, links, stream);
this.WriteMediaDataBox(pixels, stream);
stream.Flush();
}
private static void GenerateItems<TPixel>(Image<TPixel> image, byte[] pixels, List<HeicItem> items, List<HeicItemLink> links)
where TPixel : unmanaged, IPixel<TPixel>
{
HeicItem primaryItem = new(Heic4CharCode.jpeg, 1u);
primaryItem.DataLocations.Add(new HeicLocation(0L, pixels.LongLength));
primaryItem.BitsPerPixel = 24;
primaryItem.ChannelCount = 3;
primaryItem.SetExtent(image.Size);
// Create a fake thumbnail, to make our own Decoder happy.
HeicItemLink thumbnail = new(Heic4CharCode.thmb, 1u);
thumbnail.DestinationIds.Add(1u);
}
private static int WriteBoxHeader(Span<byte> buffer, Heic4CharCode type)
{
int bytesWritten = 0;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 8U);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)type);
bytesWritten += 4;
return bytesWritten;
}
private static int WriteBoxHeader(Span<byte> buffer, Heic4CharCode type, byte version, uint flags)
{
int bytesWritten = 0;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 12U);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)type);
bytesWritten += 4;
// Layout in memory is 4 bytes, 1 version byte followed by 3 flag bytes.
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], flags);
buffer[bytesWritten] = version;
bytesWritten += 4;
return bytesWritten;
}
private void WriteFileTypeBox(Stream stream)
{
Span<byte> buffer = stackalloc byte[24];
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ftyp);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.heic);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0U);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.mif1);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.heic);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
stream.Write(buffer);
}
private void WriteMetadataBox(List<HeicItem> items, List<HeicItemLink> links, Stream stream)
{
using AutoExpandingMemory<byte> memory = new(this.configuration, 0x1000);
Span<byte> buffer = memory.GetSpan(12);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.meta, 0, 0);
bytesWritten += WriteHandlerBox(memory, bytesWritten);
bytesWritten += WritePrimaryItemBox(memory, bytesWritten);
bytesWritten += WriteItemInfoBox(memory, bytesWritten, items);
bytesWritten += WriteItemReferenceBox(memory, bytesWritten, links);
bytesWritten += WriteItemPropertiesBox(memory, bytesWritten, items);
bytesWritten += WriteItemDataBox(memory, bytesWritten);
bytesWritten += WriteItemLocationBox(memory, bytesWritten, items);
buffer = memory.GetSpan(bytesWritten);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
stream.Write(buffer);
}
private static int WriteHandlerBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.hdlr, 0, 0);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.pict);
bytesWritten += 4;
for (int i = 0; i < 13; i++)
{
buffer[bytesWritten++] = 0;
}
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WritePrimaryItemBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.pitm, 0, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1);
bytesWritten += 2;
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteItemInfoBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeicItem> items)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iinf, 0, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count);
bytesWritten += 2;
foreach (HeicItem item in items)
{
int itemLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.infe, 2, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id);
bytesWritten += 2;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0);
bytesWritten += 2;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Type);
bytesWritten += 4;
buffer[bytesWritten++] = 0;
BinaryPrimitives.WriteUInt32BigEndian(buffer[itemLengthOffset..], (uint)(bytesWritten - itemLengthOffset));
}
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteItemReferenceBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeicItemLink> links)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iref, 0, 0);
foreach (HeicItemLink link in links)
{
int itemLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], link.Type);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)link.SourceId);
bytesWritten += 2;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)link.DestinationIds.Count);
bytesWritten += 2;
foreach (uint destId in link.DestinationIds)
{
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)destId);
bytesWritten += 2;
}
BinaryPrimitives.WriteUInt32BigEndian(buffer[itemLengthOffset..], (uint)(bytesWritten - itemLengthOffset));
}
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteItemPropertiesBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeicItem> items)
{
const ushort numPropPerItem = 1;
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iprp);
// Write 'ipco' box
int ipcoLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.ipco);
foreach (HeicItem item in items)
{
bytesWritten += WriteSpatialExtentPropertyBox(memory, ipcoLengthOffset + bytesWritten, item);
}
BinaryPrimitives.WriteUInt32BigEndian(buffer[ipcoLengthOffset..], (uint)(bytesWritten - ipcoLengthOffset));
// Write 'ipma' box
int ipmaLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.ipma, 0, 0);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)(items.Count * numPropPerItem));
bytesWritten += 4;
ushort propIndex = 0;
foreach (HeicItem item in items)
{
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id);
bytesWritten += 2;
buffer[bytesWritten++] = 1;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], propIndex);
bytesWritten += 2;
propIndex += numPropPerItem;
}
BinaryPrimitives.WriteUInt32BigEndian(buffer[ipmaLengthOffset..], (uint)(bytesWritten - ipmaLengthOffset));
// Update size of enclosing 'iprp' box.
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory<byte> memory, int memoryOffset, HeicItem item)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ispe);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteItemDataBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.idat);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private static int WriteItemLocationBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeicItem> items)
{
Span<byte> buffer = memory.GetSpan(memoryOffset, 100);
int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iloc, 1, 0);
buffer[bytesWritten++] = 0x44;
buffer[bytesWritten++] = 0;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1);
bytesWritten += 2;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items[0].Id);
bytesWritten += 2;
for (int i = 0; i < 4; i++)
{
buffer[bytesWritten++] = 0;
}
IEnumerable<HeicLocation> itemLocs = items.SelectMany(item => item.DataLocations).Where(loc => loc != null);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)itemLocs.Count());
bytesWritten += 2;
foreach (HeicLocation loc in itemLocs)
{
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Offset);
bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Length);
bytesWritten += 4;
}
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten;
}
private void WriteMediaDataBox(Span<byte> data, Stream stream)
{
Span<byte> buf = stackalloc byte[12];
int bytesWritten = WriteBoxHeader(buf, Heic4CharCode.mdat);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)(data.Length + bytesWritten));
stream.Write(buf[..bytesWritten]);
stream.Write(data);
}
private static async Task<byte[]> CompressPixels<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
using MemoryStream stream = new();
JpegEncoder encoder = new()
{
ColorType = JpegEncodingColor.YCbCrRatio420
};
await image.SaveAsJpegAsync(stream, encoder, cancellationToken);
return stream.ToArray();
}
}

Loading…
Cancel
Save