From 0829e868f5b3771647e81adf76b7fdf5931036ff Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 25 Dec 2023 17:30:55 +0100 Subject: [PATCH 001/190] Scaffolding for HEIC file format --- .gitattributes | 3 + src/ImageSharp/Configuration.cs | 5 +- .../Formats/Heic/HeicConfigurationModule.cs | 18 +++++ src/ImageSharp/Formats/Heic/HeicConstants.cs | 20 ++++++ src/ImageSharp/Formats/Heic/HeicDecoder.cs | 45 +++++++++++++ .../Formats/Heic/HeicDecoderCore.cs | 65 +++++++++++++++++++ src/ImageSharp/Formats/Heic/HeicEncoder.cs | 18 +++++ .../Formats/Heic/HeicEncoderCore.cs | 53 +++++++++++++++ src/ImageSharp/Formats/Heic/HeicFormat.cs | 34 ++++++++++ .../Formats/Heic/HeicImageFormatDetector.cs | 26 ++++++++ src/ImageSharp/Formats/Heic/HeicMetadata.cs | 26 ++++++++ .../Formats/Heic/HeicNalUnitType.cs | 38 +++++++++++ .../Formats/Heic/IHeicEncoderOptions.cs | 12 ++++ .../Formats/Heic/MetadataExtensions.cs | 20 ++++++ .../Formats/ImageFormatManagerTests.cs | 3 + .../TestUtilities/TestEnvironment.Formats.cs | 2 + 16 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicConstants.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicDecoder.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicEncoder.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicEncoderCore.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicFormat.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicMetadata.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicNalUnitType.cs create mode 100644 src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Heic/MetadataExtensions.cs diff --git a/.gitattributes b/.gitattributes index b5f742ab47..b1cf4dbd0a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -136,3 +136,6 @@ *.ico filter=lfs diff=lfs merge=lfs -text *.cur filter=lfs diff=lfs merge=lfs -text *.ani filter=lfs diff=lfs merge=lfs -text +*.heic filter=lfs diff=lfs merge=lfs -text +*.heif filter=lfs diff=lfs merge=lfs -text +*.avif filter=lfs diff=lfs merge=lfs -text diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 1ca5d0a46b..5d1f66a22e 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; @@ -211,6 +212,7 @@ public void Configure(IImageFormatConfigurationModule configuration) /// . /// . /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() => new( @@ -222,5 +224,6 @@ public void Configure(IImageFormatConfigurationModule configuration) new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule(), - new QoiConfigurationModule()); + new QoiConfigurationModule(), + new HeicConfigurationModule()); } diff --git a/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs b/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs new file mode 100644 index 0000000000..0ba7ceef67 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Registers the image encoders, decoders and mime type detectors for the HEIC format. +/// +public sealed class HeicConfigurationModule : IImageFormatConfigurationModule +{ + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(HeicFormat.Instance, new HeicEncoder()); + configuration.ImageFormatsManager.SetDecoder(HeicFormat.Instance, HeicDecoder.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new HeicImageFormatDetector()); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heic/HeicConstants.cs new file mode 100644 index 0000000000..99c9b8da66 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicConstants.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Contains HEIC (and H.265) constant values defined in the specification. +/// +internal static class HeicConstants +{ + /// + /// The list of mimetypes that equate to a HEIC. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/heif", "image/heif-sequence", "image/heic", "image/heic-sequence", "image/avif" }; + + /// + /// The list of file extensions that equate to a HEIC. + /// + public static readonly IEnumerable FileExtensions = new[] { "heic", "heif", "avif" }; +} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoder.cs b/src/ImageSharp/Formats/Heic/HeicDecoder.cs new file mode 100644 index 0000000000..71c8bdd363 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicDecoder.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image decoder for reading HEIC images from a stream. +public sealed class HeicDecoder : ImageDecoder +{ + private HeicDecoder() + { + } + + /// + /// Gets the shared instance. + /// + public static HeicDecoder Instance { get; } = new(); + + /// + protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + return new HeicDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(options, nameof(options)); + Guard.NotNull(stream, nameof(stream)); + + HeicDecoderCore decoder = new(options); + Image image = decoder.Decode(options.Configuration, stream, cancellationToken); + + return image; + } + + /// + protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + => this.Decode(options, stream, cancellationToken); +} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs new file mode 100644 index 0000000000..c935af778d --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Performs the PBM decoding operation. +/// +internal sealed class HeicDecoderCore : IImageDecoderInternals +{ + /// + /// The general configuration. + /// + private readonly Configuration configuration; + + /// + /// The decoded by this decoder instance. + /// + private ImageMetadata? metadata; + + /// + /// Initializes a new instance of the class. + /// + /// The decoder options. + public HeicDecoderCore(DecoderOptions options) + { + this.Options = options; + this.configuration = options.Configuration; + } + + /// + public DecoderOptions Options { get; } + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ProcessHeader(stream); + + var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + // TODO: Implement + + return image; + } + + /// + public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ProcessHeader(stream); + + // TODO: Implement + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); + } + +} diff --git a/src/ImageSharp/Formats/Heic/HeicEncoder.cs b/src/ImageSharp/Formats/Heic/HeicEncoder.cs new file mode 100644 index 0000000000..90ecc754a6 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicEncoder.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Advanced; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image encoder for writing an image to a stream as HEIC images. +public sealed class HeicEncoder : ImageEncoder +{ + /// + protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) + { + HeicEncoderCore encoder = new(image.Configuration, this); + encoder.Encode(image, stream, cancellationToken); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs new file mode 100644 index 0000000000..291ece9d52 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Text; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Image encoder for writing an image to a stream as a HEIC image. +/// +internal sealed class HeicEncoderCore : IImageEncoderInternals +{ + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder with options. + /// + private readonly HeicEncoder encoder; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The encoder with options. + public HeicEncoderCore(Configuration configuration, HeicEncoder encoder) + { + this.configuration = configuration; + this.encoder = encoder; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + // TODO: Implement + + stream.Flush(); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicFormat.cs b/src/ImageSharp/Formats/Heic/HeicFormat.cs new file mode 100644 index 0000000000..be47afce00 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicFormat.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Registers the image encoders, decoders and mime type detectors for the HEIC format. +/// +public sealed class HeicFormat : IImageFormat +{ + private HeicFormat() + { + } + + /// + /// Gets the shared instance. + /// + public static HeicFormat Instance { get; } = new(); + + /// + public string Name => "HEIC"; + + /// + public string DefaultMimeType => "image/heif"; + + /// + public IEnumerable MimeTypes => HeicConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => HeicConstants.FileExtensions; + + /// + public HeicMetadata CreateDefaultFormatMetadata() => new(); +} diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs new file mode 100644 index 0000000000..a220320f35 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Diagnostics.CodeAnalysis; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Detects HEIC file headers. +/// +public sealed class HeicImageFormatDetector : IImageFormatDetector +{ + /// + public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) + { + format = IsSupportedFileFormat(header) ? HeicFormat.Instance : null; + return format != null; + } + + private static bool IsSupportedFileFormat(ReadOnlySpan header) + { + // TODO: Implement + + return false; + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicMetadata.cs b/src/ImageSharp/Formats/Heic/HeicMetadata.cs new file mode 100644 index 0000000000..ddad88cc46 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicMetadata.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides HEIC specific metadata information for the image. +/// +public class HeicMetadata : IDeepCloneable +{ + /// + /// Initializes a new instance of the class. + /// + public HeicMetadata() => + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private HeicMetadata(HeicMetadata other) + { + } + + /// + public IDeepCloneable DeepClone() => new HeicMetadata(this); +} diff --git a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs new file mode 100644 index 0000000000..127a8dcdca --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides enumeration of supported x265's LAN Unit Types. +/// +public enum HeicNalUnitType : uint +{ + NAL_UNIT_CODED_SLICE_TRAIL_N = 0, + NAL_UNIT_CODED_SLICE_TRAIL_R, + NAL_UNIT_CODED_SLICE_TSA_N, + NAL_UNIT_CODED_SLICE_TSA_R, + NAL_UNIT_CODED_SLICE_STSA_N, + NAL_UNIT_CODED_SLICE_STSA_R, + NAL_UNIT_CODED_SLICE_RADL_N, + NAL_UNIT_CODED_SLICE_RADL_R, + NAL_UNIT_CODED_SLICE_RASL_N, + NAL_UNIT_CODED_SLICE_RASL_R, + NAL_UNIT_CODED_SLICE_BLA_W_LP = 16, + NAL_UNIT_CODED_SLICE_BLA_W_RADL, + NAL_UNIT_CODED_SLICE_BLA_N_LP, + NAL_UNIT_CODED_SLICE_IDR_W_RADL, + NAL_UNIT_CODED_SLICE_IDR_N_LP, + NAL_UNIT_CODED_SLICE_CRA, + NAL_UNIT_VPS = 32, + NAL_UNIT_SPS, + NAL_UNIT_PPS, + NAL_UNIT_ACCESS_UNIT_DELIMITER, + NAL_UNIT_EOS, + NAL_UNIT_EOB, + NAL_UNIT_FILLER_DATA, + NAL_UNIT_PREFIX_SEI, + NAL_UNIT_SUFFIX_SEI, + Unspecified = 62, + Invalid = 64, +} diff --git a/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs b/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs new file mode 100644 index 0000000000..fb73ae3ab2 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Configuration options for use during HEIC encoding. +/// +internal interface IHeicEncoderOptions +{ + // None defined yet. +} diff --git a/src/ImageSharp/Formats/Heic/MetadataExtensions.cs b/src/ImageSharp/Formats/Heic/MetadataExtensions.cs new file mode 100644 index 0000000000..b708bfc308 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/MetadataExtensions.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp; + +/// +/// Extension methods for the type. +/// +public static partial class MetadataExtensions +{ + /// + /// Gets the pbm format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static HeicMetadata GetHeicMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(HeicFormat.Instance); +} diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 324bd4783a..7d21de6dee 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -35,6 +36,7 @@ public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -44,6 +46,7 @@ public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 9508de2469..be3de2a4f8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; @@ -59,6 +60,7 @@ private static Configuration CreateDefaultConfiguration() Configuration cfg = new( new JpegConfigurationModule(), new GifConfigurationModule(), + new HeicConfigurationModule(), new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(), From b5ce29d03f5bb514dc579d9e2caf859bcf6a0a08 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 25 Dec 2023 17:39:10 +0100 Subject: [PATCH 002/190] Fix build --- src/ImageSharp/Formats/Heic/HeicDecoder.cs | 1 + src/ImageSharp/Formats/Heic/HeicDecoderCore.cs | 9 ++++++++- src/ImageSharp/Formats/Heic/HeicEncoder.cs | 1 + src/ImageSharp/Formats/Heic/HeicMetadata.cs | 4 +++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicDecoder.cs b/src/ImageSharp/Formats/Heic/HeicDecoder.cs index 71c8bdd363..9fd200575c 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoder.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoder.cs @@ -7,6 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Image decoder for reading HEIC images from a stream. +/// public sealed class HeicDecoder : ImageDecoder { private HeicDecoder() diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index c935af778d..34369e26e8 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -26,7 +26,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals private ImageMetadata? metadata; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The decoder options. public HeicDecoderCore(DecoderOptions options) @@ -38,6 +38,9 @@ public HeicDecoderCore(DecoderOptions options) /// public DecoderOptions Options { get; } + /// + public Size Dimensions { get; } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -62,4 +65,8 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); } + private void ReadNals(BufferedReadStream stream) { + + } + } diff --git a/src/ImageSharp/Formats/Heic/HeicEncoder.cs b/src/ImageSharp/Formats/Heic/HeicEncoder.cs index 90ecc754a6..8579564ad1 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoder.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoder.cs @@ -7,6 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Image encoder for writing an image to a stream as HEIC images. +/// public sealed class HeicEncoder : ImageEncoder { /// diff --git a/src/ImageSharp/Formats/Heic/HeicMetadata.cs b/src/ImageSharp/Formats/Heic/HeicMetadata.cs index ddad88cc46..55f3d7ba82 100644 --- a/src/ImageSharp/Formats/Heic/HeicMetadata.cs +++ b/src/ImageSharp/Formats/Heic/HeicMetadata.cs @@ -11,7 +11,9 @@ public class HeicMetadata : IDeepCloneable /// /// Initializes a new instance of the class. /// - public HeicMetadata() => + public HeicMetadata() + { + } /// /// Initializes a new instance of the class. From bc9755473ea93212a5ec1535bbf7d9565c2bfc10 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 26 Dec 2023 12:15:28 +0100 Subject: [PATCH 003/190] Initial Metadata parsing --- .../Formats/Heic/FourCharacterCode.cs | 209 ++++++++++ src/ImageSharp/Formats/Heic/HeicBoxType.cs | 26 ++ src/ImageSharp/Formats/Heic/HeicConstants.cs | 2 + .../Formats/Heic/HeicDecoderCore.cs | 363 +++++++++++++++++- .../Formats/Heic/HeicImageFormatDetector.cs | 10 +- src/ImageSharp/Formats/Heic/HeicItem.cs | 32 ++ .../Formats/Heic/HeicItemPropertyType.cs | 23 ++ .../Formats/Heic/HeicMetaSubBoxType.cs | 22 ++ .../Formats/Heic/HeicNalUnitType.cs | 52 ++- src/ImageSharp/Formats/Heic/Readme.md | 9 + 10 files changed, 712 insertions(+), 36 deletions(-) create mode 100644 src/ImageSharp/Formats/Heic/FourCharacterCode.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicBoxType.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicItem.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs create mode 100644 src/ImageSharp/Formats/Heic/Readme.md diff --git a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs new file mode 100644 index 0000000000..ad4cad7443 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs @@ -0,0 +1,209 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides constants for 4 Character codes used in HEIC images. +/// +public static class FourCharacterCode +{ + // TODO: Create T4 template for this file + + /// + /// File Type + /// + public const uint ftyp = 0x66747970U, + + /// + /// Metadata container + /// + public const uint meta = 0x6D657461U, + + /// + /// Media Data + /// + public const uint mdat = 0x6D646174U, + + /// + /// Item Information Entry + /// + public const uint infe = 0x696E6665U, + + /// + /// Item Data + /// + public const uint idat = 0x69646174U, + + /// + /// Item Location + /// + public const uint iloc = 0x696C6F63U, + + /// + /// EXIF metadata + /// + public const uint Exif = 0x45786966U, + + /// + /// Data Reference + /// + public const uint dref = 0x64726566U, + + /// + /// Primary Item + /// + public const uint pitm = 0x7069746DU, + + /// + /// Item Spatial Extent + /// + public const uint ispe = 0x69737064U, + + /// + /// Alternative text + /// + public const uint altt = 0, // 'altt' + + /// + /// Colour information + /// + public const uint colr = 0, // 'colr' + + /// + /// HVC configuration + /// + public const uint hvcC = 0, // 'hvcC' + + /// + /// Image Mirror + /// + public const uint imir = 0, // 'imir' + + /// + /// Image Rotation + /// + public const uint irot = 0, // 'irot' + + /// + /// Image Scaling + /// + public const uint iscl = 0, // 'iscl' + + /// + /// Pixel Aspect Ration + /// + public const uint pasp = 0, // 'pasp' + + /// + /// Pixel Information + /// + public const uint pixi = 0x70697869U, + + /// + /// Reference Location + /// + public const uint rloc = 0, // 'rloc + + /// + /// User Description + /// + public const uint udes = 0, // 'udes' + + /// + /// Item Property Container + /// + public const uint ipco = 0, + + /// + /// Item Property Association + /// + public const uint ipma = 0, + + /// + /// High Efficient Image Coding + /// + public const uint heic = 0, + + /// + /// High Efficiency Coding tile + /// + public const uint hvc1 = 0, + + /// + /// Data Information + /// + public const uint dinf = 0, + + /// + /// Group list + /// + public const uint grpl = 0, + + /// + /// Handler + /// + public const uint hdlr = 0, + + /// + /// Item Data + /// + public const uint idat = 0, // 'idat' + + /// + /// Item Information + /// + public const uint iinf = 0, // 'iinf' + + /// + /// Item Property + /// + public const uint iprp = 0, // 'iprp' + + /// + /// Item Protection + /// + public const uint ipro = 0, // 'ipro' + + /// + /// Item Reference + /// + public const uint iref = 0, // 'iref' + + /// + /// Grid + /// + public const uint grid = 0, // 'grid' + + /// + /// Derived Image + /// + public const uint dimg = 0, // 'dimg' + + /// + /// Thumbnail + /// + public const uint thmb = 0, // 'thmb' + + /// + /// Content Description + /// + public const uint cdsc = 0, // 'cdsc' + + public static uint Parse(string code) + { + if (code.Length != 4) + { + throw new ImageFormatException(); + } + Span span = Encoding.UTF8.GetBytes(code); + return BinaryPrimitives.ReadUInt32BigEndian(buffer); + } + + public static string ToString(uint fourcc) + { + Span span = stackalloc new byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(buffer, fourcc); + return Encoding.UTF8.GetString(span); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicBoxType.cs b/src/ImageSharp/Formats/Heic/HeicBoxType.cs new file mode 100644 index 0000000000..45cecacb68 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicBoxType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides enumeration of supported ISO Base Format Box Types for HEIC. +/// +public enum HeicBoxType : uint +{ + FileType = FourCharacterCode.ftyp, + Meta = FourCharacterCode.meta, + MediaData = FourCharacterCode.mdat, + ItemInfo = FourCharacterCode.infe + ItemData = FourCharacterCode.idat, + ItemLocation = FourCharacterCode.iloc, + Exif = FourCharacterCode.Exif, + ItemPropertyAssociation = FourCharacterCode.ipma, + DataReference = FourCharacterCode.dref, + PrimaryItemReference = FourCharacterCode.pitm, + ImageSpatialExtentsProperty = FourCharacterCode.ispe, + + // Possible box types outside of HEIC images: + Movie = FourCharacterCode.moov, + Track = FourCharacterCode.trak, +} diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heic/HeicConstants.cs index 99c9b8da66..62ee248f01 100644 --- a/src/ImageSharp/Formats/Heic/HeicConstants.cs +++ b/src/ImageSharp/Formats/Heic/HeicConstants.cs @@ -8,6 +8,8 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// internal static class HeicConstants { + public const uint HeicBrand = FourCharacterCode.heic; + /// /// The list of mimetypes that equate to a HEIC. /// diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 34369e26e8..551696e912 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -25,6 +25,10 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals /// private ImageMetadata? metadata; + private List items; + + private List itemLinks; + /// /// Initializes a new instance of the class. /// @@ -45,28 +49,375 @@ public HeicDecoderCore(DecoderOptions options) public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - this.ProcessHeader(stream); + if (!this.CheckFileTypeBox(stream)) + { + throw new InvalidImageFormatException(); + } + + while (!stream.Eof) + { + var length = ReadBoxHeader(stream, out var boxType); + switch (boxType) + { + case HeicBoxType.Meta: + ParseMetadata(stream, length); + break; + case HeicBoxType.MediaData: + ParseMediaData(stream, length); + break; + default: + throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'"); + } + } var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); - // TODO: Implement - return image; } /// public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { - this.ProcessHeader(stream); + this.CheckFileTypeBox(stream); - // TODO: Implement + while (!stream.Eof) + { + var length = ReadBoxHeader(stream, out var boxType); + var buffer = new byte[length]; + stream.Read(buffer); + switch (boxType) + { + case HeicBoxType.Metadata: + ParseMetadata(buffer); + break; + default: + // Silently skip all other box types. + break; + } + } return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); } - private void ReadNals(BufferedReadStream stream) { + private bool CheckFileTypeBox(BufferedReadStream stream) + { + var boxLength = ReadBoxHeader(stream, out var boxType); + Span buffer = stackalloc new byte[boxLength]; + stream.Read(buffer); + var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buf); + // TODO: Interpret minorVersion and compatible brands. + return boxTypepe == HeicBoxType.FileType && majorBrand == HeicConstants.HeicBrand; + } + + private ulong ReadBoxHeader(BufferedReadStream stream, out uint boxType) + { + // Read 4 bytes of length of box + Span buf = stackalloc new byte[8]; + stream.Read(buf); + ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); + ulong headerSize = 8; + // Read 4 bytes of box type + boxType = BinaryPrimitives.ReadUInt32BigEndian(buf.Slice(4)); + + if (boxSize == 1) + { + stream.Read(buf); + boxSize = BinaryPrimitives.ReadUInt64BigEndian(buf); + headerSize += 8UL; + } + + return boxSize - headerSize; + } + + private uint ParseBoxHeader(Span buffer, out ulong length, out uint boxType) + { + ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); + ulong bytesRead = 4; + boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + if (boxSize == 1) + { + boxSize = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(bytesRead)); + bytesRead += 8; + } + + length = boxSize - bytesRead; + return bytesRead; + } + + private void ParseMetadata(BufferedReadStream stream, uint boxLength) + { + var endPosition = stream.Position + boxLength; + while (stream.Position < endPosition) + { + var length = ReadBoxHeader(stream, out var boxType); + switch (boxType) + { + case HeicMetaSubBoxType.ItemProperty: + ParseItemPropertyContainer(stream, length); + break; + case HeicMetaSubBoxType.ItemInfo: + ParseItemInfo(stream, length); + break; + case HeicMetaSubBoxType.ItemReference: + ParseItemReference(stream, length); + break; + case HeicMetaSubBoxType.DataInformation: + case HeicMetaSubBoxType.GroupsList: + case HeicMetaSubBoxType.Handler: + case HeicMetaSubBoxType.ItemData: + case HeicMetaSubBoxType.ItemLocation: + case HeicMetaSubBoxType.ItemProtection: + case HeicMetaSubBoxType.PrimaryItem: + // TODO: Implement + break; + default: + throw new ImageFormatException($"Unknown metadata box type of '{FourCharacterCode.ToString(boxType)}'"); + } + } + } + + private void ParseItemInfo(BufferedReadStream stream, uint boxLength) + { + Span buffer = new byte[length]; + stream.Read(buffer); + uint entryCount; + int bytesRead = 0; + if (buffer[bytesRead] == 0) + { + bytesRead += 4; + entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + } + else + { + bytesRead += 4; + entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + } + + for(uint i = 0; i < entryCount; i++) + { + bytesRead += ParseBoxHeader(out var subBoxLength, out var boxType); + ParseItemInfoEntry(buffer.Slice(bytesRead, subBoxLength)); + bytesRead += subBoxLength; + } + } + + private void ParseItemInfoEntry(Span buffer, uint boxLength) + { + int bytesRead = 0; + var version = buffer[bytesRead]; + bytesRead += 4; + var item = new HeicItem(); + if (version == 0 || version == 1) + { + item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.Name.Length + 1; + item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.ContentType.Length + 1; + // Optional field. + if (bytesRead < boxLength) + { + item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.ContentEncoding.Length + 1; + } + } + + if (version == 1) + { + // Optional fields. + if (bytesRead < boxLength) + { + item.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + } + + if (bytesRead < boxLength) + { + // TODO: Parse item.Extension + } + } + + if (version >= 2) + { + if (getVersion() == 2) + { + item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + } + else if (getVersion() == 3) + { + item.Id = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + } + + item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + item.Type = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.Name.Length + 1; + if (item.Type == "mime") + { + item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.ContentType.Length + 1; + // Optional field. + if (bytesRead < boxLength) + { + item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.ContentEncoding.Length + 1; + } + } + else if (item.Type == "uri ") + { + item.UriType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + bytesRead += item.ContentEncoding.Length + 1; + } + } + } + + private void ParseItemReference(BufferedReadStream stream, uint boxLength) + { + Span buffer = new byte[length]; + stream.Read(buffer); + int bytesRead = 0; + bool largeIds = buffer[bytesRead] != 0; + bytesRead += 4; + while(bytesRead < boxLength) + { + ParseBoxHeader(buffer.Slice(bytesRead), out var subBoxLength, out var linkType); + var link = new HeicItemLink(); + link.Type = linkType; + if (largeIds) + { + link.Source = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + } + else + { + link.Source = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + } + + var count = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + for(uint i = 0; i < count; i++) + { + uint destId; + if (largeIds) + { + destId = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + bytesRead += 4; + } + else + { + destId = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + bytesRead += 2; + } + link.Destinations.Add(destId); + } + itemLinks.Add(link); + } } + private void ParseItemPropertyContainer(BufferedReadStream stream, uint boxLength) + { + var containerLength = ReadBoxHeader(stream, out var containerType); + if (containerType == FourCharacterCode.ipco) + { + // Parse Item Property Container, which is just an array of preperty boxes. + var endPosition = stream.Position + containerLength; + while (stream.Position < endPosition) + { + var length = ReadBoxHeader(stream, out var boxType); + switch (boxType) + { + case HeicItemPropertyType.ImageSpatialExtents: + // Length should be 12. + Span buffer = stackalloc new byte[length]; + stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). + var width = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); + var height = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8)); + break; + case HeicItemPropertyType.PixelAspectRatio: + // Length should be 8. + Span buffer = stackalloc new byte[length]; + stream.Read(buffer); + var horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); + var verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); + break; + case HeicItemPropertyType.PixelInformation: + Span buffer = stackalloc new byte[length]; + stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). + var channelCount = buffer[4]; + int offset = 5; + int bitsPerPixel = 0; + for (int i = 0; i < channelCount; i++) + { + bitsPerPixel += buffer[i]; + } + break; + case HeicItemPropertyType.AcessibilityText: + case HeicItemPropertyType.ImageMirror: + case HeicItemPropertyType.ImageRotation: + case HeicItemPropertyType.ImageScaling: + case HeicItemPropertyType.RelativeLocation: + case HeicItemPropertyType.UserDescription; + // TODO: Implement + break; + default: + throw new ImageFormatException($"Unknown item property box type of '{FourCharacterCode.ToString(boxType)}'"); + } + } + } + else if (containerType == FourCharacterCode.ipma) + { + // Parse Item Property Association + } + } + + private void ParseMediaData(BufferedReadStream stream, uint boxLength) + { + // TODO: Implement + } + + /// + /// Forwards the stream to just past the Start of NAL marker. + /// + private void FindStartOfNal(BufferedReadStream stream) + { + uint i = stream.Position; + uint length = 0; + var dataLength = stream.Length; + + while (i < streamLength) + { + var current = stream.ReadByte(); + if (current == 0) + { + length++; + } + else if (length > 1 && current == 1) + { + // Found the marker ! + //length++; + break; + } + else + { + // False alarm, resetting... + length = 0; + } + i++; + } + } } diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index a220320f35..583d9f4f54 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -10,6 +10,10 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// public sealed class HeicImageFormatDetector : IImageFormatDetector { + /// + int HeaderSize => 12; + + /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) { @@ -19,8 +23,8 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I private static bool IsSupportedFileFormat(ReadOnlySpan header) { - // TODO: Implement - - return false; + return + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic } } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs new file mode 100644 index 0000000000..0dfc442d47 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +public enum HeicItemType +{ + Hvc1, + Grid, + Exif +} + +public class HeicItemLink +{ + public uint Type; + public HeicItem Source; + public List Destinations = new List(); +} + +/// +/// Provides definition for a HEIC Item. +/// +public class HeicItem +{ + public uint Id; + public HeicItemType type; + public string Name; + public string ContentType; + public string ContentEncoding; + public uint ExtensionType; + public string UriType; +} diff --git a/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs b/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs new file mode 100644 index 0000000000..157860f184 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides enumeration of supported Item Property Types for HEIC. +/// +public enum HeicItemPropertyType : uint +{ + Invalid = 0, + AcessibilityText = FourCharacterCode.altt, + Colour = FourCharacterCode.colr, + HvcConfiguration = FourCharacterCode.hvcC, + ImageMirror = FourCharacterCode.imir, + ImageRotation = FourCharacterCode.irot, + ImageScaling = FourCharacterCode.iscl, + ImageSpatialExtents = FourCharacterCode.ispe, + PixelAspectRatio = FourCharacterCode.pasp, + PixelInformation = FourCharacterCode.pixi, + RelativeLocation = FourCharacterCode.rloc, + UserDescription = FourCharacterCode.udes, +} diff --git a/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs b/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs new file mode 100644 index 0000000000..46d29878c3 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Provides enumeration of supported sub type boxes within the 'meta' box for HEIC. +/// +public enum HeicMetaSubBoxType : uint +{ + Invalid = 0, + DataInformation = 0, // 'dinf' + GroupsList = 0, // 'grpl' + Handler = 0, // 'hdlr' + ItemData = 0, // 'idat' + ItemInfo = 0, // 'iinf' + ItemLocation = 0, // 'iloc' + ItemProperty = 0, // 'iprp' + ItemProtection = 0, // 'ipro' + ItemReference = 0, // 'iref' + PrimaryItem = 0, // 'pitm' +} diff --git a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs index 127a8dcdca..e12abf3c8d 100644 --- a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs +++ b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs @@ -6,33 +6,31 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Provides enumeration of supported x265's LAN Unit Types. /// -public enum HeicNalUnitType : uint +public enum HeicNalUnitType : byte { - NAL_UNIT_CODED_SLICE_TRAIL_N = 0, - NAL_UNIT_CODED_SLICE_TRAIL_R, - NAL_UNIT_CODED_SLICE_TSA_N, - NAL_UNIT_CODED_SLICE_TSA_R, - NAL_UNIT_CODED_SLICE_STSA_N, - NAL_UNIT_CODED_SLICE_STSA_R, - NAL_UNIT_CODED_SLICE_RADL_N, - NAL_UNIT_CODED_SLICE_RADL_R, - NAL_UNIT_CODED_SLICE_RASL_N, - NAL_UNIT_CODED_SLICE_RASL_R, - NAL_UNIT_CODED_SLICE_BLA_W_LP = 16, - NAL_UNIT_CODED_SLICE_BLA_W_RADL, - NAL_UNIT_CODED_SLICE_BLA_N_LP, - NAL_UNIT_CODED_SLICE_IDR_W_RADL, - NAL_UNIT_CODED_SLICE_IDR_N_LP, - NAL_UNIT_CODED_SLICE_CRA, - NAL_UNIT_VPS = 32, - NAL_UNIT_SPS, - NAL_UNIT_PPS, - NAL_UNIT_ACCESS_UNIT_DELIMITER, - NAL_UNIT_EOS, - NAL_UNIT_EOB, - NAL_UNIT_FILLER_DATA, - NAL_UNIT_PREFIX_SEI, - NAL_UNIT_SUFFIX_SEI, - Unspecified = 62, + CODED_SLICE_TRAIL_N = 0, + CODED_SLICE_TRAIL_R = 1, + + CODED_SLICE_TSA_N = 2, + CODED_SLICE_TSA_R = 3, + + CODED_SLICE_STSA_N = 4, + CODED_SLICE_STSA_R = 5, + + CODED_SLICE_RADL_N = 6, + CODED_SLICE_RADL_R = 7, + + CODED_SLICE_RASL_N = 8, + CODED_SLICE_RASL_R = 9, + + VParameterSet = 32, + SequenceParameterSet = 33, + PictureParameterSet = 34, + AccessUnitDelimiter = 35, + EndOfSequence = 36, + IsEndOfStream = 37, + FillerData = 38, + PrefixSei = 39, + SuffixSei = 40, Invalid = 64, } diff --git a/src/ImageSharp/Formats/Heic/Readme.md b/src/ImageSharp/Formats/Heic/Readme.md new file mode 100644 index 0000000000..330b27fd6a --- /dev/null +++ b/src/ImageSharp/Formats/Heic/Readme.md @@ -0,0 +1,9 @@ +# Implementation references + +[MPEG-4 register authority](https://mp4ra.org/) + +[HEIF reference implementation from Nokia](https://github.com/nokiatech/heif) + +[Open Source H265 implementation](https://bitbucket.org/multicoreware/x265_git/src) + +[Apple's metadata syntax in HEIC images](http://cheeky4n6monkey.blogspot.com/2017/10/monkey-takes-heic.html) From 235aa380ca4f6644444e067df75949847b88ecdb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 26 Dec 2023 12:17:45 +0100 Subject: [PATCH 004/190] HEIC test images --- tests/ImageSharp.Tests/TestImages.cs | 8 ++++++++ tests/Images/Input/Heic/image1.heic | 3 +++ tests/Images/Input/Heic/image2.heic | 3 +++ tests/Images/Input/Heic/image3.heic | 3 +++ tests/Images/Input/Heic/image4.heic | 3 +++ 5 files changed, 20 insertions(+) create mode 100644 tests/Images/Input/Heic/image1.heic create mode 100644 tests/Images/Input/Heic/image2.heic create mode 100644 tests/Images/Input/Heic/image3.heic create mode 100644 tests/Images/Input/Heic/image4.heic diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8aa95d3496..d078faa6d4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1104,4 +1104,12 @@ public static class Qoi public const string TestCardRGBA = "Qoi/testcard_rgba.qoi"; public const string Wikipedia008 = "Qoi/wikipedia_008.qoi"; } + + public static class Heic + { + public const string Image1 = "Heic/image1.heic"; + public const string Image2 = "Heic/image2.heic"; + public const string Image3 = "Heic/image3.heic"; + public const string Image4 = "Heic/image4.heic"; + } } diff --git a/tests/Images/Input/Heic/image1.heic b/tests/Images/Input/Heic/image1.heic new file mode 100644 index 0000000000..6266a4bc12 --- /dev/null +++ b/tests/Images/Input/Heic/image1.heic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:335699d7ba7b4b4581aed0d995aefa57ef1e6771b66595719768cd313fa2430c +size 2994394 diff --git a/tests/Images/Input/Heic/image2.heic b/tests/Images/Input/Heic/image2.heic new file mode 100644 index 0000000000..5b26cf33c1 --- /dev/null +++ b/tests/Images/Input/Heic/image2.heic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ca296eaa15c4dd912a6e0dd76ce3bc9cbe80a61e570043b00ecfaba4d70d6a6 +size 2540019 diff --git a/tests/Images/Input/Heic/image3.heic b/tests/Images/Input/Heic/image3.heic new file mode 100644 index 0000000000..00867b04e1 --- /dev/null +++ b/tests/Images/Input/Heic/image3.heic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85b3098fb5b0f364958c0e18a5ec13ddde5369826db936021dfa909a3a85b46a +size 1148790 diff --git a/tests/Images/Input/Heic/image4.heic b/tests/Images/Input/Heic/image4.heic new file mode 100644 index 0000000000..3ec147e668 --- /dev/null +++ b/tests/Images/Input/Heic/image4.heic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:676b0a76dcaa7fe9ffc41110b7791bef6cf0bfdb32455b03e149b0f8bfdb0856 +size 41465 From c0297a37c6b78d1ca71ed45e1cfc3f5cc2f00d81 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 26 Dec 2023 17:57:38 +0100 Subject: [PATCH 005/190] Adapt code to .NET 8 --- src/ImageSharp/Configuration.cs | 2 +- .../Formats/Heic/FourCharacterCode.cs | 98 ++--- src/ImageSharp/Formats/Heic/HeicBoxType.cs | 26 -- .../Formats/Heic/HeicDecoderCore.cs | 335 ++++++++++-------- .../Formats/Heic/HeicEncoderCore.cs | 1 - .../Formats/Heic/HeicImageFormatDetector.cs | 13 +- src/ImageSharp/Formats/Heic/HeicItem.cs | 76 ++-- src/ImageSharp/Formats/Heic/HeicItemLink.cs | 25 ++ .../Formats/Heic/HeicItemPropertyType.cs | 23 -- .../Formats/Heic/HeicMetaSubBoxType.cs | 22 -- 10 files changed, 330 insertions(+), 291 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heic/HeicBoxType.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicItemLink.cs delete mode 100644 src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs delete mode 100644 src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 5d1f66a22e..21ecd2cb2b 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -5,8 +5,8 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; diff --git a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs index ad4cad7443..fc9ac748dc 100644 --- a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs +++ b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs @@ -1,11 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; +using System.CodeDom.Compiler; +using System.Text; + namespace SixLabors.ImageSharp.Formats.Heic; /// /// Provides constants for 4 Character codes used in HEIC images. /// +[GeneratedCode("T4", null)] public static class FourCharacterCode { // TODO: Create T4 template for this file @@ -13,197 +18,202 @@ public static class FourCharacterCode /// /// File Type /// - public const uint ftyp = 0x66747970U, + public const uint ftyp = 0x66747970U; /// /// Metadata container /// - public const uint meta = 0x6D657461U, + public const uint meta = 0x6D657461U; /// /// Media Data /// - public const uint mdat = 0x6D646174U, + public const uint mdat = 0x6D646174U; /// /// Item Information Entry /// - public const uint infe = 0x696E6665U, + public const uint infe = 0x696E6665U; /// /// Item Data /// - public const uint idat = 0x69646174U, + public const uint idat = 0x69646174U; /// /// Item Location /// - public const uint iloc = 0x696C6F63U, + public const uint iloc = 0x696C6F63U; /// /// EXIF metadata /// - public const uint Exif = 0x45786966U, + public const uint Exif = 0x45786966U; /// /// Data Reference /// - public const uint dref = 0x64726566U, + public const uint dref = 0x64726566U; /// /// Primary Item /// - public const uint pitm = 0x7069746DU, + public const uint pitm = 0x7069746DU; /// /// Item Spatial Extent /// - public const uint ispe = 0x69737064U, + public const uint ispe = 0x69737064U; /// /// Alternative text /// - public const uint altt = 0, // 'altt' + public const uint altt = 0; // 'altt' /// /// Colour information /// - public const uint colr = 0, // 'colr' + public const uint colr = 0; // 'colr' /// /// HVC configuration /// - public const uint hvcC = 0, // 'hvcC' + public const uint hvcC = 0; // 'hvcC' /// /// Image Mirror /// - public const uint imir = 0, // 'imir' + public const uint imir = 0; // 'imir' /// /// Image Rotation /// - public const uint irot = 0, // 'irot' + public const uint irot = 0; // 'irot' /// /// Image Scaling /// - public const uint iscl = 0, // 'iscl' + public const uint iscl = 0; // 'iscl' /// /// Pixel Aspect Ration /// - public const uint pasp = 0, // 'pasp' + public const uint pasp = 0; // 'pasp' /// /// Pixel Information /// - public const uint pixi = 0x70697869U, + public const uint pixi = 0x70697869U; /// /// Reference Location /// - public const uint rloc = 0, // 'rloc + public const uint rloc = 0; // 'rloc /// /// User Description /// - public const uint udes = 0, // 'udes' + public const uint udes = 0; // 'udes' /// /// Item Property Container /// - public const uint ipco = 0, + public const uint ipco = 0; /// /// Item Property Association /// - public const uint ipma = 0, + public const uint ipma = 0; /// /// High Efficient Image Coding /// - public const uint heic = 0, + public const uint heic = 0; /// /// High Efficiency Coding tile /// - public const uint hvc1 = 0, + public const uint hvc1 = 0; /// /// Data Information /// - public const uint dinf = 0, + public const uint dinf = 0; /// /// Group list /// - public const uint grpl = 0, + public const uint grpl = 0; /// /// Handler /// - public const uint hdlr = 0, - - /// - /// Item Data - /// - public const uint idat = 0, // 'idat' + public const uint hdlr = 0; /// /// Item Information /// - public const uint iinf = 0, // 'iinf' + public const uint iinf = 0; // 'iinf' /// /// Item Property /// - public const uint iprp = 0, // 'iprp' + public const uint iprp = 0; // 'iprp' /// /// Item Protection /// - public const uint ipro = 0, // 'ipro' + public const uint ipro = 0; // 'ipro' /// /// Item Reference /// - public const uint iref = 0, // 'iref' + public const uint iref = 0; // 'iref' /// /// Grid /// - public const uint grid = 0, // 'grid' + public const uint grid = 0; // 'grid' /// /// Derived Image /// - public const uint dimg = 0, // 'dimg' + public const uint dimg = 0; // 'dimg' /// /// Thumbnail /// - public const uint thmb = 0, // 'thmb' + public const uint thmb = 0; // 'thmb' /// /// Content Description /// - public const uint cdsc = 0, // 'cdsc' + public const uint cdsc = 0; // 'cdsc' + + /// + /// MIME type + /// + public const uint mime = 0; // 'mime' + + /// + /// URI + /// + public const uint uri = 0; // 'uri ' public static uint Parse(string code) { if (code.Length != 4) { - throw new ImageFormatException(); + throw new ImageFormatException("Unbale to parse FourCC code of more than 4 characters."); } Span span = Encoding.UTF8.GetBytes(code); - return BinaryPrimitives.ReadUInt32BigEndian(buffer); + return BinaryPrimitives.ReadUInt32BigEndian(span); } public static string ToString(uint fourcc) { - Span span = stackalloc new byte[4]; - BinaryPrimitives.WriteUInt32BigEndian(buffer, fourcc); + Span span = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(span, fourcc); return Encoding.UTF8.GetString(span); } } diff --git a/src/ImageSharp/Formats/Heic/HeicBoxType.cs b/src/ImageSharp/Formats/Heic/HeicBoxType.cs deleted file mode 100644 index 45cecacb68..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicBoxType.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported ISO Base Format Box Types for HEIC. -/// -public enum HeicBoxType : uint -{ - FileType = FourCharacterCode.ftyp, - Meta = FourCharacterCode.meta, - MediaData = FourCharacterCode.mdat, - ItemInfo = FourCharacterCode.infe - ItemData = FourCharacterCode.idat, - ItemLocation = FourCharacterCode.iloc, - Exif = FourCharacterCode.Exif, - ItemPropertyAssociation = FourCharacterCode.ipma, - DataReference = FourCharacterCode.dref, - PrimaryItemReference = FourCharacterCode.pitm, - ImageSpatialExtentsProperty = FourCharacterCode.ispe, - - // Possible box types outside of HEIC images: - Movie = FourCharacterCode.moov, - Track = FourCharacterCode.trak, -} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 551696e912..a9e7c4d056 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.CodeAnalysis; +using System.Buffers.Binary; +using System.Text; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats.Heic; @@ -25,6 +25,8 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals /// private ImageMetadata? metadata; + private uint primaryItem; + private List items; private List itemLinks; @@ -37,6 +39,9 @@ public HeicDecoderCore(DecoderOptions options) { this.Options = options; this.configuration = options.Configuration; + this.metadata = new ImageMetadata(); + this.items = new List(); + this.itemLinks = new List(); } /// @@ -51,19 +56,19 @@ public Image Decode(BufferedReadStream stream, CancellationToken { if (!this.CheckFileTypeBox(stream)) { - throw new InvalidImageFormatException(); + throw new ImageFormatException("Not an HEIC image."); } - while (!stream.Eof) + while (stream.EofHitCount == 0) { - var length = ReadBoxHeader(stream, out var boxType); + long length = this.ReadBoxHeader(stream, out var boxType); switch (boxType) { - case HeicBoxType.Meta: - ParseMetadata(stream, length); + case FourCharacterCode.meta: + this.ParseMetadata(stream, length); break; - case HeicBoxType.MediaData: - ParseMediaData(stream, length); + case FourCharacterCode.mdat: + this.ParseMediaData(stream, length); break; default: throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'"); @@ -82,63 +87,64 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat { this.CheckFileTypeBox(stream); - while (!stream.Eof) + while (stream.EofHitCount == 0) { - var length = ReadBoxHeader(stream, out var boxType); - var buffer = new byte[length]; - stream.Read(buffer); + long length = this.ReadBoxHeader(stream, out uint boxType); switch (boxType) { - case HeicBoxType.Metadata: - ParseMetadata(buffer); + case FourCharacterCode.meta: + this.ParseMetadata(stream, length); break; default: // Silently skip all other box types. break; } } + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); } private bool CheckFileTypeBox(BufferedReadStream stream) { - var boxLength = ReadBoxHeader(stream, out var boxType); - Span buffer = stackalloc new byte[boxLength]; + var boxLength = this.ReadBoxHeader(stream, out var boxType); + Span buffer = stackalloc byte[(int)boxLength]; stream.Read(buffer); - var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buf); + var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + // TODO: Interpret minorVersion and compatible brands. - return boxTypepe == HeicBoxType.FileType && majorBrand == HeicConstants.HeicBrand; + return boxType == FourCharacterCode.ftyp && majorBrand == FourCharacterCode.heic; } - private ulong ReadBoxHeader(BufferedReadStream stream, out uint boxType) + private long ReadBoxHeader(BufferedReadStream stream, out uint boxType) { // Read 4 bytes of length of box - Span buf = stackalloc new byte[8]; + Span buf = stackalloc byte[8]; stream.Read(buf); - ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); - ulong headerSize = 8; + long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); + long headerSize = 8; + // Read 4 bytes of box type - boxType = BinaryPrimitives.ReadUInt32BigEndian(buf.Slice(4)); + boxType = BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); if (boxSize == 1) { stream.Read(buf); - boxSize = BinaryPrimitives.ReadUInt64BigEndian(buf); - headerSize += 8UL; + boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); + headerSize += 8; } return boxSize - headerSize; } - private uint ParseBoxHeader(Span buffer, out ulong length, out uint boxType) + private static int ParseBoxHeader(Span buffer, out long length, out uint boxType) { - ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); - ulong bytesRead = 4; - boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); + int bytesRead = 4; + boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; if (boxSize == 1) { - boxSize = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(bytesRead)); + boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); bytesRead += 8; } @@ -146,30 +152,32 @@ private uint ParseBoxHeader(Span buffer, out ulong length, out uint boxTyp return bytesRead; } - private void ParseMetadata(BufferedReadStream stream, uint boxLength) + private void ParseMetadata(BufferedReadStream stream, long boxLength) { - var endPosition = stream.Position + boxLength; + long endPosition = stream.Position + boxLength; while (stream.Position < endPosition) { - var length = ReadBoxHeader(stream, out var boxType); + long length = this.ReadBoxHeader(stream, out uint boxType); switch (boxType) { - case HeicMetaSubBoxType.ItemProperty: - ParseItemPropertyContainer(stream, length); + case FourCharacterCode.iprp: + this.ParseItemPropertyContainer(stream, length); break; - case HeicMetaSubBoxType.ItemInfo: - ParseItemInfo(stream, length); + case FourCharacterCode.iinf: + this.ParseItemInfo(stream, length); break; - case HeicMetaSubBoxType.ItemReference: - ParseItemReference(stream, length); + case FourCharacterCode.iref: + this.ParseItemReference(stream, length); break; - case HeicMetaSubBoxType.DataInformation: - case HeicMetaSubBoxType.GroupsList: - case HeicMetaSubBoxType.Handler: - case HeicMetaSubBoxType.ItemData: - case HeicMetaSubBoxType.ItemLocation: - case HeicMetaSubBoxType.ItemProtection: - case HeicMetaSubBoxType.PrimaryItem: + case FourCharacterCode.pitm: + this.ParsePrimaryItem(stream, length); + break; + case FourCharacterCode.dinf: + case FourCharacterCode.grpl: + case FourCharacterCode.hdlr: + case FourCharacterCode.idat: + case FourCharacterCode.iloc: + case FourCharacterCode.ipro: // TODO: Implement break; default: @@ -178,63 +186,65 @@ private void ParseMetadata(BufferedReadStream stream, uint boxLength) } } - private void ParseItemInfo(BufferedReadStream stream, uint boxLength) + private void ParseItemInfo(BufferedReadStream stream, long boxLength) { - Span buffer = new byte[length]; + Span buffer = stackalloc byte[(int)boxLength]; stream.Read(buffer); uint entryCount; int bytesRead = 0; if (buffer[bytesRead] == 0) { bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } else { bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } - for(uint i = 0; i < entryCount; i++) + for (uint i = 0; i < entryCount; i++) { - bytesRead += ParseBoxHeader(out var subBoxLength, out var boxType); - ParseItemInfoEntry(buffer.Slice(bytesRead, subBoxLength)); - bytesRead += subBoxLength; + bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]); } } - private void ParseItemInfoEntry(Span buffer, uint boxLength) + private int ParseItemInfoEntry(Span buffer) { - int bytesRead = 0; - var version = buffer[bytesRead]; + int bytesRead = ParseBoxHeader(buffer, out long boxLength, out uint boxType); + byte version = buffer[bytesRead]; bytesRead += 4; - var item = new HeicItem(); - if (version == 0 || version == 1) + HeicItem? item = null; + if (version is 0 or 1) { - item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + uint itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; - item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + item = new HeicItem(boxType, itemId); + + // Skip Protection Index, not sure what that means... bytesRead += 2; - item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; + // Optional field. if (bytesRead < boxLength) { - item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentEncoding.Length + 1; } } if (version == 1) { + // Optional fields. if (bytesRead < boxLength) { - item.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + item!.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } @@ -246,132 +256,162 @@ private void ParseItemInfoEntry(Span buffer, uint boxLength) if (version >= 2) { - if (getVersion() == 2) + uint itemId = 0U; + if (version == 2) { - item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - else if (getVersion() == 3) + else if (version == 3) { - item.Id = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } - item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + // Skip Protection Index, not sure what that means... bytesRead += 2; - item.Type = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; - item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item = new HeicItem(itemId, itemType); + item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - if (item.Type == "mime") + if (item.Type == FourCharacterCode.mime) { - item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; + // Optional field. if (bytesRead < boxLength) { - item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); + item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentEncoding.Length + 1; } } - else if (item.Type == "uri ") + else if (item.Type == FourCharacterCode.uri) { - item.UriType = ReadNullTerminatedString(buffer.Slice(bytesRead)); - bytesRead += item.ContentEncoding.Length + 1; + item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); + bytesRead += item.UriType.Length + 1; } } + + return bytesRead; } - private void ParseItemReference(BufferedReadStream stream, uint boxLength) + private void ParseItemReference(BufferedReadStream stream, long boxLength) { - Span buffer = new byte[length]; + Span buffer = new byte[boxLength]; stream.Read(buffer); int bytesRead = 0; bool largeIds = buffer[bytesRead] != 0; bytesRead += 4; - while(bytesRead < boxLength) + while (bytesRead < boxLength) { - ParseBoxHeader(buffer.Slice(bytesRead), out var subBoxLength, out var linkType); - var link = new HeicItemLink(); - link.Type = linkType; + ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out uint linkType); + uint sourceId; if (largeIds) { - link.Source = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } else { - link.Source = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - var count = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + HeicItemLink link = new(linkType, sourceId); + + int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; - for(uint i = 0; i < count; i++) + for (uint i = 0; i < count; i++) { uint destId; if (largeIds) { - destId = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); + destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; } else { - destId = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); + destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; } - link.Destinations.Add(destId); + + link.DestinationIds.Add(destId); } - itemLinks.Add(link); + this.itemLinks!.Add(link); } } - private void ParseItemPropertyContainer(BufferedReadStream stream, uint boxLength) + private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) { - var containerLength = ReadBoxHeader(stream, out var containerType); + // BoxLength should be 6 or 8. + Span buffer = stackalloc byte[(int)boxLength]; + stream.Read(buffer); + byte version = buffer[0]; + if (version == 0) + { + this.primaryItem = BinaryPrimitives.ReadUInt16BigEndian(buffer[4..]); + } + else + { + this.primaryItem = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + } + } + + private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) + { + // Cannot use Dictionary here, Properties can have multiple instances with the same key. + List> properties = new(); + long containerLength = this.ReadBoxHeader(stream, out uint containerType); if (containerType == FourCharacterCode.ipco) { // Parse Item Property Container, which is just an array of preperty boxes. - var endPosition = stream.Position + containerLength; + long endPosition = stream.Position + containerLength; while (stream.Position < endPosition) { - var length = ReadBoxHeader(stream, out var boxType); + int length = (int)this.ReadBoxHeader(stream, out uint boxType); + Span buffer = stackalloc byte[length]; switch (boxType) { - case HeicItemPropertyType.ImageSpatialExtents: + case FourCharacterCode.ispe: // Length should be 12. - Span buffer = stackalloc new byte[length]; stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). - var width = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); - var height = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8)); + uint width = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + uint height = BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + properties.Add(new KeyValuePair(FourCharacterCode.ispe, new uint[] { width, height })); break; - case HeicItemPropertyType.PixelAspectRatio: + case FourCharacterCode.pasp: // Length should be 8. - Span buffer = stackalloc new byte[length]; stream.Read(buffer); - var horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); - var verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); + uint horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); + uint verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + properties.Add(new KeyValuePair(FourCharacterCode.pasp, new uint[] { horizontalSpacing, verticalSpacing })); break; - case HeicItemPropertyType.PixelInformation: - Span buffer = stackalloc new byte[length]; + case FourCharacterCode.pixi: stream.Read(buffer); + // Skip over version (8 bits) and flags (24 bits). - var channelCount = buffer[4]; + int channelCount = buffer[4]; int offset = 5; int bitsPerPixel = 0; for (int i = 0; i < channelCount; i++) { - bitsPerPixel += buffer[i]; + bitsPerPixel += buffer[offset + i]; } + + properties.Add(new KeyValuePair(FourCharacterCode.pixi, new int[] { channelCount, bitsPerPixel })); + break; - case HeicItemPropertyType.AcessibilityText: - case HeicItemPropertyType.ImageMirror: - case HeicItemPropertyType.ImageRotation: - case HeicItemPropertyType.ImageScaling: - case HeicItemPropertyType.RelativeLocation: - case HeicItemPropertyType.UserDescription; + case FourCharacterCode.altt: + case FourCharacterCode.imir: + case FourCharacterCode.irot: + case FourCharacterCode.iscl: + case FourCharacterCode.rloc: + case FourCharacterCode.udes: // TODO: Implement break; default: @@ -382,42 +422,49 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, uint boxLengt else if (containerType == FourCharacterCode.ipma) { // Parse Item Property Association + Span buffer = stackalloc byte[(int)boxLength]; + byte version = buffer[0]; + byte flags = buffer[3]; + int itemId; + int bytesRead = 4; + if (version < 1) + { + itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + } + else + { + itemId = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + bytesRead += 4; + } + + int associationCount = buffer[bytesRead++]; + for (int i = 0; i < associationCount; i++) + { + uint propId; + if (flags == 1) + { + propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; + bytesRead += 2; + } + else + { + propId = buffer[bytesRead++] & 0x4FU; + } + + this.items![itemId].SetProperty(properties[(int)propId]); + } } } - private void ParseMediaData(BufferedReadStream stream, uint boxLength) + private void ParseMediaData(BufferedReadStream stream, long boxLength) { // TODO: Implement } - /// - /// Forwards the stream to just past the Start of NAL marker. - /// - private void FindStartOfNal(BufferedReadStream stream) + private static string ReadNullTerminatedString(Span span) { - uint i = stream.Position; - uint length = 0; - var dataLength = stream.Length; - - while (i < streamLength) - { - var current = stream.ReadByte(); - if (current == 0) - { - length++; - } - else if (length > 1 && current == 1) - { - // Found the marker ! - //length++; - break; - } - else - { - // False alarm, resetting... - length = 0; - } - i++; - } + Span bytes = span[..span.IndexOf((byte)0)]; + return Encoding.UTF8.GetString(bytes); } } diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index 291ece9d52..55f892a8ce 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -47,7 +47,6 @@ public void Encode(Image image, Stream stream, CancellationToken Guard.NotNull(stream, nameof(stream)); // TODO: Implement - stream.Flush(); } } diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index 583d9f4f54..eeda941092 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; namespace SixLabors.ImageSharp.Formats.Heic; @@ -11,8 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; public sealed class HeicImageFormatDetector : IImageFormatDetector { /// - int HeaderSize => 12; - + public int HeaderSize => 12; /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) @@ -21,10 +21,7 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I return format != null; } - private static bool IsSupportedFileFormat(ReadOnlySpan header) - { - return - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic - } + private static bool IsSupportedFileFormat(ReadOnlySpan header) => + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic; } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index 0dfc442d47..4349a65858 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -3,30 +3,62 @@ namespace SixLabors.ImageSharp.Formats.Heic; -public enum HeicItemType -{ - Hvc1, - Grid, - Exif -} - -public class HeicItemLink -{ - public uint Type; - public HeicItem Source; - public List Destinations = new List(); -} - /// /// Provides definition for a HEIC Item. /// -public class HeicItem +public class HeicItem(uint type, uint id) { - public uint Id; - public HeicItemType type; - public string Name; - public string ContentType; - public string ContentEncoding; - public uint ExtensionType; - public string UriType; + /// + /// Gets the ID of this Item. + /// + public uint Id { get; } = id; + + /// + /// Gets the type of this Item. + /// + public uint Type { get; } = type; + + /// + /// Gets or sets the name of this item. + /// + public string? Name { get; set; } + + /// + /// Gets or sets the Content Type of this item. + /// + public string? ContentType { get; set; } + + /// + /// Gets or sets the Content Encoding of this item. + /// + public string? ContentEncoding { get; set; } + + /// + /// Gets or sets the type of extension of this item. + /// + public uint ExtensionType { get; set; } + + /// + /// Gets or sets the URI of this item. + /// + public string? UriType { get; set; } + + /// + /// Sets a property on this item. + /// + public void SetProperty(KeyValuePair pair) + { + switch (pair.Key) + { + case FourCharacterCode.ispe: + // Set image extents + break; + case FourCharacterCode.pasp: + // Set pixel aspact ratio + break; + case FourCharacterCode.pixi: + // Set pixel information + break; + } + } } diff --git a/src/ImageSharp/Formats/Heic/HeicItemLink.cs b/src/ImageSharp/Formats/Heic/HeicItemLink.cs new file mode 100644 index 0000000000..a93946c2c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicItemLink.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Link between instances within the same HEIC file. +/// +public class HeicItemLink(uint type, uint sourceId) +{ + /// + /// Gets the type of link. + /// + public uint Type { get; } = type; + + /// + /// Gets the ID of the source item of this link. + /// + public uint SourceId { get; } = sourceId; + + /// + /// Gets the destination item IDs of this link. + /// + public List DestinationIds { get; } = new List(); +} diff --git a/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs b/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs deleted file mode 100644 index 157860f184..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported Item Property Types for HEIC. -/// -public enum HeicItemPropertyType : uint -{ - Invalid = 0, - AcessibilityText = FourCharacterCode.altt, - Colour = FourCharacterCode.colr, - HvcConfiguration = FourCharacterCode.hvcC, - ImageMirror = FourCharacterCode.imir, - ImageRotation = FourCharacterCode.irot, - ImageScaling = FourCharacterCode.iscl, - ImageSpatialExtents = FourCharacterCode.ispe, - PixelAspectRatio = FourCharacterCode.pasp, - PixelInformation = FourCharacterCode.pixi, - RelativeLocation = FourCharacterCode.rloc, - UserDescription = FourCharacterCode.udes, -} diff --git a/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs b/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs deleted file mode 100644 index 46d29878c3..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported sub type boxes within the 'meta' box for HEIC. -/// -public enum HeicMetaSubBoxType : uint -{ - Invalid = 0, - DataInformation = 0, // 'dinf' - GroupsList = 0, // 'grpl' - Handler = 0, // 'hdlr' - ItemData = 0, // 'idat' - ItemInfo = 0, // 'iinf' - ItemLocation = 0, // 'iloc' - ItemProperty = 0, // 'iprp' - ItemProtection = 0, // 'ipro' - ItemReference = 0, // 'iref' - PrimaryItem = 0, // 'pitm' -} From 96b92e847b4360bd58aac1f33bd1189f32ab5017 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 03:08:54 +0100 Subject: [PATCH 006/190] 4CC generated by template --- .../Formats/Heic/FourCharacterCode.cs | 219 ------------------ src/ImageSharp/Formats/Heic/HeicConstants.cs | 4 +- .../Formats/Heic/HeicDecoderCore.cs | 138 ++++++----- .../Formats/Heic/HeicFourCharacterCodes.cs | 201 ++++++++++++++++ .../Formats/Heic/HeicFourCharacterCodes.tt | 84 +++++++ .../Formats/Heic/HeicImageFormatDetector.cs | 4 +- src/ImageSharp/Formats/Heic/HeicItem.cs | 47 ++-- src/ImageSharp/Formats/Heic/HeicItemLink.cs | 4 +- .../Formats/Heic/HeicNalUnitType.cs | 36 --- src/ImageSharp/ImageSharp.csproj | 9 + 10 files changed, 416 insertions(+), 330 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heic/FourCharacterCode.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs create mode 100644 src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt delete mode 100644 src/ImageSharp/Formats/Heic/HeicNalUnitType.cs diff --git a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs b/src/ImageSharp/Formats/Heic/FourCharacterCode.cs deleted file mode 100644 index fc9ac748dc..0000000000 --- a/src/ImageSharp/Formats/Heic/FourCharacterCode.cs +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Buffers.Binary; -using System.CodeDom.Compiler; -using System.Text; - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides constants for 4 Character codes used in HEIC images. -/// -[GeneratedCode("T4", null)] -public static class FourCharacterCode -{ - // TODO: Create T4 template for this file - - /// - /// File Type - /// - public const uint ftyp = 0x66747970U; - - /// - /// Metadata container - /// - public const uint meta = 0x6D657461U; - - /// - /// Media Data - /// - public const uint mdat = 0x6D646174U; - - /// - /// Item Information Entry - /// - public const uint infe = 0x696E6665U; - - /// - /// Item Data - /// - public const uint idat = 0x69646174U; - - /// - /// Item Location - /// - public const uint iloc = 0x696C6F63U; - - /// - /// EXIF metadata - /// - public const uint Exif = 0x45786966U; - - /// - /// Data Reference - /// - public const uint dref = 0x64726566U; - - /// - /// Primary Item - /// - public const uint pitm = 0x7069746DU; - - /// - /// Item Spatial Extent - /// - public const uint ispe = 0x69737064U; - - /// - /// Alternative text - /// - public const uint altt = 0; // 'altt' - - /// - /// Colour information - /// - public const uint colr = 0; // 'colr' - - /// - /// HVC configuration - /// - public const uint hvcC = 0; // 'hvcC' - - /// - /// Image Mirror - /// - public const uint imir = 0; // 'imir' - - /// - /// Image Rotation - /// - public const uint irot = 0; // 'irot' - - /// - /// Image Scaling - /// - public const uint iscl = 0; // 'iscl' - - /// - /// Pixel Aspect Ration - /// - public const uint pasp = 0; // 'pasp' - - /// - /// Pixel Information - /// - public const uint pixi = 0x70697869U; - - /// - /// Reference Location - /// - public const uint rloc = 0; // 'rloc - - /// - /// User Description - /// - public const uint udes = 0; // 'udes' - - /// - /// Item Property Container - /// - public const uint ipco = 0; - - /// - /// Item Property Association - /// - public const uint ipma = 0; - - /// - /// High Efficient Image Coding - /// - public const uint heic = 0; - - /// - /// High Efficiency Coding tile - /// - public const uint hvc1 = 0; - - /// - /// Data Information - /// - public const uint dinf = 0; - - /// - /// Group list - /// - public const uint grpl = 0; - - /// - /// Handler - /// - public const uint hdlr = 0; - - /// - /// Item Information - /// - public const uint iinf = 0; // 'iinf' - - /// - /// Item Property - /// - public const uint iprp = 0; // 'iprp' - - /// - /// Item Protection - /// - public const uint ipro = 0; // 'ipro' - - /// - /// Item Reference - /// - public const uint iref = 0; // 'iref' - - /// - /// Grid - /// - public const uint grid = 0; // 'grid' - - /// - /// Derived Image - /// - public const uint dimg = 0; // 'dimg' - - /// - /// Thumbnail - /// - public const uint thmb = 0; // 'thmb' - - /// - /// Content Description - /// - public const uint cdsc = 0; // 'cdsc' - - /// - /// MIME type - /// - public const uint mime = 0; // 'mime' - - /// - /// URI - /// - public const uint uri = 0; // 'uri ' - - public static uint Parse(string code) - { - if (code.Length != 4) - { - throw new ImageFormatException("Unbale to parse FourCC code of more than 4 characters."); - } - Span span = Encoding.UTF8.GetBytes(code); - return BinaryPrimitives.ReadUInt32BigEndian(span); - } - - public static string ToString(uint fourcc) - { - Span span = stackalloc byte[4]; - BinaryPrimitives.WriteUInt32BigEndian(span, fourcc); - return Encoding.UTF8.GetString(span); - } -} diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heic/HeicConstants.cs index 62ee248f01..809818e403 100644 --- a/src/ImageSharp/Formats/Heic/HeicConstants.cs +++ b/src/ImageSharp/Formats/Heic/HeicConstants.cs @@ -8,12 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// internal static class HeicConstants { - public const uint HeicBrand = FourCharacterCode.heic; + public const Heic4CharCode HeicBrand = Heic4CharCode.heic; /// /// The list of mimetypes that equate to a HEIC. /// - public static readonly IEnumerable MimeTypes = new[] { "image/heif", "image/heif-sequence", "image/heic", "image/heic-sequence", "image/avif" }; + public static readonly IEnumerable MimeTypes = new[] { "image/heif", "image/heic", "image/avif" }; /// /// The list of file extensions that equate to a HEIC. diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index a9e7c4d056..93efcd21d1 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -61,21 +61,27 @@ public Image Decode(BufferedReadStream stream, CancellationToken while (stream.EofHitCount == 0) { - long length = this.ReadBoxHeader(stream, out var boxType); + long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) { - case FourCharacterCode.meta: + case Heic4CharCode.meta: this.ParseMetadata(stream, length); break; - case FourCharacterCode.mdat: + case Heic4CharCode.mdat: this.ParseMediaData(stream, length); break; default: - throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'"); + throw new ImageFormatException($"Unknown box type of '{Enum.GetName(boxType)}'"); } } - var image = new Image(this.configuration, this.pixelSize.Width, this.pixelSize.Height, this.metadata); + HeicItem? item = this.FindItemById(this.primaryItem); + if (item == null) + { + throw new ImageFormatException("No primary item found"); + } + + var image = new Image(this.configuration, item.Extent.Width, item.Extent.Height, this.metadata); Buffer2D pixels = image.GetRootFramePixelBuffer(); @@ -89,10 +95,10 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat while (stream.EofHitCount == 0) { - long length = this.ReadBoxHeader(stream, out uint boxType); + long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) { - case FourCharacterCode.meta: + case Heic4CharCode.meta: this.ParseMetadata(stream, length); break; default: @@ -101,21 +107,27 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat } } - return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); + HeicItem? item = this.FindItemById(this.primaryItem); + if (item == null) + { + throw new ImageFormatException("No primary item found"); + } + + return new ImageInfo(new PixelTypeInfo(item.BitsPerPixel), new(item.Extent.Width, item.Extent.Height), this.metadata); } private bool CheckFileTypeBox(BufferedReadStream stream) { - var boxLength = this.ReadBoxHeader(stream, out var boxType); + var boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); Span buffer = stackalloc byte[(int)boxLength]; stream.Read(buffer); var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); // TODO: Interpret minorVersion and compatible brands. - return boxType == FourCharacterCode.ftyp && majorBrand == FourCharacterCode.heic; + return boxType == Heic4CharCode.ftyp && majorBrand == (uint)Heic4CharCode.heic; } - private long ReadBoxHeader(BufferedReadStream stream, out uint boxType) + private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) { // Read 4 bytes of length of box Span buf = stackalloc byte[8]; @@ -124,7 +136,7 @@ private long ReadBoxHeader(BufferedReadStream stream, out uint boxType) long headerSize = 8; // Read 4 bytes of box type - boxType = BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); + boxType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); if (boxSize == 1) { @@ -136,11 +148,11 @@ private long ReadBoxHeader(BufferedReadStream stream, out uint boxType) return boxSize - headerSize; } - private static int ParseBoxHeader(Span buffer, out long length, out uint boxType) + private static int ParseBoxHeader(Span buffer, out long length, out Heic4CharCode boxType) { long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); int bytesRead = 4; - boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + boxType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; if (boxSize == 1) { @@ -157,31 +169,31 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) long endPosition = stream.Position + boxLength; while (stream.Position < endPosition) { - long length = this.ReadBoxHeader(stream, out uint boxType); + long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) { - case FourCharacterCode.iprp: + case Heic4CharCode.iprp: this.ParseItemPropertyContainer(stream, length); break; - case FourCharacterCode.iinf: + case Heic4CharCode.iinf: this.ParseItemInfo(stream, length); break; - case FourCharacterCode.iref: + case Heic4CharCode.iref: this.ParseItemReference(stream, length); break; - case FourCharacterCode.pitm: + case Heic4CharCode.pitm: this.ParsePrimaryItem(stream, length); break; - case FourCharacterCode.dinf: - case FourCharacterCode.grpl: - case FourCharacterCode.hdlr: - case FourCharacterCode.idat: - case FourCharacterCode.iloc: - case FourCharacterCode.ipro: + case Heic4CharCode.dinf: + case Heic4CharCode.grpl: + case Heic4CharCode.hdlr: + case Heic4CharCode.idat: + case Heic4CharCode.iloc: + case Heic4CharCode.ipro: // TODO: Implement break; default: - throw new ImageFormatException($"Unknown metadata box type of '{FourCharacterCode.ToString(boxType)}'"); + throw new ImageFormatException($"Unknown metadata box type of '{Enum.GetName(boxType)}'"); } } } @@ -213,7 +225,7 @@ private void ParseItemInfo(BufferedReadStream stream, long boxLength) private int ParseItemInfoEntry(Span buffer) { - int bytesRead = ParseBoxHeader(buffer, out long boxLength, out uint boxType); + int bytesRead = ParseBoxHeader(buffer, out long boxLength, out Heic4CharCode boxType); byte version = buffer[bytesRead]; bytesRead += 4; HeicItem? item = null; @@ -240,7 +252,6 @@ private int ParseItemInfoEntry(Span buffer) if (version == 1) { - // Optional fields. if (bytesRead < boxLength) { @@ -272,10 +283,10 @@ private int ParseItemInfoEntry(Span buffer) bytesRead += 2; uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; - item = new HeicItem(itemId, itemType); + item = new HeicItem((Heic4CharCode)itemId, itemType); item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - if (item.Type == FourCharacterCode.mime) + if (item.Type == Heic4CharCode.mime) { item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; @@ -287,7 +298,7 @@ private int ParseItemInfoEntry(Span buffer) bytesRead += item.ContentEncoding.Length + 1; } } - else if (item.Type == FourCharacterCode.uri) + else if (item.Type == Heic4CharCode.uri) { item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.UriType.Length + 1; @@ -306,7 +317,7 @@ private void ParseItemReference(BufferedReadStream stream, long boxLength) bytesRead += 4; while (bytesRead < boxLength) { - ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out uint linkType); + ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); uint sourceId; if (largeIds) { @@ -363,35 +374,35 @@ private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) { // Cannot use Dictionary here, Properties can have multiple instances with the same key. - List> properties = new(); - long containerLength = this.ReadBoxHeader(stream, out uint containerType); - if (containerType == FourCharacterCode.ipco) + List> properties = new(); + long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType); + if (containerType == Heic4CharCode.ipco) { // Parse Item Property Container, which is just an array of preperty boxes. long endPosition = stream.Position + containerLength; while (stream.Position < endPosition) { - int length = (int)this.ReadBoxHeader(stream, out uint boxType); + int length = (int)this.ReadBoxHeader(stream, out Heic4CharCode boxType); Span buffer = stackalloc byte[length]; switch (boxType) { - case FourCharacterCode.ispe: + case Heic4CharCode.ispe: // Length should be 12. stream.Read(buffer); // Skip over version (8 bits) and flags (24 bits). - uint width = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - uint height = BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); - properties.Add(new KeyValuePair(FourCharacterCode.ispe, new uint[] { width, height })); + int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + properties.Add(new KeyValuePair(Heic4CharCode.ispe, new Size(width, height))); break; - case FourCharacterCode.pasp: + case Heic4CharCode.pasp: // Length should be 8. stream.Read(buffer); - uint horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); - uint verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - properties.Add(new KeyValuePair(FourCharacterCode.pasp, new uint[] { horizontalSpacing, verticalSpacing })); + int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); + int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + properties.Add(new KeyValuePair(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); break; - case FourCharacterCode.pixi: + case Heic4CharCode.pixi: stream.Read(buffer); // Skip over version (8 bits) and flags (24 bits). @@ -403,23 +414,23 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt bitsPerPixel += buffer[offset + i]; } - properties.Add(new KeyValuePair(FourCharacterCode.pixi, new int[] { channelCount, bitsPerPixel })); + properties.Add(new KeyValuePair(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); break; - case FourCharacterCode.altt: - case FourCharacterCode.imir: - case FourCharacterCode.irot: - case FourCharacterCode.iscl: - case FourCharacterCode.rloc: - case FourCharacterCode.udes: + case Heic4CharCode.altt: + case Heic4CharCode.imir: + case Heic4CharCode.irot: + case Heic4CharCode.iscl: + case Heic4CharCode.rloc: + case Heic4CharCode.udes: // TODO: Implement break; default: - throw new ImageFormatException($"Unknown item property box type of '{FourCharacterCode.ToString(boxType)}'"); + throw new ImageFormatException($"Unknown item property box type of '{Enum.GetName(boxType)}'"); } } } - else if (containerType == FourCharacterCode.ipma) + else if (containerType == Heic4CharCode.ipma) { // Parse Item Property Association Span buffer = stackalloc byte[(int)boxLength]; @@ -452,7 +463,21 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt propId = buffer[bytesRead++] & 0x4FU; } - this.items![itemId].SetProperty(properties[(int)propId]); + KeyValuePair prop = properties[(int)propId]; + switch (prop.Key) + { + case Heic4CharCode.ispe: + this.items[itemId].SetExtent((Size)prop.Value); + break; + case Heic4CharCode.pasp: + this.items[itemId].PixelAspectRatio = (Size)prop.Value; + break; + case Heic4CharCode.pixi: + int[] values = (int[])prop.Value; + this.items[itemId].ChannelCount = values[0]; + this.items[itemId].BitsPerPixel = values[1]; + break; + } } } } @@ -462,6 +487,9 @@ private void ParseMediaData(BufferedReadStream stream, long boxLength) // TODO: Implement } + private HeicItem? FindItemById(uint itemId) + => this.items.FirstOrDefault(item => item.Id == itemId); + private static string ReadNullTerminatedString(Span span) { Span bytes = span[..span.IndexOf((byte)0)]; diff --git a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs b/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs new file mode 100644 index 0000000000..c7da21d0fb --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs @@ -0,0 +1,201 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// + +using System.CodeDom.Compiler; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Supported 4 character codes for use in HEIC images. +/// +[GeneratedCode("T4", null)] +public enum Heic4CharCode : uint +{ + /// + /// File Type. + /// + ftyp = 0x66747970U, + + /// + /// Metadata. + /// + meta = 0x6D657461U, + + /// + /// Media Data. + /// + mdat = 0x6D646174U, + + /// + /// Item Information Entry. + /// + infe = 0x696E6665U, + + /// + /// Item Data. + /// + idat = 0x69646174U, + + /// + /// Item Location. + /// + iloc = 0x696C6F63U, + + /// + /// EXIF metadata. + /// + Exif = 0x45786966U, + + /// + /// Data Reference. + /// + dref = 0x64726566U, + + /// + /// Primary Item. + /// + pitm = 0x7069746DU, + + /// + /// Item Spatial Extent. + /// + ispe = 0x69737065U, + + /// + /// Alternative text. + /// + altt = 0x616C7474U, + + /// + /// Colour information. + /// + colr = 0x636F6C72U, + + /// + /// HVC configuration. + /// + hvcC = 0x68766343U, + + /// + /// Image Mirror. + /// + imir = 0x696D6972U, + + /// + /// Image Rotation. + /// + irot = 0x69726F74U, + + /// + /// Image Scaling. + /// + iscl = 0x6973636CU, + + /// + /// Pixel Aspect Ratio. + /// + pasp = 0x70617370U, + + /// + /// Pixel Information. + /// + pixi = 0x70697869U, + + /// + /// Reference Location. + /// + rloc = 0x726C6F63U, + + /// + /// User Description. + /// + udes = 0x75646573U, + + /// + /// Item Property Container. + /// + ipco = 0x6970636FU, + + /// + /// Item Property Association. + /// + ipma = 0x69706D61U, + + /// + /// High Efficient Image Coding. + /// + heic = 0x68656963U, + + /// + /// High Efficiency Coding tile. + /// + hvc1 = 0x68766331U, + + /// + /// Data Information. + /// + dinf = 0x64696E66U, + + /// + /// Group list. + /// + grpl = 0x6772706CU, + + /// + /// Handler. + /// + hdlr = 0x68646C72U, + + /// + /// Item Information. + /// + iinf = 0x69696E66U, + + /// + /// Item Property. + /// + iprp = 0x69707270U, + + /// + /// Item Protection. + /// + ipro = 0x6970726FU, + + /// + /// Item Reference. + /// + iref = 0x69726566U, + + /// + /// Grid. + /// + grid = 0x67726964U, + + /// + /// Derived Image. + /// + dimg = 0x64696D67U, + + /// + /// Thumbnail. + /// + thmb = 0x74686D62U, + + /// + /// Content Description. + /// + cdsc = 0x63647363U, + + /// + /// MIME type. + /// + mime = 0x6D696D65U, + + /// + /// URI. + /// + uri = 0x75726920U, + +} diff --git a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt b/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt new file mode 100644 index 0000000000..f2b1ec7cc8 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt @@ -0,0 +1,84 @@ +<#@ template language="C#" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +// +<# + var codes = new []{ + "ftyp", "File Type", + "meta", "Metadata", + "mdat", "Media Data", + "infe", "Item Information Entry", + "idat", "Item Data", + "iloc", "Item Location", + "Exif", "EXIF metadata", + "dref", "Data Reference", + "pitm", "Primary Item", + "ispe", "Item Spatial Extent", + "altt", "Alternative text", + "colr", "Colour information", + "hvcC", "HVC configuration", + "imir", "Image Mirror", + "irot", "Image Rotation", + "iscl", "Image Scaling", + "pasp", "Pixel Aspect Ratio", + "pixi", "Pixel Information", + "rloc", "Reference Location", + "udes", "User Description", + "ipco", "Item Property Container", + "ipma", "Item Property Association", + "heic", "High Efficient Image Coding", + "hvc1", "High Efficiency Coding tile", + "dinf", "Data Information", + "grpl", "Group list", + "hdlr", "Handler", + "iinf", "Item Information", + "iprp", "Item Property", + "ipro", "Item Protection", + "iref", "Item Reference", + "grid", "Grid", + "dimg", "Derived Image", + "thmb", "Thumbnail", + "cdsc", "Content Description", + "mime", "MIME type", + "uri ", "URI", + }; +#> + +using System.CodeDom.Compiler; + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Supported 4 character codes for use in HEIC images. +/// +[GeneratedCode("T4", null)] +public enum Heic4CharCode : uint +{ +<# + for (int i = 0; i < codes.Length; i += 2) + { + string shortName = codes[i]; + string longName = codes[i + 1]; + string hex = Code2Hex(shortName); +#> + /// + /// <#= longName #>. + /// + <#= shortName #> = <#= hex #>, + +<# + } +#> +} +<#+ + +private string Code2Hex(string code) +{ + byte[] b = Encoding.ASCII.GetBytes(code); + return String.Format("0x{0:X2}{1:X2}{2:X2}{3:X2}U", b[0], b[1], b[2], b[3]); +} + +#> diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index eeda941092..4a8195e2bd 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -22,6 +22,6 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I } private static bool IsSupportedFileFormat(ReadOnlySpan header) => - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp && - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic; + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp && + BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == (uint)Heic4CharCode.heic; } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index 4349a65858..654eecd53d 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Provides definition for a HEIC Item. /// -public class HeicItem(uint type, uint id) +public class HeicItem(Heic4CharCode type, uint id) { /// /// Gets the ID of this Item. @@ -16,7 +16,7 @@ public class HeicItem(uint type, uint id) /// /// Gets the type of this Item. /// - public uint Type { get; } = type; + public Heic4CharCode Type { get; } = type; /// /// Gets or sets the name of this item. @@ -44,21 +44,40 @@ public class HeicItem(uint type, uint id) public string? UriType { get; set; } /// - /// Sets a property on this item. + /// Gets or sets the aspect ratio of the pixels. /// - public void SetProperty(KeyValuePair pair) + public Size PixelAspectRatio { get; set; } + + /// + /// Gets or sets the number of color channels in each pixel. + /// + public int ChannelCount { get; set; } + + /// + /// Gets or sets the number of bits in a single pixel. + /// + public int BitsPerPixel { get; set; } + + /// + /// Gets the spatial extent of this item. + /// + public Size Extent { get; private set; } + + /// + /// Gets the spatial extent of this grid cells in this item. + /// + public Size GridCellExtent { get; private set; } + + public void SetExtent(Size extent) { - switch (pair.Key) + if (this.Extent == default) + { + this.Extent = extent; + } + else { - case FourCharacterCode.ispe: - // Set image extents - break; - case FourCharacterCode.pasp: - // Set pixel aspact ratio - break; - case FourCharacterCode.pixi: - // Set pixel information - break; + this.GridCellExtent = extent; } } + } diff --git a/src/ImageSharp/Formats/Heic/HeicItemLink.cs b/src/ImageSharp/Formats/Heic/HeicItemLink.cs index a93946c2c4..da1578fcfa 100644 --- a/src/ImageSharp/Formats/Heic/HeicItemLink.cs +++ b/src/ImageSharp/Formats/Heic/HeicItemLink.cs @@ -6,12 +6,12 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Link between instances within the same HEIC file. /// -public class HeicItemLink(uint type, uint sourceId) +public class HeicItemLink(Heic4CharCode type, uint sourceId) { /// /// Gets the type of link. /// - public uint Type { get; } = type; + public Heic4CharCode Type { get; } = type; /// /// Gets the ID of the source item of this link. diff --git a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs b/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs deleted file mode 100644 index e12abf3c8d..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicNalUnitType.cs +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Provides enumeration of supported x265's LAN Unit Types. -/// -public enum HeicNalUnitType : byte -{ - CODED_SLICE_TRAIL_N = 0, - CODED_SLICE_TRAIL_R = 1, - - CODED_SLICE_TSA_N = 2, - CODED_SLICE_TSA_R = 3, - - CODED_SLICE_STSA_N = 4, - CODED_SLICE_STSA_R = 5, - - CODED_SLICE_RADL_N = 6, - CODED_SLICE_RADL_R = 7, - - CODED_SLICE_RASL_N = 8, - CODED_SLICE_RASL_R = 9, - - VParameterSet = 32, - SequenceParameterSet = 33, - PictureParameterSet = 34, - AccessUnitDelimiter = 35, - EndOfSequence = 36, - IsEndOfStream = 37, - FillerData = 38, - PrefixSei = 39, - SuffixSei = 40, - Invalid = 64, -} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6096bd33e3..c34250b71c 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -52,6 +52,11 @@ + + True + True + HeicFourCharacterCodes.tt + True True @@ -150,6 +155,10 @@ + + TextTemplatingFileGenerator + HeicFourCharacterCodes.cs + TextTemplatingFileGenerator Block8x8F.Generated.cs From 9ad76ce6a0e0456598d71c1c9550c87783b37b08 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 12:30:53 +0100 Subject: [PATCH 007/190] Rename Heic4CharCode files --- .../Heic/{HeicFourCharacterCodes.cs => Heic4CharCode.cs} | 2 +- .../Heic/{HeicFourCharacterCodes.tt => Heic4CharCode.tt} | 2 +- src/ImageSharp/ImageSharp.csproj | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/ImageSharp/Formats/Heic/{HeicFourCharacterCodes.cs => Heic4CharCode.cs} (99%) rename src/ImageSharp/Formats/Heic/{HeicFourCharacterCodes.tt => Heic4CharCode.tt} (98%) diff --git a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs similarity index 99% rename from src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs rename to src/ImageSharp/Formats/Heic/Heic4CharCode.cs index c7da21d0fb..2dddbbfd1a 100644 --- a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Supported 4 character codes for use in HEIC images. /// -[GeneratedCode("T4", null)] +[GeneratedCode("T4", "")] public enum Heic4CharCode : uint { /// diff --git a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt similarity index 98% rename from src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt rename to src/ImageSharp/Formats/Heic/Heic4CharCode.tt index f2b1ec7cc8..d6b0870e24 100644 --- a/src/ImageSharp/Formats/Heic/HeicFourCharacterCodes.tt +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt @@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; /// /// Supported 4 character codes for use in HEIC images. /// -[GeneratedCode("T4", null)] +[GeneratedCode("T4", "")] public enum Heic4CharCode : uint { <# diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index c34250b71c..06e7d13336 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -52,10 +52,10 @@ - + True True - HeicFourCharacterCodes.tt + Heic4CharCode.tt True @@ -155,9 +155,9 @@ - + TextTemplatingFileGenerator - HeicFourCharacterCodes.cs + Heic4CharCode.cs TextTemplatingFileGenerator From c3ce98000a3a2b3e9d33a758bae2370a5ccdc397 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 12:48:18 +0100 Subject: [PATCH 008/190] Use single buffer per decoder instance --- .../Formats/Heic/HeicDecoderCore.cs | 236 +++++++++++------- src/ImageSharp/Formats/Heic/HeicItem.cs | 8 +- 2 files changed, 151 insertions(+), 93 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 93efcd21d1..376dd466ed 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -27,9 +27,11 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals private uint primaryItem; - private List items; + private readonly List items; - private List itemLinks; + private readonly List itemLinks; + + private readonly byte[] buffer; /// /// Initializes a new instance of the class. @@ -42,6 +44,7 @@ public HeicDecoderCore(DecoderOptions options) this.metadata = new ImageMetadata(); this.items = new List(); this.itemLinks = new List(); + this.buffer = new byte[80]; } /// @@ -83,6 +86,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken var image = new Image(this.configuration, item.Extent.Width, item.Extent.Height, this.metadata); + // TODO: Parse pixel data Buffer2D pixels = image.GetRootFramePixelBuffer(); return image; @@ -103,6 +107,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat break; default: // Silently skip all other box types. + SkipBox(stream, length); break; } } @@ -118,10 +123,9 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat private bool CheckFileTypeBox(BufferedReadStream stream) { - var boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); - Span buffer = stackalloc byte[(int)boxLength]; - stream.Read(buffer); - var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + Span buffer = this.ReadIntoBuffer(stream, boxLength); + uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); // TODO: Interpret minorVersion and compatible brands. return boxType == Heic4CharCode.ftyp && majorBrand == (uint)Heic4CharCode.heic; @@ -130,8 +134,7 @@ private bool CheckFileTypeBox(BufferedReadStream stream) private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) { // Read 4 bytes of length of box - Span buf = stackalloc byte[8]; - stream.Read(buf); + Span buf = this.ReadIntoBuffer(stream, 8); long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); long headerSize = 8; @@ -140,7 +143,7 @@ private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) if (boxSize == 1) { - stream.Read(buf); + buf = this.ReadIntoBuffer(stream, 8); boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); headerSize += 8; } @@ -185,12 +188,21 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) this.ParsePrimaryItem(stream, length); break; case Heic4CharCode.dinf: - case Heic4CharCode.grpl: + SkipBox(stream, length); + break; case Heic4CharCode.hdlr: + SkipBox(stream, length); + break; case Heic4CharCode.idat: + SkipBox(stream, length); + break; case Heic4CharCode.iloc: + this.ParseItemLocation(stream, length); + break; + case Heic4CharCode.grpl: case Heic4CharCode.ipro: // TODO: Implement + SkipBox(stream, length); break; default: throw new ImageFormatException($"Unknown metadata box type of '{Enum.GetName(boxType)}'"); @@ -200,22 +212,12 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) private void ParseItemInfo(BufferedReadStream stream, long boxLength) { - Span buffer = stackalloc byte[(int)boxLength]; - stream.Read(buffer); + Span buffer = this.ReadIntoBuffer(stream, boxLength); uint entryCount; int bytesRead = 0; - if (buffer[bytesRead] == 0) - { - bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; - } - else - { - bytesRead += 4; - entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; - } + byte version = buffer[bytesRead]; + bytesRead += 4; + entryCount = ReadUInt16Or32(buffer, version != 0, ref bytesRead); for (uint i = 0; i < entryCount; i++) { @@ -267,17 +269,7 @@ private int ParseItemInfoEntry(Span buffer) if (version >= 2) { - uint itemId = 0U; - if (version == 2) - { - itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; - } - else if (version == 3) - { - itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; - } + uint itemId = ReadUInt16Or32(buffer, version == 3, ref bytesRead); // Skip Protection Index, not sure what that means... bytesRead += 2; @@ -310,44 +302,21 @@ private int ParseItemInfoEntry(Span buffer) private void ParseItemReference(BufferedReadStream stream, long boxLength) { - Span buffer = new byte[boxLength]; - stream.Read(buffer); + Span buffer = this.ReadIntoBuffer(stream, boxLength); int bytesRead = 0; bool largeIds = buffer[bytesRead] != 0; bytesRead += 4; while (bytesRead < boxLength) { ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); - uint sourceId; - if (largeIds) - { - sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; - } - else - { - sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; - } - + uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); HeicItemLink link = new(linkType, sourceId); int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; for (uint i = 0; i < count; i++) { - uint destId; - if (largeIds) - { - destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; - } - else - { - destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; - } - + uint destId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); link.DestinationIds.Add(destId); } @@ -358,17 +327,10 @@ private void ParseItemReference(BufferedReadStream stream, long boxLength) private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) { // BoxLength should be 6 or 8. - Span buffer = stackalloc byte[(int)boxLength]; - stream.Read(buffer); + Span buffer = this.ReadIntoBuffer(stream, boxLength); byte version = buffer[0]; - if (version == 0) - { - this.primaryItem = BinaryPrimitives.ReadUInt16BigEndian(buffer[4..]); - } - else - { - this.primaryItem = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - } + int bytesRead = 0; + this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); } private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) @@ -383,28 +345,21 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt while (stream.Position < endPosition) { int length = (int)this.ReadBoxHeader(stream, out Heic4CharCode boxType); - Span buffer = stackalloc byte[length]; + Span buffer = this.ReadIntoBuffer(stream, boxLength); switch (boxType) { case Heic4CharCode.ispe: - // Length should be 12. - stream.Read(buffer); - // Skip over version (8 bits) and flags (24 bits). int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); properties.Add(new KeyValuePair(Heic4CharCode.ispe, new Size(width, height))); break; case Heic4CharCode.pasp: - // Length should be 8. - stream.Read(buffer); int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); properties.Add(new KeyValuePair(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); break; case Heic4CharCode.pixi: - stream.Read(buffer); - // Skip over version (8 bits) and flags (24 bits). int channelCount = buffer[4]; int offset = 5; @@ -424,6 +379,7 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt case Heic4CharCode.rloc: case Heic4CharCode.udes: // TODO: Implement + SkipBox(stream, length); break; default: throw new ImageFormatException($"Unknown item property box type of '{Enum.GetName(boxType)}'"); @@ -433,21 +389,11 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt else if (containerType == Heic4CharCode.ipma) { // Parse Item Property Association - Span buffer = stackalloc byte[(int)boxLength]; + Span buffer = this.ReadIntoBuffer(stream, boxLength); byte version = buffer[0]; byte flags = buffer[3]; - int itemId; int bytesRead = 4; - if (version < 1) - { - itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; - } - else - { - itemId = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; - } + int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); int associationCount = buffer[bytesRead++]; for (int i = 0; i < associationCount; i++) @@ -482,9 +428,115 @@ private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLengt } } + private void ParseItemLocation(BufferedReadStream stream, long boxLength) + { + Span buffer = this.ReadIntoBuffer(stream, boxLength); + int bytesRead = 0; + byte version = buffer[bytesRead++]; + byte b1 = buffer[bytesRead++]; + byte b2 = buffer[bytesRead++]; + int offsetSize = (b1 >> 4) & 0x0f; + int lengthSize = b1 & 0x0f; + int baseOffsetSize = (b2 >> 4) & 0x0f; + int indexSize = 0; + if (version is 1 or 2) + { + indexSize = b2 & 0x0f; + } + + uint itemCount = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); + for (uint i = 0; i < itemCount; i++) + { + uint itemId = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); + if (version is 1 or 2) + { + bytesRead += 2; + byte b3 = buffer[bytesRead++]; + int constructionMethod = b3 & 0x0f; + } + + uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + ulong baseOffset = ReadUIntVariable(buffer, baseOffsetSize, ref bytesRead); + uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + for (uint j = 0; j < extentCount; j++) + { + if (version is 1 or 2 && indexSize > 0) + { + ulong extentIndex = ReadUIntVariable(buffer, indexSize, ref bytesRead); + } + + ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); + ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); + } + } + } + + private static uint ReadUInt16Or32(Span buffer, bool isLarge, ref int bytesRead) + { + uint result; + if (isLarge) + { + result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + bytesRead += 4; + } + else + { + result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + } + + return result; + } + + private static ulong ReadUIntVariable(Span buffer, int numBytes, ref int bytesRead) + { + ulong result = 0UL; + if (numBytes == 8) + { + result = BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); + bytesRead += 8; + } + else if (numBytes == 4) + { + result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + bytesRead += 4; + } + else if (numBytes == 2) + { + result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + bytesRead += 2; + } + else if (numBytes == 1) + { + result = buffer[bytesRead++]; + } + + return result; + } + private void ParseMediaData(BufferedReadStream stream, long boxLength) { - // TODO: Implement + SkipBox(stream, boxLength); + } + + private static void SkipBox(BufferedReadStream stream, long boxLength) + => stream.Skip((int)boxLength); + + private Span ReadIntoBuffer(BufferedReadStream stream, long length) + { + if (length <= this.buffer.Length) + { + stream.Read(this.buffer, 0, (int)length); + return this.buffer; + } + else + { + Span temp = new byte[length]; + stream.Read(temp); + return temp; + } } private HeicItem? FindItemById(uint itemId) diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index 654eecd53d..481ed04860 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -68,6 +68,13 @@ public class HeicItem(Heic4CharCode type, uint id) /// public Size GridCellExtent { get; private set; } + /// + /// Set the image extent. + /// + /// The size to set the extent to. + /// + /// Might be called twice for a grid, in which case the second call is the cell extent. + /// public void SetExtent(Size extent) { if (this.Extent == default) @@ -79,5 +86,4 @@ public void SetExtent(Size extent) this.GridCellExtent = extent; } } - } From ff9629a2f35bf3d6893463774cb0607d091d0ffb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 13:00:18 +0100 Subject: [PATCH 009/190] Add another sample image --- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Heic/dwsample-heic-640.heic | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 tests/Images/Input/Heic/dwsample-heic-640.heic diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d078faa6d4..a690f3f310 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1111,5 +1111,6 @@ public static class Heic public const string Image2 = "Heic/image2.heic"; public const string Image3 = "Heic/image3.heic"; public const string Image4 = "Heic/image4.heic"; + public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; } } diff --git a/tests/Images/Input/Heic/dwsample-heic-640.heic b/tests/Images/Input/Heic/dwsample-heic-640.heic new file mode 100644 index 0000000000..5b40f44e05 --- /dev/null +++ b/tests/Images/Input/Heic/dwsample-heic-640.heic @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9249ef2c23113ee8a5de5e99f3eff353652abbb081bc26a66772769ee03c51b4 +size 191883 From 028e8a5dbf64c8f597085a01e2f48ec9ed9ccf84 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 13:43:46 +0100 Subject: [PATCH 010/190] Initial tests --- .../Formats/Heic/HeicDecoderTests.cs | 42 +++++++++++++++++++ .../Formats/Heic/HeicEncoderTests.cs | 32 ++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs new file mode 100644 index 0000000000..108eba286b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Formats.Heic; + +[Trait("Format", "Heic")] +[ValidateDisposedMemoryAllocations] +public class HeicDecoderTests +{ + [Theory] + [InlineData(TestImages.Heic.Image1)] + [InlineData(TestImages.Heic.Sample640x427)] + public void Identify(string imagePath) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(stream); + HeicMetadata heicMetadata = imageInfo.Metadata.GetHeicMetadata(); + + Assert.NotNull(imageInfo); + Assert.Equal(imageInfo.Metadata.DecodedImageFormat, HeicFormat.Instance); + //Assert.Equal(heicMetadata.Channels, channels); + } + + [Theory] + [WithFile(TestImages.Heic.Image1, PixelTypes.Rgba32)] + [WithFile(TestImages.Heic.Sample640x427, PixelTypes.Rgba32)] + public void Decode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + HeicMetadata qoiMetadata = image.Metadata.GetHeicMetadata(); + image.DebugSave(provider); + + image.CompareToReferenceOutput(provider); + //Assert.Equal(heicMetadata.Channels, channels); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs new file mode 100644 index 0000000000..8bfbce973f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +namespace SixLabors.ImageSharp.Tests.Formats.Heic; + +[Trait("Format", "Heic")] +[ValidateDisposedMemoryAllocations] +public class HeicEncoderTests +{ + [Theory] + [WithFile(TestImages.Heic.Sample640x427, PixelTypes.Rgba32)] + public static void Encode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(new MagickReferenceDecoder()); + using MemoryStream stream = new(); + HeicEncoder encoder = new(); + image.Save(stream, encoder); + stream.Position = 0; + + using Image encodedImage = (Image)Image.Load(stream); + HeicMetadata heicMetadata = encodedImage.Metadata.GetHeicMetadata(); + + ImageComparer.Exact.CompareImages(image, encodedImage); + //Assert.Equal(heicMetadata.Channels, channels); + } +} From f892c0475f218f71c24f4628cfc853ec480ea928 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 15:53:22 +0100 Subject: [PATCH 011/190] Byte alignment fixes --- .../Formats/Heic/HeicDecoderCore.cs | 213 ++++++++++-------- 1 file changed, 121 insertions(+), 92 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 376dd466ed..a14fa2588a 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using System.Globalization; using System.Text; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -62,7 +63,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken throw new ImageFormatException("Not an HEIC image."); } - while (stream.EofHitCount == 0) + while (stream.Position < stream.Length) { long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) @@ -97,7 +98,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat { this.CheckFileTypeBox(stream); - while (stream.EofHitCount == 0) + while (stream.Position < stream.Length) { long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) @@ -170,13 +171,14 @@ private static int ParseBoxHeader(Span buffer, out long length, out Heic4C private void ParseMetadata(BufferedReadStream stream, long boxLength) { long endPosition = stream.Position + boxLength; + stream.Skip(4); while (stream.Position < endPosition) { long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); switch (boxType) { case Heic4CharCode.iprp: - this.ParseItemPropertyContainer(stream, length); + this.ParseItemProperties(stream, length); break; case Heic4CharCode.iinf: this.ParseItemInfo(stream, length); @@ -273,9 +275,9 @@ private int ParseItemInfoEntry(Span buffer) // Skip Protection Index, not sure what that means... bytesRead += 2; - uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + Heic4CharCode itemType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; - item = new HeicItem((Heic4CharCode)itemId, itemType); + item = new HeicItem(itemType, itemId); item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; if (item.Type == Heic4CharCode.mime) @@ -297,6 +299,11 @@ private int ParseItemInfoEntry(Span buffer) } } + if (item != null) + { + this.items.Add(item); + } + return bytesRead; } @@ -308,7 +315,7 @@ private void ParseItemReference(BufferedReadStream stream, long boxLength) bytesRead += 4; while (bytesRead < boxLength) { - ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); + bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); HeicItemLink link = new(linkType, sourceId); @@ -329,101 +336,122 @@ private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) // BoxLength should be 6 or 8. Span buffer = this.ReadIntoBuffer(stream, boxLength); byte version = buffer[0]; - int bytesRead = 0; + int bytesRead = 4; this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); } - private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) + private void ParseItemProperties(BufferedReadStream stream, long boxLength) { // Cannot use Dictionary here, Properties can have multiple instances with the same key. List> properties = new(); - long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType); - if (containerType == Heic4CharCode.ipco) + long endBoxPosition = stream.Position + boxLength; + while (stream.Position < endBoxPosition) { - // Parse Item Property Container, which is just an array of preperty boxes. - long endPosition = stream.Position + containerLength; - while (stream.Position < endPosition) + long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType); + if (containerType == Heic4CharCode.ipco) { - int length = (int)this.ReadBoxHeader(stream, out Heic4CharCode boxType); - Span buffer = this.ReadIntoBuffer(stream, boxLength); - switch (boxType) - { - case Heic4CharCode.ispe: - // Skip over version (8 bits) and flags (24 bits). - int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); - properties.Add(new KeyValuePair(Heic4CharCode.ispe, new Size(width, height))); - break; - case Heic4CharCode.pasp: - int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); - int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - properties.Add(new KeyValuePair(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); - break; - case Heic4CharCode.pixi: - // Skip over version (8 bits) and flags (24 bits). - int channelCount = buffer[4]; - int offset = 5; - int bitsPerPixel = 0; - for (int i = 0; i < channelCount; i++) - { - bitsPerPixel += buffer[offset + i]; - } - - properties.Add(new KeyValuePair(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); - - break; - case Heic4CharCode.altt: - case Heic4CharCode.imir: - case Heic4CharCode.irot: - case Heic4CharCode.iscl: - case Heic4CharCode.rloc: - case Heic4CharCode.udes: - // TODO: Implement - SkipBox(stream, length); - break; - default: - throw new ImageFormatException($"Unknown item property box type of '{Enum.GetName(boxType)}'"); - } + // Parse Item Property Container, which is just an array of property boxes. + this.ParsePropertyContainer(stream, containerLength, properties); + } + else if (containerType == Heic4CharCode.ipma) + { + // Parse Item Property Association + this.ParsePropertyAssociation(stream, containerLength, properties); + } + else + { + string enumName = Enum.GetName(containerType) ?? ((uint)containerType).ToString("X", CultureInfo.InvariantCulture); + throw new ImageFormatException($"Unknown container type in property box of '{enumName}'"); } } - else if (containerType == Heic4CharCode.ipma) + } + + private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, List> properties) + { + long endPosition = stream.Position + boxLength; + while (stream.Position < endPosition) { - // Parse Item Property Association - Span buffer = this.ReadIntoBuffer(stream, boxLength); - byte version = buffer[0]; - byte flags = buffer[3]; - int bytesRead = 4; - int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); - - int associationCount = buffer[bytesRead++]; - for (int i = 0; i < associationCount; i++) + int itemLength = (int)this.ReadBoxHeader(stream, out Heic4CharCode itemType); + Span buffer = this.ReadIntoBuffer(stream, itemLength); + switch (itemType) { - uint propId; - if (flags == 1) - { - propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; - bytesRead += 2; - } - else - { - propId = buffer[bytesRead++] & 0x4FU; - } + case Heic4CharCode.ispe: + // Skip over version (8 bits) and flags (24 bits). + int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + properties.Add(new KeyValuePair(Heic4CharCode.ispe, new Size(width, height))); + break; + case Heic4CharCode.pasp: + int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); + int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + properties.Add(new KeyValuePair(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); + break; + case Heic4CharCode.pixi: + // Skip over version (8 bits) and flags (24 bits). + int channelCount = buffer[4]; + int offset = 5; + int bitsPerPixel = 0; + for (int i = 0; i < channelCount; i++) + { + bitsPerPixel += buffer[offset + i]; + } + + properties.Add(new KeyValuePair(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); - KeyValuePair prop = properties[(int)propId]; - switch (prop.Key) - { - case Heic4CharCode.ispe: - this.items[itemId].SetExtent((Size)prop.Value); - break; - case Heic4CharCode.pasp: - this.items[itemId].PixelAspectRatio = (Size)prop.Value; - break; - case Heic4CharCode.pixi: - int[] values = (int[])prop.Value; - this.items[itemId].ChannelCount = values[0]; - this.items[itemId].BitsPerPixel = values[1]; - break; - } + break; + case Heic4CharCode.altt: + case Heic4CharCode.colr: + case Heic4CharCode.imir: + case Heic4CharCode.irot: + case Heic4CharCode.iscl: + case Heic4CharCode.hvcC: + case Heic4CharCode.rloc: + case Heic4CharCode.udes: + // TODO: Implement + break; + default: + string enumName = Enum.GetName(itemType) ?? ((uint)itemType).ToString("X", CultureInfo.InvariantCulture); + throw new ImageFormatException($"Unknown item type in property box of '{enumName}'"); + } + } + } + + private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) + { + Span buffer = this.ReadIntoBuffer(stream, boxLength); + byte version = buffer[0]; + byte flags = buffer[3]; + int bytesRead = 4; + int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); + + int associationCount = buffer[bytesRead++]; + for (int i = 0; i < associationCount; i++) + { + uint propId; + if (flags == 1) + { + propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; + bytesRead += 2; + } + else + { + propId = buffer[bytesRead++] & 0x4FU; + } + + KeyValuePair prop = properties[(int)propId]; + switch (prop.Key) + { + case Heic4CharCode.ispe: + this.items[itemId].SetExtent((Size)prop.Value); + break; + case Heic4CharCode.pasp: + this.items[itemId].PixelAspectRatio = (Size)prop.Value; + break; + case Heic4CharCode.pixi: + int[] values = (int[])prop.Value; + this.items[itemId].ChannelCount = values[0]; + this.items[itemId].BitsPerPixel = values[1]; + break; } } } @@ -432,7 +460,8 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) { Span buffer = this.ReadIntoBuffer(stream, boxLength); int bytesRead = 0; - byte version = buffer[bytesRead++]; + byte version = buffer[bytesRead]; + bytesRead += 4; byte b1 = buffer[bytesRead++]; byte b2 = buffer[bytesRead++]; int offsetSize = (b1 >> 4) & 0x0f; @@ -444,13 +473,13 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) indexSize = b2 & 0x0f; } - uint itemCount = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); + uint itemCount = ReadUInt16Or32(buffer, version == 2, ref bytesRead); for (uint i = 0; i < itemCount; i++) { - uint itemId = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); + uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); if (version is 1 or 2) { - bytesRead += 2; + bytesRead++; byte b3 = buffer[bytesRead++]; int constructionMethod = b3 & 0x0f; } From 193fd94af1ce6480342388c32089a3ec832584fc Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 16:08:28 +0100 Subject: [PATCH 012/190] Boundary checking before box allocation --- .../Formats/Heic/HeicDecoderCore.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index a14fa2588a..b17bd1163c 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -65,14 +65,15 @@ public Image Decode(BufferedReadStream stream, CancellationToken while (stream.Position < stream.Length) { - long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + EnsureBoxBoundary(boxLength, stream); switch (boxType) { case Heic4CharCode.meta: - this.ParseMetadata(stream, length); + this.ParseMetadata(stream, boxLength); break; case Heic4CharCode.mdat: - this.ParseMediaData(stream, length); + this.ParseMediaData(stream, boxLength); break; default: throw new ImageFormatException($"Unknown box type of '{Enum.GetName(boxType)}'"); @@ -100,15 +101,16 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat while (stream.Position < stream.Length) { - long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + EnsureBoxBoundary(boxLength, stream); switch (boxType) { case Heic4CharCode.meta: - this.ParseMetadata(stream, length); + this.ParseMetadata(stream, boxLength); break; default: // Silently skip all other box types. - SkipBox(stream, length); + SkipBox(stream, boxLength); break; } } @@ -175,6 +177,7 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) while (stream.Position < endPosition) { long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + EnsureBoxBoundary(length, boxLength); switch (boxType) { case Heic4CharCode.iprp: @@ -348,6 +351,7 @@ private void ParseItemProperties(BufferedReadStream stream, long boxLength) while (stream.Position < endBoxPosition) { long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType); + EnsureBoxBoundary(containerLength, boxLength); if (containerType == Heic4CharCode.ipco) { // Parse Item Property Container, which is just an array of property boxes. @@ -372,6 +376,7 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L while (stream.Position < endPosition) { int itemLength = (int)this.ReadBoxHeader(stream, out Heic4CharCode itemType); + EnsureBoxBoundary(itemLength, boxLength); Span buffer = this.ReadIntoBuffer(stream, itemLength); switch (itemType) { @@ -568,6 +573,17 @@ private Span ReadIntoBuffer(BufferedReadStream stream, long length) } } + private static void EnsureBoxBoundary(long boxLength, Stream stream) + => EnsureBoxBoundary(boxLength, stream.Length - stream.Position); + + private static void EnsureBoxBoundary(long boxLength, long parentLength) + { + if (boxLength > parentLength) + { + throw new ImageFormatException("Box size beyond boundary"); + } + } + private HeicItem? FindItemById(uint itemId) => this.items.FirstOrDefault(item => item.Id == itemId); From 8366d72af9579f91bc37368b4769755283a0fbe9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 18:23:54 +0100 Subject: [PATCH 013/190] Add HIF test image --- .gitattributes | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Heic/IMG-20230508-0053.hif | 3 +++ 3 files changed, 5 insertions(+) create mode 100755 tests/Images/Input/Heic/IMG-20230508-0053.hif diff --git a/.gitattributes b/.gitattributes index b1cf4dbd0a..42194daa60 100644 --- a/.gitattributes +++ b/.gitattributes @@ -138,4 +138,5 @@ *.ani filter=lfs diff=lfs merge=lfs -text *.heic filter=lfs diff=lfs merge=lfs -text *.heif filter=lfs diff=lfs merge=lfs -text +*.hif filter=lfs diff=lfs merge=lfs -text *.avif filter=lfs diff=lfs merge=lfs -text diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a690f3f310..a460f007cd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1112,5 +1112,6 @@ public static class Heic public const string Image3 = "Heic/image3.heic"; public const string Image4 = "Heic/image4.heic"; public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; + public const string FujiFilm-HIF = "Heic/IMG-20230508-0053.hif"; } } diff --git a/tests/Images/Input/Heic/IMG-20230508-0053.hif b/tests/Images/Input/Heic/IMG-20230508-0053.hif new file mode 100755 index 0000000000..0d0c657275 --- /dev/null +++ b/tests/Images/Input/Heic/IMG-20230508-0053.hif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff1be6d42e8dbfc4d072bb73f4450d2afabad176b7aece108b51f8429f400edc +size 12367360 From 5f9db61a5067e529a838e97c4bb038db3221e649 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 18:22:36 +0100 Subject: [PATCH 014/190] Add handler parsing --- src/ImageSharp/Formats/Heic/Heic4CharCode.cs | 5 ++++ src/ImageSharp/Formats/Heic/Heic4CharCode.tt | 1 + .../Formats/Heic/HeicDecoderCore.cs | 25 +++++++++++++------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs index 2dddbbfd1a..1e6d9a2bb2 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -198,4 +198,9 @@ public enum Heic4CharCode : uint /// uri = 0x75726920U, + /// + /// Picture handler type. + /// + pict = 0x70696374U, + } diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt index d6b0870e24..8d716c78fa 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt @@ -44,6 +44,7 @@ "cdsc", "Content Description", "mime", "MIME type", "uri ", "URI", + "pict", "Picture handler type", }; #> diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index b17bd1163c..526cdf19a8 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -192,21 +192,17 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) case Heic4CharCode.pitm: this.ParsePrimaryItem(stream, length); break; - case Heic4CharCode.dinf: - SkipBox(stream, length); - break; case Heic4CharCode.hdlr: - SkipBox(stream, length); - break; - case Heic4CharCode.idat: - SkipBox(stream, length); + this.ParseHandler(stream, length); break; case Heic4CharCode.iloc: this.ParseItemLocation(stream, length); break; + case Heic4CharCode.dinf: + case Heic4CharCode.idat: case Heic4CharCode.grpl: case Heic4CharCode.ipro: - // TODO: Implement + // Silently skip these boxes. SkipBox(stream, length); break; default: @@ -215,6 +211,19 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) } } + private void ParseHandler(BufferedReadStream stream, long boxLength) + { + Span buffer = this.ReadIntoBuffer(stream, boxLength); + + // Only read the handler type, to check if this is not a movie file. + int bytesRead = 8; + Heic4CharCode handlerType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + if (handlerType != Heic4CharCode.pict) + { + throw new ImageFormatException("Not a picture file."); + } + } + private void ParseItemInfo(BufferedReadStream stream, long boxLength) { Span buffer = this.ReadIntoBuffer(stream, boxLength); From d6dc2b1473ce1c3f0359135fb015b913d9bd53ee Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 18:48:06 +0100 Subject: [PATCH 015/190] Parse HIF metadata --- src/ImageSharp/Formats/Heic/Heic4CharCode.cs | 22 ++++++++++++++++++- src/ImageSharp/Formats/Heic/Heic4CharCode.tt | 6 ++++- .../Formats/Heic/HeicDecoderCore.cs | 22 ++++++++++++++----- .../Formats/Heic/HeicImageFormatDetector.cs | 8 ++++--- .../Formats/Heic/HeicDecoderTests.cs | 4 ++-- tests/ImageSharp.Tests/TestImages.cs | 2 +- 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs index 1e6d9a2bb2..f80db91b83 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -124,15 +124,30 @@ public enum Heic4CharCode : uint ipma = 0x69706D61U, /// - /// High Efficient Image Coding. + /// High Efficient Image Coding brand. /// heic = 0x68656963U, + /// + /// High Efficient Image Coding brand (legacy name). + /// + heix = 0x68656978U, + + /// + /// High Efficient File brand. + /// + hif1 = 0x68696631U, + /// /// High Efficiency Coding tile. /// hvc1 = 0x68766331U, + /// + /// Legacy JPEG coded tile. + /// + jpeg = 0x6A706567U, + /// /// Data Information. /// @@ -203,4 +218,9 @@ public enum Heic4CharCode : uint /// pict = 0x70696374U, + /// + /// Unique Identifier. + /// + uuid = 0x75756964U, + } diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt index 8d716c78fa..7744763771 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt @@ -29,8 +29,11 @@ "udes", "User Description", "ipco", "Item Property Container", "ipma", "Item Property Association", - "heic", "High Efficient Image Coding", + "heic", "High Efficient Image Coding brand", + "heix", "High Efficient Image Coding brand (legacy name)", + "hif1", "High Efficient File brand", "hvc1", "High Efficiency Coding tile", + "jpeg", "Legacy JPEG coded tile", "dinf", "Data Information", "grpl", "Group list", "hdlr", "Handler", @@ -45,6 +48,7 @@ "mime", "MIME type", "uri ", "URI", "pict", "Picture handler type", + "uuid", "Unique Identifier", }; #> diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 526cdf19a8..232efce594 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -202,11 +202,12 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) case Heic4CharCode.idat: case Heic4CharCode.grpl: case Heic4CharCode.ipro: + case Heic4CharCode.uuid: // Silently skip these boxes. SkipBox(stream, length); break; default: - throw new ImageFormatException($"Unknown metadata box type of '{Enum.GetName(boxType)}'"); + throw new ImageFormatException($"Unknown metadata box type of '{PrettyPrint(boxType)}'"); } } } @@ -373,8 +374,7 @@ private void ParseItemProperties(BufferedReadStream stream, long boxLength) } else { - string enumName = Enum.GetName(containerType) ?? ((uint)containerType).ToString("X", CultureInfo.InvariantCulture); - throw new ImageFormatException($"Unknown container type in property box of '{enumName}'"); + throw new ImageFormatException($"Unknown container type in property box of '{PrettyPrint(containerType)}'"); } } } @@ -424,8 +424,7 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L // TODO: Implement break; default: - string enumName = Enum.GetName(itemType) ?? ((uint)itemType).ToString("X", CultureInfo.InvariantCulture); - throw new ImageFormatException($"Unknown item type in property box of '{enumName}'"); + throw new ImageFormatException($"Unknown item type in property box of '{PrettyPrint(itemType)}'"); } } } @@ -601,4 +600,17 @@ private static string ReadNullTerminatedString(Span span) Span bytes = span[..span.IndexOf((byte)0)]; return Encoding.UTF8.GetString(bytes); } + + private static string PrettyPrint(Heic4CharCode code) + { + string? pretty = Enum.GetName(code); + if (string.IsNullOrEmpty(pretty)) + { + Span bytes = stackalloc byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(bytes, (uint)code); + pretty = Encoding.ASCII.GetString(bytes); + } + + return pretty; + } } diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index 4a8195e2bd..e100069cb4 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -21,7 +21,9 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I return format != null; } - private static bool IsSupportedFileFormat(ReadOnlySpan header) => - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp && - BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == (uint)Heic4CharCode.heic; + private static bool IsSupportedFileFormat(ReadOnlySpan header) { + bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp; + uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); + return hasFtyp && (brand == (uint)Heic4CharCode.heic || brand == (uint)Heic4CharCode.heix); + } } diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs index 108eba286b..8f50b2298f 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs @@ -13,6 +13,7 @@ public class HeicDecoderTests [Theory] [InlineData(TestImages.Heic.Image1)] [InlineData(TestImages.Heic.Sample640x427)] + [InlineData(TestImages.Heic.FujiFilmHif)] public void Identify(string imagePath) { TestFile testFile = TestFile.Create(imagePath); @@ -27,8 +28,7 @@ public void Identify(string imagePath) } [Theory] - [WithFile(TestImages.Heic.Image1, PixelTypes.Rgba32)] - [WithFile(TestImages.Heic.Sample640x427, PixelTypes.Rgba32)] + [WithFile(TestImages.Heic.FujiFilmHif, PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index a460f007cd..d30974fa1d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1112,6 +1112,6 @@ public static class Heic public const string Image3 = "Heic/image3.heic"; public const string Image4 = "Heic/image4.heic"; public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; - public const string FujiFilm-HIF = "Heic/IMG-20230508-0053.hif"; + public const string FujiFilmHif = "Heic/IMG-20230508-0053.hif"; } } From f90d4ebe957fe05464c89db7d8c8d7806165b2f7 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 19:14:29 +0100 Subject: [PATCH 016/190] More HIF support --- src/ImageSharp/Formats/Heic/Heic4CharCode.cs | 5 +++++ src/ImageSharp/Formats/Heic/Heic4CharCode.tt | 1 + src/ImageSharp/Formats/Heic/HeicConstants.cs | 2 +- .../Formats/Heic/HeicDecoderCore.cs | 17 +++++++++++++--- src/ImageSharp/Formats/Heic/HeicItem.cs | 6 ++++++ src/ImageSharp/Formats/Heic/HeicLocation.cs | 20 +++++++++++++++++++ 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 src/ImageSharp/Formats/Heic/HeicLocation.cs diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs index f80db91b83..42867059dd 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -223,4 +223,9 @@ public enum Heic4CharCode : uint /// uuid = 0x75756964U, + /// + /// Free space. + /// + free = 0x66726565U, + } diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt index 7744763771..1bcbc8312a 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt @@ -49,6 +49,7 @@ "uri ", "URI", "pict", "Picture handler type", "uuid", "Unique Identifier", + "free", "Free space", }; #> diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heic/HeicConstants.cs index 809818e403..03a394a9fb 100644 --- a/src/ImageSharp/Formats/Heic/HeicConstants.cs +++ b/src/ImageSharp/Formats/Heic/HeicConstants.cs @@ -18,5 +18,5 @@ internal static class HeicConstants /// /// The list of file extensions that equate to a HEIC. /// - public static readonly IEnumerable FileExtensions = new[] { "heic", "heif", "avif" }; + public static readonly IEnumerable FileExtensions = new[] { "heic", "heif", "hif", "avif" }; } diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 232efce594..23679740e6 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -75,8 +75,15 @@ public Image Decode(BufferedReadStream stream, CancellationToken case Heic4CharCode.mdat: this.ParseMediaData(stream, boxLength); break; + case Heic4CharCode.free: + SkipBox(stream, boxLength); + break; + case 0U: + // Some files have trailing zeros, skiping to EOF. + stream.Skip((int)(stream.Length - stream.Position)); + break; default: - throw new ImageFormatException($"Unknown box type of '{Enum.GetName(boxType)}'"); + throw new ImageFormatException($"Unknown box type of '{PrettyPrint(boxType)}'"); } } @@ -129,9 +136,10 @@ private bool CheckFileTypeBox(BufferedReadStream stream) long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); Span buffer = this.ReadIntoBuffer(stream, boxLength); uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + bool correctBrand = majorBrand == (uint)Heic4CharCode.heic || majorBrand == (uint)Heic4CharCode.heix; // TODO: Interpret minorVersion and compatible brands. - return boxType == Heic4CharCode.ftyp && majorBrand == (uint)Heic4CharCode.heic; + return boxType == Heic4CharCode.ftyp && correctBrand; } private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) @@ -490,6 +498,7 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) for (uint i = 0; i < itemCount; i++) { uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); + HeicItem? item = this.FindItemById(itemId); if (version is 1 or 2) { bytesRead++; @@ -506,11 +515,13 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) { if (version is 1 or 2 && indexSize > 0) { - ulong extentIndex = ReadUIntVariable(buffer, indexSize, ref bytesRead); + _ = ReadUIntVariable(buffer, indexSize, ref bytesRead); } ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); + HeicLocation loc = new HeicLocation((long)extentOffset, (long)extentLength); + item?.DataLocations.Add(loc); } } } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index 481ed04860..beccfada38 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. + namespace SixLabors.ImageSharp.Formats.Heic; /// @@ -68,6 +69,11 @@ public class HeicItem(Heic4CharCode type, uint id) /// public Size GridCellExtent { get; private set; } + /// + /// Gets the list of data locations for this item. + /// + public List DataLocations { get; } = new List(); + /// /// Set the image extent. /// diff --git a/src/ImageSharp/Formats/Heic/HeicLocation.cs b/src/ImageSharp/Formats/Heic/HeicLocation.cs new file mode 100644 index 0000000000..7a342656e3 --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicLocation.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Location within the file of an . +/// +public class HeicLocation(long offset, long length) +{ + /// + /// Gets the file offset of this location. + /// + public long Offset { get; } = offset; + + /// + /// Gets the length of this location. + /// + public long Length { get; } = length; +} From f234c8b0bc6ec9d4113cadd01570476d4334b5a8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 27 Dec 2023 19:55:43 +0100 Subject: [PATCH 017/190] Decoding of thumbnail with JPEG --- .../Formats/Heic/HeicDecoderCore.cs | 37 +++++++++++++++---- .../Formats/Heic/HeicImageFormatDetector.cs | 3 +- src/ImageSharp/Formats/Heic/HeicItem.cs | 1 - 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 23679740e6..0d3a31f0d3 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; using System.Buffers.Binary; using System.Globalization; using System.Text; @@ -63,6 +64,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken throw new ImageFormatException("Not an HEIC image."); } + Image? image = null; while (stream.Position < stream.Length) { long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); @@ -73,7 +75,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken this.ParseMetadata(stream, boxLength); break; case Heic4CharCode.mdat: - this.ParseMediaData(stream, boxLength); + image = this.ParseMediaData(stream, boxLength); break; case Heic4CharCode.free: SkipBox(stream, boxLength); @@ -93,10 +95,10 @@ public Image Decode(BufferedReadStream stream, CancellationToken throw new ImageFormatException("No primary item found"); } - var image = new Image(this.configuration, item.Extent.Width, item.Extent.Height, this.metadata); - - // TODO: Parse pixel data - Buffer2D pixels = image.GetRootFramePixelBuffer(); + if (image == null) + { + throw new NotImplementedException("No JPEG image decoded"); + } return image; } @@ -569,9 +571,30 @@ private static ulong ReadUIntVariable(Span buffer, int numBytes, ref int b return result; } - private void ParseMediaData(BufferedReadStream stream, long boxLength) + private Image ParseMediaData(BufferedReadStream stream, long boxLength) + where TPixel : unmanaged, IPixel { - SkipBox(stream, boxLength); + // FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. + HeicItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heic4CharCode.thmb); + if (thumbLink == null) + { + throw new NotImplementedException("No thumbnail found"); + } + + HeicItem? thumbItem = this.FindItemById(thumbLink.SourceId); + if (thumbItem == null || thumbItem.Type != Heic4CharCode.jpeg) + { + throw new NotImplementedException("No HVC decoding implemented yet"); + } + + int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; + int thumbFileLength = (int)thumbItem.DataLocations[0].Length; + stream.Skip((int)(thumbFileOffset - stream.Position)); + using IMemoryOwner thumbMemory = this.configuration.MemoryAllocator.Allocate(thumbFileLength); + Span thumbSpan = thumbMemory.GetSpan(); + stream.Read(thumbSpan); + + return Image.Load(thumbSpan); } private static void SkipBox(BufferedReadStream stream, long boxLength) diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index e100069cb4..a48f272b5f 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -21,7 +21,8 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I return format != null; } - private static bool IsSupportedFileFormat(ReadOnlySpan header) { + private static bool IsSupportedFileFormat(ReadOnlySpan header) + { bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp; uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); return hasFtyp && (brand == (uint)Heic4CharCode.heic || brand == (uint)Heic4CharCode.heix); diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heic/HeicItem.cs index beccfada38..4127aebb01 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heic/HeicItem.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. - namespace SixLabors.ImageSharp.Formats.Heic; /// From e972551bb5970783398768fe80a55221e34cc8cb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 28 Dec 2023 11:06:51 +0100 Subject: [PATCH 018/190] Exposed used compression method in metadata --- .../Formats/Heic/HeicCompressionMethod.cs | 45 +++++++++++++++++++ .../Formats/Heic/HeicDecoderCore.cs | 12 ++++- src/ImageSharp/Formats/Heic/HeicMetadata.cs | 8 +++- .../Formats/Heic/HeicDecoderTests.cs | 16 +++---- tests/ImageSharp.Tests/TestImages.cs | 1 + 5 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs diff --git a/src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs b/src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs new file mode 100644 index 0000000000..efe826f85c --- /dev/null +++ b/src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heic; + +/// +/// Compression algorithms possible inside an HEIF (High Efficiency File Format) based file. +/// +public enum HeicCompressionMethod +{ + /// + /// High Efficiency Video Coding + /// + Hevc, + + /// + /// Legact JPEG + /// + LegacyJpeg, + + /// + /// JPEG 2000 + /// + Jpeg2000, + + /// + /// JPEG-XR + /// + JpegXR, + + /// + /// JPEG-XS + /// + JpegXS, + + /// + /// AOMedia's Video 1 coding + /// + Av1, + + /// + /// Advanced Video Coding + /// + Avc, +} diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 0d3a31f0d3..ed2381cc62 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -25,7 +25,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals /// /// The decoded by this decoder instance. /// - private ImageMetadata? metadata; + private readonly ImageMetadata metadata; private uint primaryItem; @@ -130,6 +130,12 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat throw new ImageFormatException("No primary item found"); } + HeicMetadata meta = this.metadata.GetHeicMetadata(); + if (this.itemLinks.Any(link => link.Type == Heic4CharCode.thmb)) + { + meta.CompressionMethod = HeicCompressionMethod.LegacyJpeg; + } + return new ImageInfo(new PixelTypeInfo(item.BitsPerPixel), new(item.Extent.Width, item.Extent.Height), this.metadata); } @@ -432,6 +438,7 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L case Heic4CharCode.rloc: case Heic4CharCode.udes: // TODO: Implement + properties.Add(new KeyValuePair(itemType, new object())); break; default: throw new ImageFormatException($"Unknown item type in property box of '{PrettyPrint(itemType)}'"); @@ -594,6 +601,9 @@ private Image ParseMediaData(BufferedReadStream stream, long box Span thumbSpan = thumbMemory.GetSpan(); stream.Read(thumbSpan); + HeicMetadata meta = this.metadata.GetHeicMetadata(); + meta.CompressionMethod = HeicCompressionMethod.LegacyJpeg; + return Image.Load(thumbSpan); } diff --git a/src/ImageSharp/Formats/Heic/HeicMetadata.cs b/src/ImageSharp/Formats/Heic/HeicMetadata.cs index 55f3d7ba82..f6f95a86cd 100644 --- a/src/ImageSharp/Formats/Heic/HeicMetadata.cs +++ b/src/ImageSharp/Formats/Heic/HeicMetadata.cs @@ -20,8 +20,12 @@ public HeicMetadata() /// /// The metadata to create an instance from. private HeicMetadata(HeicMetadata other) - { - } + => this.CompressionMethod = other.CompressionMethod; + + /// + /// Gets or sets the compression method used for the primary frame. + /// + public HeicCompressionMethod CompressionMethod { get; set; } /// public IDeepCloneable DeepClone() => new HeicMetadata(this); diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs index 8f50b2298f..b3862c3d06 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs @@ -11,10 +11,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heic; public class HeicDecoderTests { [Theory] - [InlineData(TestImages.Heic.Image1)] - [InlineData(TestImages.Heic.Sample640x427)] - [InlineData(TestImages.Heic.FujiFilmHif)] - public void Identify(string imagePath) + [InlineData(TestImages.Heic.Image1, HeicCompressionMethod.Hevc)] + [InlineData(TestImages.Heic.Sample640x427, HeicCompressionMethod.Hevc)] + [InlineData(TestImages.Heic.FujiFilmHif, HeicCompressionMethod.LegacyJpeg)] + public void Identify(string imagePath, HeicCompressionMethod compressionMethod) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); @@ -23,8 +23,8 @@ public void Identify(string imagePath) HeicMetadata heicMetadata = imageInfo.Metadata.GetHeicMetadata(); Assert.NotNull(imageInfo); - Assert.Equal(imageInfo.Metadata.DecodedImageFormat, HeicFormat.Instance); - //Assert.Equal(heicMetadata.Channels, channels); + Assert.Equal(HeicFormat.Instance, imageInfo.Metadata.DecodedImageFormat); + Assert.Equal(compressionMethod, heicMetadata.CompressionMethod); } [Theory] @@ -33,10 +33,10 @@ public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - HeicMetadata qoiMetadata = image.Metadata.GetHeicMetadata(); + HeicMetadata heicMetadata = image.Metadata.GetHeicMetadata(); image.DebugSave(provider); image.CompareToReferenceOutput(provider); - //Assert.Equal(heicMetadata.Channels, channels); + Assert.Equal(HeicCompressionMethod.LegacyJpeg, heicMetadata.CompressionMethod); } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d30974fa1d..ef80704b08 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1112,6 +1112,7 @@ public static class Heic public const string Image3 = "Heic/image3.heic"; public const string Image4 = "Heic/image4.heic"; public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; + // Downloaded from: https://github.com/draktable-org/darktable/issues/14473 public const string FujiFilmHif = "Heic/IMG-20230508-0053.hif"; } } From 183b873fa2141e69b3109651086fd85963ae6198 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 28 Dec 2023 15:42:07 +0100 Subject: [PATCH 019/190] Reference image for Heic Decoder Test --- tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs | 2 +- .../HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs index b3862c3d06..b85c928f87 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs @@ -37,6 +37,6 @@ public void Decode(TestImageProvider provider) image.DebugSave(provider); image.CompareToReferenceOutput(provider); - Assert.Equal(HeicCompressionMethod.LegacyJpeg, heicMetadata.CompressionMethod); + Assert.Equal(HeicCompressionMethod.Hevc, heicMetadata.CompressionMethod); } } diff --git a/tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png b/tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png new file mode 100644 index 0000000000..1dbb286d20 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b21c55369e0cf2a15398a6538335c3caab59da83321d095c74913de9d25532d2 +size 3736336 From 0bfa0fed2bdbb63974d92fa00665e97630029e4f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 28 Dec 2023 22:51:28 +0100 Subject: [PATCH 020/190] Initial encoder --- .../Formats/Heic/AutoExpandingMemory.cs | 55 ++++ src/ImageSharp/Formats/Heic/Heic4CharCode.cs | 7 +- src/ImageSharp/Formats/Heic/Heic4CharCode.tt | 3 +- .../Formats/Heic/HeicDecoderCore.cs | 1 + .../Formats/Heic/HeicEncoderCore.cs | 285 +++++++++++++++++- 5 files changed, 345 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs diff --git a/src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs b/src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs new file mode 100644 index 0000000000..376654a205 --- /dev/null +++ b/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; + +/// +/// Memory class that will expand dynamically when full. +/// +internal sealed class AutoExpandingMemory : IDisposable + where T : unmanaged +{ + private readonly Configuration configuration; + private IMemoryOwner allocation; + + public AutoExpandingMemory(Configuration configuration, int initialSize) + { + Guard.MustBeGreaterThan(initialSize, 0, nameof(initialSize)); + + this.configuration = configuration; + this.allocation = this.configuration.MemoryAllocator.Allocate(initialSize); + } + + public Span GetSpan(int requestedSize) + { + Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize)); + this.EnsureCapacity(requestedSize); + + return this.allocation.Memory.Span[..requestedSize]; + } + + public Span 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 newAllocation = this.configuration.MemoryAllocator.Allocate(newSize); + this.allocation.Memory.CopyTo(newAllocation.Memory); + this.allocation.Dispose(); + this.allocation = newAllocation; + } + } +} diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs index 42867059dd..5e184a26e0 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -113,6 +113,11 @@ public enum Heic4CharCode : uint /// udes = 0x75646573U, + /// + /// IPMP Control Box. + /// + ipmc = 0x69706D63U, + /// /// Item Property Container. /// @@ -136,7 +141,7 @@ public enum Heic4CharCode : uint /// /// High Efficient File brand. /// - hif1 = 0x68696631U, + mif1 = 0x6D696631U, /// /// High Efficiency Coding tile. diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt index 1bcbc8312a..7faca67008 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/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", diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index ed2381cc62..2a111ad0b3 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -219,6 +219,7 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) case Heic4CharCode.grpl: case Heic4CharCode.ipro: case Heic4CharCode.uuid: + case Heic4CharCode.ipmc: // Silently skip these boxes. SkipBox(stream, length); break; diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index 55f892a8ce..e028cde1a6 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/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 @@ public HeicEncoderCore(Configuration configuration, HeicEncoder encoder) /// The to encode from. /// The to encode the image data to. /// The token to request cancellation. - public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + public async void Encode(Image image, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - // TODO: Implement + byte[] pixels = await CompressPixels(image, cancellationToken); + List items = new(); + List 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(Image image, byte[] pixels, List items, List links) + where TPixel : unmanaged, IPixel + { + 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 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 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 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 items, List links, Stream stream) + { + using AutoExpandingMemory memory = new(this.configuration, 0x1000); + Span 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 memory, int memoryOffset) + { + Span 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 memory, int memoryOffset) + { + Span 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 memory, int memoryOffset, List items) + { + Span 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 memory, int memoryOffset, List links) + { + Span 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 memory, int memoryOffset, List items) + { + const ushort numPropPerItem = 1; + Span 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 memory, int memoryOffset, HeicItem item) + { + Span 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 memory, int memoryOffset) + { + Span buffer = memory.GetSpan(memoryOffset, 100); + int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.idat); + + BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); + return bytesWritten; + } + + private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) + { + Span 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 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 data, Stream stream) + { + Span 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 CompressPixels(Image image, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + using MemoryStream stream = new(); + JpegEncoder encoder = new() + { + ColorType = JpegEncodingColor.YCbCrRatio420 + }; + await image.SaveAsJpegAsync(stream, encoder, cancellationToken); + return stream.ToArray(); + } } From b60ce906e3b5678359a71c0a1ecfd82216d52de0 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 28 Dec 2023 22:59:31 +0100 Subject: [PATCH 021/190] Move to Memory namespace --- .../{Formats/Heic => Memory}/AutoExpandingMemory.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) rename src/ImageSharp/{Formats/Heic => Memory}/AutoExpandingMemory.cs (87%) diff --git a/src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs b/src/ImageSharp/Memory/AutoExpandingMemory.cs similarity index 87% rename from src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs rename to src/ImageSharp/Memory/AutoExpandingMemory.cs index 376654a205..7d118e05bc 100644 --- a/src/ImageSharp/Formats/Heic/AutoExpandingMemory.cs +++ b/src/ImageSharp/Memory/AutoExpandingMemory.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Memory; /// /// Memory class that will expand dynamically when full. @@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Heic; internal sealed class AutoExpandingMemory : IDisposable where T : unmanaged { + private const int IncreaseFactor = 5; private readonly Configuration configuration; private IMemoryOwner allocation; @@ -32,7 +33,7 @@ public Span GetSpan(int requestedSize) public Span GetSpan(int offset, int requestedSize) { - Guard.MustBeGreaterThan(offset, 0, nameof(offset)); + Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize)); this.EnsureCapacity(offset + requestedSize); @@ -45,7 +46,7 @@ private void EnsureCapacity(int requestedSize) { if (requestedSize > this.allocation.Memory.Length) { - int newSize = requestedSize + (requestedSize / 5); + int newSize = requestedSize + (requestedSize / IncreaseFactor); IMemoryOwner newAllocation = this.configuration.MemoryAllocator.Allocate(newSize); this.allocation.Memory.CopyTo(newAllocation.Memory); this.allocation.Dispose(); From 23388b264a9b13dcffb96131a988707792bf6946 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 28 Dec 2023 23:00:16 +0100 Subject: [PATCH 022/190] Box size predictions --- src/ImageSharp/Formats/Heic/HeicEncoderCore.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index e028cde1a6..11bf586a48 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -138,7 +138,7 @@ private void WriteMetadataBox(List items, List links, St private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryOffset) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 24); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.hdlr, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; @@ -155,7 +155,7 @@ private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryO private static int WritePrimaryItemBox(AutoExpandingMemory memory, int memoryOffset) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 12); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.pitm, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); bytesWritten += 2; @@ -166,7 +166,7 @@ private static int WritePrimaryItemBox(AutoExpandingMemory memory, int mem private static int WriteItemInfoBox(AutoExpandingMemory memory, int memoryOffset, List items) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 6 + (items.Count * 9)); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iinf, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count); bytesWritten += 2; @@ -191,7 +191,7 @@ private static int WriteItemInfoBox(AutoExpandingMemory memory, int memory private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List links) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 4 + (links.Count << 4)); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iref, 0, 0); foreach (HeicItemLink link in links) { @@ -255,7 +255,7 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memory, int memoryOffset, HeicItem item) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 16); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ispe); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width); bytesWritten += 4; @@ -268,7 +268,7 @@ private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memor private static int WriteItemDataBox(AutoExpandingMemory memory, int memoryOffset) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 10); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.idat); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); @@ -277,7 +277,7 @@ private static int WriteItemDataBox(AutoExpandingMemory memory, int memory private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) { - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 30 + (items.Count << 3)); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iloc, 1, 0); buffer[bytesWritten++] = 0x44; buffer[bytesWritten++] = 0; From 3f4e74b82a5e5ecb6fd24cf8eb72195598dc04ca Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 09:36:55 +0100 Subject: [PATCH 023/190] Fix Heic encoder test --- .../Formats/Heic/HeicEncoderCore.cs | 23 +++++++++++-------- .../Formats/Heic/HeicEncoderTests.cs | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index 11bf586a48..f1efa5a254 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -68,10 +68,12 @@ private static void GenerateItems(Image image, byte[] pixels, Li primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; primaryItem.SetExtent(image.Size); + items.Add(primaryItem); // Create a fake thumbnail, to make our own Decoder happy. HeicItemLink thumbnail = new(Heic4CharCode.thmb, 1u); thumbnail.DestinationIds.Add(1u); + links.Add(thumbnail); } private static int WriteBoxHeader(Span buffer, Heic4CharCode type) @@ -126,7 +128,7 @@ private void WriteMetadataBox(List items, List links, St bytesWritten += WriteHandlerBox(memory, bytesWritten); bytesWritten += WritePrimaryItemBox(memory, bytesWritten); bytesWritten += WriteItemInfoBox(memory, bytesWritten, items); - bytesWritten += WriteItemReferenceBox(memory, bytesWritten, links); + bytesWritten += WriteItemReferenceBox(memory, bytesWritten, items, links); bytesWritten += WriteItemPropertiesBox(memory, bytesWritten, items); bytesWritten += WriteItemDataBox(memory, bytesWritten); bytesWritten += WriteItemLocationBox(memory, bytesWritten, items); @@ -138,7 +140,7 @@ private void WriteMetadataBox(List items, List links, St private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryOffset) { - Span buffer = memory.GetSpan(memoryOffset, 24); + Span buffer = memory.GetSpan(memoryOffset, 33); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.hdlr, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; @@ -155,7 +157,7 @@ private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryO private static int WritePrimaryItemBox(AutoExpandingMemory memory, int memoryOffset) { - Span buffer = memory.GetSpan(memoryOffset, 12); + Span buffer = memory.GetSpan(memoryOffset, 14); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.pitm, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); bytesWritten += 2; @@ -166,7 +168,7 @@ private static int WritePrimaryItemBox(AutoExpandingMemory memory, int mem private static int WriteItemInfoBox(AutoExpandingMemory memory, int memoryOffset, List items) { - Span buffer = memory.GetSpan(memoryOffset, 6 + (items.Count * 9)); + Span buffer = memory.GetSpan(memoryOffset, 14 + (items.Count * 21)); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iinf, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count); bytesWritten += 2; @@ -189,9 +191,9 @@ private static int WriteItemInfoBox(AutoExpandingMemory memory, int memory return bytesWritten; } - private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List links) + private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List items, List links) { - Span buffer = memory.GetSpan(memoryOffset, 4 + (links.Count << 4)); + Span buffer = memory.GetSpan(memoryOffset, 12 + (links.Count * (12 + (items.Count * 2)))); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iref, 0, 0); foreach (HeicItemLink link in links) { @@ -217,7 +219,7 @@ private static int WriteItemReferenceBox(AutoExpandingMemory memory, int m private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int memoryOffset, List items) { const ushort numPropPerItem = 1; - Span buffer = memory.GetSpan(memoryOffset, 100); + Span buffer = memory.GetSpan(memoryOffset, 20); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iprp); // Write 'ipco' box @@ -225,10 +227,11 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.ipco); foreach (HeicItem item in items) { - bytesWritten += WriteSpatialExtentPropertyBox(memory, ipcoLengthOffset + bytesWritten, item); + bytesWritten += WriteSpatialExtentPropertyBox(memory, memoryOffset + bytesWritten, item); } BinaryPrimitives.WriteUInt32BigEndian(buffer[ipcoLengthOffset..], (uint)(bytesWritten - ipcoLengthOffset)); + buffer = memory.GetSpan(memoryOffset, bytesWritten + 16 + (5 * items.Count * numPropPerItem)); // Write 'ipma' box int ipmaLengthOffset = bytesWritten; @@ -255,8 +258,8 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memory, int memoryOffset, HeicItem item) { - Span buffer = memory.GetSpan(memoryOffset, 16); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ispe); + Span buffer = memory.GetSpan(memoryOffset, 20); + int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ispe, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height); diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs index 8bfbce973f..3b6087d0a5 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs @@ -23,7 +23,7 @@ public static void Encode(TestImageProvider provider) image.Save(stream, encoder); stream.Position = 0; - using Image encodedImage = (Image)Image.Load(stream); + using Image encodedImage = Image.Load(stream); HeicMetadata heicMetadata = encodedImage.Metadata.GetHeicMetadata(); ImageComparer.Exact.CompareImages(image, encodedImage); From 54db9c75ff371a9dad75d43226e3188aeaed4b34 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 09:57:17 +0100 Subject: [PATCH 024/190] Box size improvements --- src/ImageSharp/Formats/Heic/HeicEncoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs index f1efa5a254..ce2784da80 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs @@ -90,7 +90,7 @@ private static int WriteBoxHeader(Span buffer, Heic4CharCode type) private static int WriteBoxHeader(Span buffer, Heic4CharCode type, byte version, uint flags) { int bytesWritten = 0; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 12U); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 12); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)type); bytesWritten += 4; @@ -109,7 +109,7 @@ private void WriteFileTypeBox(Stream stream) int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ftyp); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.heic); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0U); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.mif1); bytesWritten += 4; @@ -280,7 +280,7 @@ private static int WriteItemDataBox(AutoExpandingMemory memory, int memory private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) { - Span buffer = memory.GetSpan(memoryOffset, 30 + (items.Count << 3)); + Span buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8)); int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iloc, 1, 0); buffer[bytesWritten++] = 0x44; buffer[bytesWritten++] = 0; From 6372a9a713e3d5ac6d61027ae08eec3267b0c6ea Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 09:59:36 +0100 Subject: [PATCH 025/190] Add AVIF test image --- tests/ImageSharp.Tests/TestImages.cs | 4 ++++ tests/Images/Input/Heic/Irvine_CA.avif | 3 +++ 2 files changed, 7 insertions(+) create mode 100644 tests/Images/Input/Heic/Irvine_CA.avif diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index ef80704b08..4b6b1463c0 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1112,7 +1112,11 @@ public static class Heic public const string Image3 = "Heic/image3.heic"; public const string Image4 = "Heic/image4.heic"; public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; + // Downloaded from: https://github.com/draktable-org/darktable/issues/14473 public const string FujiFilmHif = "Heic/IMG-20230508-0053.hif"; + + // Downloaded from: https://github.com/AOMediaCodec/av1-avif/blob/master/testFiles/Microsoft/Irvine_CA.avif + public const string IrvineAvif = "Heic/Irvine_CA.avif"; } } diff --git a/tests/Images/Input/Heic/Irvine_CA.avif b/tests/Images/Input/Heic/Irvine_CA.avif new file mode 100644 index 0000000000..81cd4c05ed --- /dev/null +++ b/tests/Images/Input/Heic/Irvine_CA.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee9b8544668ba71e584311be8eb590d0a92464aa24aa75ab05af92ab4c9ccf4c +size 28133 From 3fae594cec3b1b1949bfa80e5e1cd446992b61a9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 10:09:19 +0100 Subject: [PATCH 026/190] Identify AVIF files --- src/ImageSharp/Formats/Heic/Heic4CharCode.cs | 15 +++++++++++++++ src/ImageSharp/Formats/Heic/Heic4CharCode.tt | 3 +++ src/ImageSharp/Formats/Heic/HeicDecoderCore.cs | 12 ++++++++++-- .../Formats/Heic/HeicImageFormatDetector.cs | 2 +- .../Formats/Heic/HeicDecoderTests.cs | 1 + 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs index 5e184a26e0..a64c94b686 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.cs @@ -78,6 +78,11 @@ public enum Heic4CharCode : uint /// hvcC = 0x68766343U, + /// + /// AV1 configuration. + /// + av1C = 0x61763143U, + /// /// Image Mirror. /// @@ -143,6 +148,11 @@ public enum Heic4CharCode : uint /// mif1 = 0x6D696631U, + /// + /// AVIF brand. + /// + avif = 0x61766966U, + /// /// High Efficiency Coding tile. /// @@ -153,6 +163,11 @@ public enum Heic4CharCode : uint /// jpeg = 0x6A706567U, + /// + /// AOMedia Video Coding tile. + /// + av01 = 0x61763031U, + /// /// Data Information. /// diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt index 7faca67008..61ceb332ac 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/src/ImageSharp/Formats/Heic/Heic4CharCode.tt @@ -20,6 +20,7 @@ "altt", "Alternative text", "colr", "Colour information", "hvcC", "HVC configuration", + "av1C", "AV1 configuration", "imir", "Image Mirror", "irot", "Image Rotation", "iscl", "Image Scaling", @@ -33,8 +34,10 @@ "heic", "High Efficient Image Coding brand", "heix", "High Efficient Image Coding brand (legacy name)", "mif1", "High Efficient File brand", + "avif", "AVIF brand", "hvc1", "High Efficiency Coding tile", "jpeg", "Legacy JPEG coded tile", + "av01", "AOMedia Video Coding tile", "dinf", "Data Information", "grpl", "Group list", "hdlr", "Handler", diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs index 2a111ad0b3..ca6d2eb7a4 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs @@ -131,10 +131,17 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat } HeicMetadata meta = this.metadata.GetHeicMetadata(); - if (this.itemLinks.Any(link => link.Type == Heic4CharCode.thmb)) + HeicCompressionMethod compressionMethod = HeicCompressionMethod.Hevc; + if (item.Type == Heic4CharCode.av01) { - meta.CompressionMethod = HeicCompressionMethod.LegacyJpeg; + compressionMethod = HeicCompressionMethod.Av1; } + else if (this.itemLinks.Any(link => link.Type == Heic4CharCode.thmb)) + { + compressionMethod = HeicCompressionMethod.LegacyJpeg; + } + + meta.CompressionMethod = compressionMethod; return new ImageInfo(new PixelTypeInfo(item.BitsPerPixel), new(item.Extent.Width, item.Extent.Height), this.metadata); } @@ -436,6 +443,7 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L case Heic4CharCode.irot: case Heic4CharCode.iscl: case Heic4CharCode.hvcC: + case Heic4CharCode.av1C: case Heic4CharCode.rloc: case Heic4CharCode.udes: // TODO: Implement diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs index a48f272b5f..93aefca223 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs @@ -25,6 +25,6 @@ private static bool IsSupportedFileFormat(ReadOnlySpan header) { bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp; uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); - return hasFtyp && (brand == (uint)Heic4CharCode.heic || brand == (uint)Heic4CharCode.heix); + return hasFtyp && (brand == (uint)Heic4CharCode.heic || brand == (uint)Heic4CharCode.heix || brand == (uint)Heic4CharCode.avif); } } diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs index b85c928f87..e08d5b2208 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs @@ -14,6 +14,7 @@ public class HeicDecoderTests [InlineData(TestImages.Heic.Image1, HeicCompressionMethod.Hevc)] [InlineData(TestImages.Heic.Sample640x427, HeicCompressionMethod.Hevc)] [InlineData(TestImages.Heic.FujiFilmHif, HeicCompressionMethod.LegacyJpeg)] + [InlineData(TestImages.Heic.IrvineAvif, HeicCompressionMethod.Av1)] public void Identify(string imagePath, HeicCompressionMethod compressionMethod) { TestFile testFile = TestFile.Create(imagePath); From 046b28f8c6c45f0e1a526a6d41a2caa60afa1777 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 11:00:38 +0100 Subject: [PATCH 027/190] Tests for AutoExpandingMemory class --- .../Memory/AutoExpandingMemoryTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs diff --git a/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs b/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs new file mode 100644 index 0000000000..24ed904c0d --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs @@ -0,0 +1,54 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Memory; + +public class AutoExpandingMemoryTests +{ + private readonly Configuration configurtion = Configuration.Default; + + [Theory] + [InlineData(1000, 2000)] + [InlineData(1000, 1000)] + [InlineData(200, 1000)] + [InlineData(200, 200)] + [InlineData(200, 100)] + public void ExpandToRequestedCapacity(int initialCapacity, int requestedCapacity) + { + AutoExpandingMemory memory = new(this.configurtion, initialCapacity); + Span span = memory.GetSpan(requestedCapacity); + Assert.Equal(requestedCapacity, span.Length); + } + + [Theory] + [InlineData(1000, 2000)] + [InlineData(1000, 1000)] + [InlineData(200, 1000)] + [InlineData(200, 200)] + [InlineData(200, 100)] + public void KeepDataWhileExpanding(int initialCapacity, int requestedCapacity) + { + AutoExpandingMemory memory = new(this.configurtion, initialCapacity); + Span firstSpan = memory.GetSpan(initialCapacity); + firstSpan[1] = 1; + firstSpan[2] = 2; + firstSpan[3] = 3; + Span expandedSpan = memory.GetSpan(requestedCapacity); + Assert.Equal(3, firstSpan[3]); + Assert.Equal(firstSpan[3], expandedSpan[3]); + } + + [Theory] + [InlineData(1, -1)] + [InlineData(1, 0)] + [InlineData(-2, 1)] + [InlineData(-2, 0)] + public void Guards(int initialCapacity, int requestedCapacity) => + Assert.Throws(() => + { + AutoExpandingMemory memory = new(this.configurtion, initialCapacity); + _ = memory.GetSpan(requestedCapacity); + }); +} From 9d99b1a515626bc1b9f26aa6255a89469f4568d3 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 13:35:24 +0100 Subject: [PATCH 028/190] Move to Heif directory --- tests/Images/Input/{Heic => Heif}/IMG-20230508-0053.hif | 0 tests/Images/Input/{Heic => Heif}/Irvine_CA.avif | 0 tests/Images/Input/{Heic => Heif}/dwsample-heic-640.heic | 0 tests/Images/Input/{Heic => Heif}/image1.heic | 0 tests/Images/Input/{Heic => Heif}/image2.heic | 0 tests/Images/Input/{Heic => Heif}/image3.heic | 0 tests/Images/Input/{Heic => Heif}/image4.heic | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename tests/Images/Input/{Heic => Heif}/IMG-20230508-0053.hif (100%) mode change 100755 => 100644 rename tests/Images/Input/{Heic => Heif}/Irvine_CA.avif (100%) rename tests/Images/Input/{Heic => Heif}/dwsample-heic-640.heic (100%) rename tests/Images/Input/{Heic => Heif}/image1.heic (100%) rename tests/Images/Input/{Heic => Heif}/image2.heic (100%) rename tests/Images/Input/{Heic => Heif}/image3.heic (100%) rename tests/Images/Input/{Heic => Heif}/image4.heic (100%) diff --git a/tests/Images/Input/Heic/IMG-20230508-0053.hif b/tests/Images/Input/Heif/IMG-20230508-0053.hif old mode 100755 new mode 100644 similarity index 100% rename from tests/Images/Input/Heic/IMG-20230508-0053.hif rename to tests/Images/Input/Heif/IMG-20230508-0053.hif diff --git a/tests/Images/Input/Heic/Irvine_CA.avif b/tests/Images/Input/Heif/Irvine_CA.avif similarity index 100% rename from tests/Images/Input/Heic/Irvine_CA.avif rename to tests/Images/Input/Heif/Irvine_CA.avif diff --git a/tests/Images/Input/Heic/dwsample-heic-640.heic b/tests/Images/Input/Heif/dwsample-heic-640.heic similarity index 100% rename from tests/Images/Input/Heic/dwsample-heic-640.heic rename to tests/Images/Input/Heif/dwsample-heic-640.heic diff --git a/tests/Images/Input/Heic/image1.heic b/tests/Images/Input/Heif/image1.heic similarity index 100% rename from tests/Images/Input/Heic/image1.heic rename to tests/Images/Input/Heif/image1.heic diff --git a/tests/Images/Input/Heic/image2.heic b/tests/Images/Input/Heif/image2.heic similarity index 100% rename from tests/Images/Input/Heic/image2.heic rename to tests/Images/Input/Heif/image2.heic diff --git a/tests/Images/Input/Heic/image3.heic b/tests/Images/Input/Heif/image3.heic similarity index 100% rename from tests/Images/Input/Heic/image3.heic rename to tests/Images/Input/Heif/image3.heic diff --git a/tests/Images/Input/Heic/image4.heic b/tests/Images/Input/Heif/image4.heic similarity index 100% rename from tests/Images/Input/Heic/image4.heic rename to tests/Images/Input/Heif/image4.heic From 4645e407259f5296c057d1bfe98821778040f8e6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 14:10:25 +0100 Subject: [PATCH 029/190] Rename namespace and classes to Heif --- .gitattributes | 4 - src/ImageSharp/Configuration.cs | 6 +- .../Formats/Heic/HeicConfigurationModule.cs | 18 -- .../Heif4CharCode.cs} | 6 +- .../Heif4CharCode.tt} | 6 +- .../HeifCompressionMethod.cs} | 4 +- .../Formats/Heif/HeifConfigurationModule.cs | 18 ++ .../HeifConstants.cs} | 8 +- .../HeicDecoder.cs => Heif/HeifDecoder.cs} | 14 +- .../HeifDecoderCore.cs} | 201 +++++++++--------- .../HeicEncoder.cs => Heif/HeifEncoder.cs} | 10 +- .../HeifEncoderCore.cs} | 92 ++++---- .../HeicFormat.cs => Heif/HeifFormat.cs} | 14 +- .../HeifImageFormatDetector.cs} | 12 +- .../{Heic/HeicItem.cs => Heif/HeifItem.cs} | 10 +- .../HeicItemLink.cs => Heif/HeifItemLink.cs} | 8 +- .../HeicLocation.cs => Heif/HeifLocation.cs} | 6 +- .../HeicMetadata.cs => Heif/HeifMetadata.cs} | 18 +- .../{Heic => Heif}/IHeicEncoderOptions.cs | 4 +- .../{Heic => Heif}/MetadataExtensions.cs | 8 +- .../Formats/{Heic => Heif}/Readme.md | 0 src/ImageSharp/ImageSharp.csproj | 8 +- .../HeifDecoderTests.cs} | 28 +-- .../HeifEncoderTests.cs} | 18 +- .../Formats/ImageFormatManagerTests.cs | 6 +- tests/ImageSharp.Tests/TestImages.cs | 16 +- .../TestUtilities/TestEnvironment.Formats.cs | 4 +- .../Decode_Rgba32_IMG-20230508-0053.png | 0 28 files changed, 274 insertions(+), 273 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs rename src/ImageSharp/Formats/{Heic/Heic4CharCode.cs => Heif/Heif4CharCode.cs} (96%) rename src/ImageSharp/Formats/{Heic/Heic4CharCode.tt => Heif/Heif4CharCode.tt} (94%) rename src/ImageSharp/Formats/{Heic/HeicCompressionMethod.cs => Heif/HeifCompressionMethod.cs} (90%) create mode 100644 src/ImageSharp/Formats/Heif/HeifConfigurationModule.cs rename src/ImageSharp/Formats/{Heic/HeicConstants.cs => Heif/HeifConstants.cs} (70%) rename src/ImageSharp/Formats/{Heic/HeicDecoder.cs => Heif/HeifDecoder.cs} (76%) rename src/ImageSharp/Formats/{Heic/HeicDecoderCore.cs => Heif/HeifDecoderCore.cs} (79%) rename src/ImageSharp/Formats/{Heic/HeicEncoder.cs => Heif/HeifEncoder.cs} (55%) rename src/ImageSharp/Formats/{Heic/HeicEncoderCore.cs => Heif/HeifEncoderCore.cs} (82%) rename src/ImageSharp/Formats/{Heic/HeicFormat.cs => Heif/HeifFormat.cs} (57%) rename src/ImageSharp/Formats/{Heic/HeicImageFormatDetector.cs => Heif/HeifImageFormatDetector.cs} (63%) rename src/ImageSharp/Formats/{Heic/HeicItem.cs => Heif/HeifItem.cs} (89%) rename src/ImageSharp/Formats/{Heic/HeicItemLink.cs => Heif/HeifItemLink.cs} (67%) rename src/ImageSharp/Formats/{Heic/HeicLocation.cs => Heif/HeifLocation.cs} (69%) rename src/ImageSharp/Formats/{Heic/HeicMetadata.cs => Heif/HeifMetadata.cs} (50%) rename src/ImageSharp/Formats/{Heic => Heif}/IHeicEncoderOptions.cs (63%) rename src/ImageSharp/Formats/{Heic => Heif}/MetadataExtensions.cs (58%) rename src/ImageSharp/Formats/{Heic => Heif}/Readme.md (100%) rename tests/ImageSharp.Tests/Formats/{Heic/HeicDecoderTests.cs => Heif/HeifDecoderTests.cs} (51%) rename tests/ImageSharp.Tests/Formats/{Heic/HeicEncoderTests.cs => Heif/HeifEncoderTests.cs} (60%) rename tests/Images/External/ReferenceOutput/{HeicDecoderTests => HeifDecoderTests}/Decode_Rgba32_IMG-20230508-0053.png (100%) diff --git a/.gitattributes b/.gitattributes index 42194daa60..b5f742ab47 100644 --- a/.gitattributes +++ b/.gitattributes @@ -136,7 +136,3 @@ *.ico filter=lfs diff=lfs merge=lfs -text *.cur filter=lfs diff=lfs merge=lfs -text *.ani filter=lfs diff=lfs merge=lfs -text -*.heic filter=lfs diff=lfs merge=lfs -text -*.heif filter=lfs diff=lfs merge=lfs -text -*.hif filter=lfs diff=lfs merge=lfs -text -*.avif filter=lfs diff=lfs merge=lfs -text diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 21ecd2cb2b..daf43edc35 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; @@ -212,7 +212,7 @@ public void Configure(IImageFormatConfigurationModule configuration) /// . /// . /// . - /// . + /// . /// /// The default configuration of . internal static Configuration CreateDefaultInstance() => new( @@ -225,5 +225,5 @@ public void Configure(IImageFormatConfigurationModule configuration) new TiffConfigurationModule(), new WebpConfigurationModule(), new QoiConfigurationModule(), - new HeicConfigurationModule()); + new HeifConfigurationModule()); } diff --git a/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs b/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs deleted file mode 100644 index 0ba7ceef67..0000000000 --- a/src/ImageSharp/Formats/Heic/HeicConfigurationModule.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heic; - -/// -/// Registers the image encoders, decoders and mime type detectors for the HEIC format. -/// -public sealed class HeicConfigurationModule : IImageFormatConfigurationModule -{ - /// - public void Configure(Configuration configuration) - { - configuration.ImageFormatsManager.SetEncoder(HeicFormat.Instance, new HeicEncoder()); - configuration.ImageFormatsManager.SetDecoder(HeicFormat.Instance, HeicDecoder.Instance); - configuration.ImageFormatsManager.AddImageFormatDetector(new HeicImageFormatDetector()); - } -} diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs similarity index 96% rename from src/ImageSharp/Formats/Heic/Heic4CharCode.cs rename to src/ImageSharp/Formats/Heif/Heif4CharCode.cs index a64c94b686..2b14a62725 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -5,13 +5,13 @@ using System.CodeDom.Compiler; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Supported 4 character codes for use in HEIC images. +/// Supported 4 character codes for use in HEIF images. /// [GeneratedCode("T4", "")] -public enum Heic4CharCode : uint +public enum Heif4CharCode : uint { /// /// File Type. diff --git a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt similarity index 94% rename from src/ImageSharp/Formats/Heic/Heic4CharCode.tt rename to src/ImageSharp/Formats/Heif/Heif4CharCode.tt index 61ceb332ac..6f15a33ab9 100644 --- a/src/ImageSharp/Formats/Heic/Heic4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -59,13 +59,13 @@ using System.CodeDom.Compiler; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Supported 4 character codes for use in HEIC images. +/// Supported 4 character codes for use in HEIF images. /// [GeneratedCode("T4", "")] -public enum Heic4CharCode : uint +public enum Heif4CharCode : uint { <# for (int i = 0; i < codes.Length; i += 2) diff --git a/src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs b/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs similarity index 90% rename from src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs rename to src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs index efe826f85c..44d33d2393 100644 --- a/src/ImageSharp/Formats/Heic/HeicCompressionMethod.cs +++ b/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// /// Compression algorithms possible inside an HEIF (High Efficiency File Format) based file. /// -public enum HeicCompressionMethod +public enum HeifCompressionMethod { /// /// High Efficiency Video Coding diff --git a/src/ImageSharp/Formats/Heif/HeifConfigurationModule.cs b/src/ImageSharp/Formats/Heif/HeifConfigurationModule.cs new file mode 100644 index 0000000000..e08418bdb4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifConfigurationModule.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Registers the image encoders, decoders and mime type detectors for the HEIF format. +/// +public sealed class HeifConfigurationModule : IImageFormatConfigurationModule +{ + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(HeifFormat.Instance, new HeifEncoder()); + configuration.ImageFormatsManager.SetDecoder(HeifFormat.Instance, HeifDecoder.Instance); + configuration.ImageFormatsManager.AddImageFormatDetector(new HeifImageFormatDetector()); + } +} diff --git a/src/ImageSharp/Formats/Heic/HeicConstants.cs b/src/ImageSharp/Formats/Heif/HeifConstants.cs similarity index 70% rename from src/ImageSharp/Formats/Heic/HeicConstants.cs rename to src/ImageSharp/Formats/Heif/HeifConstants.cs index 03a394a9fb..5ef49053dd 100644 --- a/src/ImageSharp/Formats/Heic/HeicConstants.cs +++ b/src/ImageSharp/Formats/Heif/HeifConstants.cs @@ -1,14 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Contains HEIC (and H.265) constant values defined in the specification. +/// Contains HEIF constant values defined in the specification. /// -internal static class HeicConstants +internal static class HeifConstants { - public const Heic4CharCode HeicBrand = Heic4CharCode.heic; + public const Heif4CharCode HeicBrand = Heif4CharCode.heic; /// /// The list of mimetypes that equate to a HEIC. diff --git a/src/ImageSharp/Formats/Heic/HeicDecoder.cs b/src/ImageSharp/Formats/Heif/HeifDecoder.cs similarity index 76% rename from src/ImageSharp/Formats/Heic/HeicDecoder.cs rename to src/ImageSharp/Formats/Heif/HeifDecoder.cs index 9fd200575c..c1d79b1096 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoder.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoder.cs @@ -3,21 +3,21 @@ using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Image decoder for reading HEIC images from a stream. +/// Image decoder for reading HEIF images from a stream. /// -public sealed class HeicDecoder : ImageDecoder +public sealed class HeifDecoder : ImageDecoder { - private HeicDecoder() + private HeifDecoder() { } /// /// Gets the shared instance. /// - public static HeicDecoder Instance { get; } = new(); + public static HeifDecoder Instance { get; } = new(); /// protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) @@ -25,7 +25,7 @@ protected override ImageInfo Identify(DecoderOptions options, Stream stream, Can Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new HeicDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + return new HeifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); } /// @@ -34,7 +34,7 @@ protected override Image Decode(DecoderOptions options, Stream s Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - HeicDecoderCore decoder = new(options); + HeifDecoderCore decoder = new(options); Image image = decoder.Decode(options.Configuration, stream, cancellationToken); return image; diff --git a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs similarity index 79% rename from src/ImageSharp/Formats/Heic/HeicDecoderCore.cs rename to src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index ca6d2eb7a4..fb81a943c5 100644 --- a/src/ImageSharp/Formats/Heic/HeicDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -3,19 +3,18 @@ using System.Buffers; using System.Buffers.Binary; -using System.Globalization; using System.Text; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Performs the PBM decoding operation. +/// Performs the HEIF decoding operation. /// -internal sealed class HeicDecoderCore : IImageDecoderInternals +internal sealed class HeifDecoderCore : IImageDecoderInternals { /// /// The general configuration. @@ -29,23 +28,23 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals private uint primaryItem; - private readonly List items; + private readonly List items; - private readonly List itemLinks; + private readonly List itemLinks; private readonly byte[] buffer; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The decoder options. - public HeicDecoderCore(DecoderOptions options) + public HeifDecoderCore(DecoderOptions options) { this.Options = options; this.configuration = options.Configuration; this.metadata = new ImageMetadata(); - this.items = new List(); - this.itemLinks = new List(); + this.items = new List(); + this.itemLinks = new List(); this.buffer = new byte[80]; } @@ -61,23 +60,23 @@ public Image Decode(BufferedReadStream stream, CancellationToken { if (!this.CheckFileTypeBox(stream)) { - throw new ImageFormatException("Not an HEIC image."); + throw new ImageFormatException("Not an HEIF image."); } Image? image = null; while (stream.Position < stream.Length) { - long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); EnsureBoxBoundary(boxLength, stream); switch (boxType) { - case Heic4CharCode.meta: + case Heif4CharCode.meta: this.ParseMetadata(stream, boxLength); break; - case Heic4CharCode.mdat: + case Heif4CharCode.mdat: image = this.ParseMediaData(stream, boxLength); break; - case Heic4CharCode.free: + case Heif4CharCode.free: SkipBox(stream, boxLength); break; case 0U: @@ -89,7 +88,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken } } - HeicItem? item = this.FindItemById(this.primaryItem); + HeifItem? item = this.FindItemById(this.primaryItem); if (item == null) { throw new ImageFormatException("No primary item found"); @@ -100,6 +99,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken throw new NotImplementedException("No JPEG image decoded"); } + this.UpdateMetadata(image.Metadata, item); return image; } @@ -110,11 +110,11 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat while (stream.Position < stream.Length) { - long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); EnsureBoxBoundary(boxLength, stream); switch (boxType) { - case Heic4CharCode.meta: + case Heif4CharCode.meta: this.ParseMetadata(stream, boxLength); break; default: @@ -124,40 +124,45 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat } } - HeicItem? item = this.FindItemById(this.primaryItem); + HeifItem? item = this.FindItemById(this.primaryItem); if (item == null) { throw new ImageFormatException("No primary item found"); } - HeicMetadata meta = this.metadata.GetHeicMetadata(); - HeicCompressionMethod compressionMethod = HeicCompressionMethod.Hevc; - if (item.Type == Heic4CharCode.av01) - { - compressionMethod = HeicCompressionMethod.Av1; - } - else if (this.itemLinks.Any(link => link.Type == Heic4CharCode.thmb)) - { - compressionMethod = HeicCompressionMethod.LegacyJpeg; - } - - meta.CompressionMethod = compressionMethod; + this.UpdateMetadata(this.metadata, item); return new ImageInfo(new PixelTypeInfo(item.BitsPerPixel), new(item.Extent.Width, item.Extent.Height), this.metadata); } private bool CheckFileTypeBox(BufferedReadStream stream) { - long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); Span buffer = this.ReadIntoBuffer(stream, boxLength); uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); - bool correctBrand = majorBrand == (uint)Heic4CharCode.heic || majorBrand == (uint)Heic4CharCode.heix; + bool correctBrand = majorBrand is (uint)Heif4CharCode.heic or (uint)Heif4CharCode.heix or (uint)Heif4CharCode.avif; // TODO: Interpret minorVersion and compatible brands. - return boxType == Heic4CharCode.ftyp && correctBrand; + return boxType == Heif4CharCode.ftyp && correctBrand; + } + + private void UpdateMetadata(ImageMetadata metadata, HeifItem item) + { + HeifMetadata meta = metadata.GetHeifMetadata(); + HeifCompressionMethod compressionMethod = HeifCompressionMethod.Hevc; + if (item.Type == Heif4CharCode.av01) + { + compressionMethod = HeifCompressionMethod.Av1; + } + else if (item.Type == Heif4CharCode.jpeg || this.itemLinks.Any(link => link.Type == Heif4CharCode.thmb)) + { + compressionMethod = HeifCompressionMethod.LegacyJpeg; + } + + meta.CompressionMethod = compressionMethod; } - private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) + private long ReadBoxHeader(BufferedReadStream stream, out Heif4CharCode boxType) { // Read 4 bytes of length of box Span buf = this.ReadIntoBuffer(stream, 8); @@ -165,7 +170,7 @@ private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) long headerSize = 8; // Read 4 bytes of box type - boxType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); + boxType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buf[4..]); if (boxSize == 1) { @@ -177,11 +182,11 @@ private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) return boxSize - headerSize; } - private static int ParseBoxHeader(Span buffer, out long length, out Heic4CharCode boxType) + private static int ParseBoxHeader(Span buffer, out long length, out Heif4CharCode boxType) { long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); int bytesRead = 4; - boxType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + boxType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; if (boxSize == 1) { @@ -199,34 +204,34 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) stream.Skip(4); while (stream.Position < endPosition) { - long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType); + long length = this.ReadBoxHeader(stream, out Heif4CharCode boxType); EnsureBoxBoundary(length, boxLength); switch (boxType) { - case Heic4CharCode.iprp: + case Heif4CharCode.iprp: this.ParseItemProperties(stream, length); break; - case Heic4CharCode.iinf: + case Heif4CharCode.iinf: this.ParseItemInfo(stream, length); break; - case Heic4CharCode.iref: + case Heif4CharCode.iref: this.ParseItemReference(stream, length); break; - case Heic4CharCode.pitm: + case Heif4CharCode.pitm: this.ParsePrimaryItem(stream, length); break; - case Heic4CharCode.hdlr: + case Heif4CharCode.hdlr: this.ParseHandler(stream, length); break; - case Heic4CharCode.iloc: + case Heif4CharCode.iloc: this.ParseItemLocation(stream, length); break; - case Heic4CharCode.dinf: - case Heic4CharCode.idat: - case Heic4CharCode.grpl: - case Heic4CharCode.ipro: - case Heic4CharCode.uuid: - case Heic4CharCode.ipmc: + case Heif4CharCode.dinf: + case Heif4CharCode.idat: + case Heif4CharCode.grpl: + case Heif4CharCode.ipro: + case Heif4CharCode.uuid: + case Heif4CharCode.ipmc: // Silently skip these boxes. SkipBox(stream, length); break; @@ -242,8 +247,8 @@ private void ParseHandler(BufferedReadStream stream, long boxLength) // Only read the handler type, to check if this is not a movie file. int bytesRead = 8; - Heic4CharCode handlerType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - if (handlerType != Heic4CharCode.pict) + Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + if (handlerType != Heif4CharCode.pict) { throw new ImageFormatException("Not a picture file."); } @@ -266,15 +271,15 @@ private void ParseItemInfo(BufferedReadStream stream, long boxLength) private int ParseItemInfoEntry(Span buffer) { - int bytesRead = ParseBoxHeader(buffer, out long boxLength, out Heic4CharCode boxType); + int bytesRead = ParseBoxHeader(buffer, out long boxLength, out Heif4CharCode boxType); byte version = buffer[bytesRead]; bytesRead += 4; - HeicItem? item = null; + HeifItem? item = null; if (version is 0 or 1) { uint itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; - item = new HeicItem(boxType, itemId); + item = new HeifItem(boxType, itemId); // Skip Protection Index, not sure what that means... bytesRead += 2; @@ -312,12 +317,12 @@ private int ParseItemInfoEntry(Span buffer) // Skip Protection Index, not sure what that means... bytesRead += 2; - Heic4CharCode itemType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + Heif4CharCode itemType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); bytesRead += 4; - item = new HeicItem(itemType, itemId); + item = new HeifItem(itemType, itemId); item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - if (item.Type == Heic4CharCode.mime) + if (item.Type == Heif4CharCode.mime) { item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; @@ -329,7 +334,7 @@ private int ParseItemInfoEntry(Span buffer) bytesRead += item.ContentEncoding.Length + 1; } } - else if (item.Type == Heic4CharCode.uri) + else if (item.Type == Heif4CharCode.uri) { item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.UriType.Length + 1; @@ -352,9 +357,9 @@ private void ParseItemReference(BufferedReadStream stream, long boxLength) bytesRead += 4; while (bytesRead < boxLength) { - bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); + bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); - HeicItemLink link = new(linkType, sourceId); + HeifItemLink link = new(linkType, sourceId); int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); bytesRead += 2; @@ -380,18 +385,18 @@ private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) private void ParseItemProperties(BufferedReadStream stream, long boxLength) { // Cannot use Dictionary here, Properties can have multiple instances with the same key. - List> properties = new(); + List> properties = new(); long endBoxPosition = stream.Position + boxLength; while (stream.Position < endBoxPosition) { - long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType); + long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType); EnsureBoxBoundary(containerLength, boxLength); - if (containerType == Heic4CharCode.ipco) + if (containerType == Heif4CharCode.ipco) { // Parse Item Property Container, which is just an array of property boxes. this.ParsePropertyContainer(stream, containerLength, properties); } - else if (containerType == Heic4CharCode.ipma) + else if (containerType == Heif4CharCode.ipma) { // Parse Item Property Association this.ParsePropertyAssociation(stream, containerLength, properties); @@ -403,28 +408,28 @@ private void ParseItemProperties(BufferedReadStream stream, long boxLength) } } - private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, List> properties) + private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, List> properties) { long endPosition = stream.Position + boxLength; while (stream.Position < endPosition) { - int itemLength = (int)this.ReadBoxHeader(stream, out Heic4CharCode itemType); + int itemLength = (int)this.ReadBoxHeader(stream, out Heif4CharCode itemType); EnsureBoxBoundary(itemLength, boxLength); Span buffer = this.ReadIntoBuffer(stream, itemLength); switch (itemType) { - case Heic4CharCode.ispe: + case Heif4CharCode.ispe: // Skip over version (8 bits) and flags (24 bits). int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); - properties.Add(new KeyValuePair(Heic4CharCode.ispe, new Size(width, height))); + properties.Add(new KeyValuePair(Heif4CharCode.ispe, new Size(width, height))); break; - case Heic4CharCode.pasp: + case Heif4CharCode.pasp: int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - properties.Add(new KeyValuePair(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); + properties.Add(new KeyValuePair(Heif4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); break; - case Heic4CharCode.pixi: + case Heif4CharCode.pixi: // Skip over version (8 bits) and flags (24 bits). int channelCount = buffer[4]; int offset = 5; @@ -434,20 +439,20 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L bitsPerPixel += buffer[offset + i]; } - properties.Add(new KeyValuePair(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); + properties.Add(new KeyValuePair(Heif4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); break; - case Heic4CharCode.altt: - case Heic4CharCode.colr: - case Heic4CharCode.imir: - case Heic4CharCode.irot: - case Heic4CharCode.iscl: - case Heic4CharCode.hvcC: - case Heic4CharCode.av1C: - case Heic4CharCode.rloc: - case Heic4CharCode.udes: + case Heif4CharCode.altt: + case Heif4CharCode.colr: + case Heif4CharCode.imir: + case Heif4CharCode.irot: + case Heif4CharCode.iscl: + case Heif4CharCode.hvcC: + case Heif4CharCode.av1C: + case Heif4CharCode.rloc: + case Heif4CharCode.udes: // TODO: Implement - properties.Add(new KeyValuePair(itemType, new object())); + properties.Add(new KeyValuePair(itemType, new object())); break; default: throw new ImageFormatException($"Unknown item type in property box of '{PrettyPrint(itemType)}'"); @@ -455,7 +460,7 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L } } - private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) + private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) { Span buffer = this.ReadIntoBuffer(stream, boxLength); byte version = buffer[0]; @@ -477,16 +482,16 @@ private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, propId = buffer[bytesRead++] & 0x4FU; } - KeyValuePair prop = properties[(int)propId]; + KeyValuePair prop = properties[(int)propId]; switch (prop.Key) { - case Heic4CharCode.ispe: + case Heif4CharCode.ispe: this.items[itemId].SetExtent((Size)prop.Value); break; - case Heic4CharCode.pasp: + case Heif4CharCode.pasp: this.items[itemId].PixelAspectRatio = (Size)prop.Value; break; - case Heic4CharCode.pixi: + case Heif4CharCode.pixi: int[] values = (int[])prop.Value; this.items[itemId].ChannelCount = values[0]; this.items[itemId].BitsPerPixel = values[1]; @@ -516,7 +521,7 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) for (uint i = 0; i < itemCount; i++) { uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); - HeicItem? item = this.FindItemById(itemId); + HeifItem? item = this.FindItemById(itemId); if (version is 1 or 2) { bytesRead++; @@ -538,7 +543,7 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); - HeicLocation loc = new HeicLocation((long)extentOffset, (long)extentLength); + HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); item?.DataLocations.Add(loc); } } @@ -591,14 +596,14 @@ private Image ParseMediaData(BufferedReadStream stream, long box where TPixel : unmanaged, IPixel { // FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. - HeicItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heic4CharCode.thmb); + HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.thmb); if (thumbLink == null) { throw new NotImplementedException("No thumbnail found"); } - HeicItem? thumbItem = this.FindItemById(thumbLink.SourceId); - if (thumbItem == null || thumbItem.Type != Heic4CharCode.jpeg) + HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId); + if (thumbItem == null || thumbItem.Type != Heif4CharCode.jpeg) { throw new NotImplementedException("No HVC decoding implemented yet"); } @@ -610,8 +615,8 @@ private Image ParseMediaData(BufferedReadStream stream, long box Span thumbSpan = thumbMemory.GetSpan(); stream.Read(thumbSpan); - HeicMetadata meta = this.metadata.GetHeicMetadata(); - meta.CompressionMethod = HeicCompressionMethod.LegacyJpeg; + HeifMetadata meta = this.metadata.GetHeifMetadata(); + meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; return Image.Load(thumbSpan); } @@ -645,7 +650,7 @@ private static void EnsureBoxBoundary(long boxLength, long parentLength) } } - private HeicItem? FindItemById(uint itemId) + private HeifItem? FindItemById(uint itemId) => this.items.FirstOrDefault(item => item.Id == itemId); private static string ReadNullTerminatedString(Span span) @@ -654,7 +659,7 @@ private static string ReadNullTerminatedString(Span span) return Encoding.UTF8.GetString(bytes); } - private static string PrettyPrint(Heic4CharCode code) + private static string PrettyPrint(Heif4CharCode code) { string? pretty = Enum.GetName(code); if (string.IsNullOrEmpty(pretty)) diff --git a/src/ImageSharp/Formats/Heic/HeicEncoder.cs b/src/ImageSharp/Formats/Heif/HeifEncoder.cs similarity index 55% rename from src/ImageSharp/Formats/Heic/HeicEncoder.cs rename to src/ImageSharp/Formats/Heif/HeifEncoder.cs index 8579564ad1..170dc7bd72 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoder.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoder.cs @@ -1,19 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Advanced; - -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Image encoder for writing an image to a stream as HEIC images. +/// Image encoder for writing an image to a stream as HEIF images. /// -public sealed class HeicEncoder : ImageEncoder +public sealed class HeifEncoder : ImageEncoder { /// protected override void Encode(Image image, Stream stream, CancellationToken cancellationToken) { - HeicEncoderCore encoder = new(image.Configuration, this); + HeifEncoderCore encoder = new(image.Configuration, this); encoder.Encode(image, stream, cancellationToken); } } diff --git a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs similarity index 82% rename from src/ImageSharp/Formats/Heic/HeicEncoderCore.cs rename to src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index ce2784da80..8f83d9c6d8 100644 --- a/src/ImageSharp/Formats/Heic/HeicEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -1,18 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Image encoder for writing an image to a stream as a HEIC image. +/// Image encoder for writing an image to a stream as a HEIF image. /// -internal sealed class HeicEncoderCore : IImageEncoderInternals +internal sealed class HeifEncoderCore : IImageEncoderInternals { /// /// The global configuration. @@ -22,14 +21,14 @@ internal sealed class HeicEncoderCore : IImageEncoderInternals /// /// The encoder with options. /// - private readonly HeicEncoder encoder; + private readonly HeifEncoder encoder; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration. /// The encoder with options. - public HeicEncoderCore(Configuration configuration, HeicEncoder encoder) + public HeifEncoderCore(Configuration configuration, HeifEncoder encoder) { this.configuration = configuration; this.encoder = encoder; @@ -49,8 +48,8 @@ public async void Encode(Image image, Stream stream, Cancellatio Guard.NotNull(stream, nameof(stream)); byte[] pixels = await CompressPixels(image, cancellationToken); - List items = new(); - List links = new(); + List items = new(); + List links = new(); GenerateItems(image, pixels, items, links); // Write out the generated header and pixels. @@ -58,25 +57,28 @@ public async void Encode(Image image, Stream stream, Cancellatio this.WriteMetadataBox(items, links, stream); this.WriteMediaDataBox(pixels, stream); stream.Flush(); + + HeifMetadata meta = image.Metadata.GetHeifMetadata(); + meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; } - private static void GenerateItems(Image image, byte[] pixels, List items, List links) + private static void GenerateItems(Image image, byte[] pixels, List items, List links) where TPixel : unmanaged, IPixel { - HeicItem primaryItem = new(Heic4CharCode.jpeg, 1u); - primaryItem.DataLocations.Add(new HeicLocation(0L, pixels.LongLength)); + HeifItem primaryItem = new(Heif4CharCode.jpeg, 1u); + primaryItem.DataLocations.Add(new HeifLocation(0L, pixels.LongLength)); primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; primaryItem.SetExtent(image.Size); items.Add(primaryItem); // Create a fake thumbnail, to make our own Decoder happy. - HeicItemLink thumbnail = new(Heic4CharCode.thmb, 1u); + HeifItemLink thumbnail = new(Heif4CharCode.thmb, 1u); thumbnail.DestinationIds.Add(1u); links.Add(thumbnail); } - private static int WriteBoxHeader(Span buffer, Heic4CharCode type) + private static int WriteBoxHeader(Span buffer, Heif4CharCode type) { int bytesWritten = 0; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 8U); @@ -87,7 +89,7 @@ private static int WriteBoxHeader(Span buffer, Heic4CharCode type) return bytesWritten; } - private static int WriteBoxHeader(Span buffer, Heic4CharCode type, byte version, uint flags) + private static int WriteBoxHeader(Span buffer, Heif4CharCode type, byte version, uint flags) { int bytesWritten = 0; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 12); @@ -106,25 +108,25 @@ private static int WriteBoxHeader(Span buffer, Heic4CharCode type, byte ve private void WriteFileTypeBox(Stream stream) { Span buffer = stackalloc byte[24]; - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ftyp); - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.heic); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ftyp); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.mif1); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.mif1); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.heic); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); stream.Write(buffer); } - private void WriteMetadataBox(List items, List links, Stream stream) + private void WriteMetadataBox(List items, List links, Stream stream) { using AutoExpandingMemory memory = new(this.configuration, 0x1000); Span buffer = memory.GetSpan(12); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.meta, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.meta, 0, 0); bytesWritten += WriteHandlerBox(memory, bytesWritten); bytesWritten += WritePrimaryItemBox(memory, bytesWritten); bytesWritten += WriteItemInfoBox(memory, bytesWritten, items); @@ -141,10 +143,10 @@ private void WriteMetadataBox(List items, List links, St private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 33); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.hdlr, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.hdlr, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heic4CharCode.pict); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.pict); bytesWritten += 4; for (int i = 0; i < 13; i++) { @@ -158,7 +160,7 @@ private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryO private static int WritePrimaryItemBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 14); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.pitm, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.pitm, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); bytesWritten += 2; @@ -166,16 +168,16 @@ private static int WritePrimaryItemBox(AutoExpandingMemory memory, int mem return bytesWritten; } - private static int WriteItemInfoBox(AutoExpandingMemory memory, int memoryOffset, List items) + private static int WriteItemInfoBox(AutoExpandingMemory memory, int memoryOffset, List items) { Span buffer = memory.GetSpan(memoryOffset, 14 + (items.Count * 21)); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iinf, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iinf, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count); bytesWritten += 2; - foreach (HeicItem item in items) + foreach (HeifItem item in items) { int itemLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.infe, 2, 0); + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.infe, 2, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id); bytesWritten += 2; BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0); @@ -191,11 +193,11 @@ private static int WriteItemInfoBox(AutoExpandingMemory memory, int memory return bytesWritten; } - private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List items, List links) + private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List items, List links) { Span buffer = memory.GetSpan(memoryOffset, 12 + (links.Count * (12 + (items.Count * 2)))); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iref, 0, 0); - foreach (HeicItemLink link in links) + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iref, 0, 0); + foreach (HeifItemLink link in links) { int itemLengthOffset = bytesWritten; bytesWritten += WriteBoxHeader(buffer[bytesWritten..], link.Type); @@ -216,16 +218,16 @@ private static int WriteItemReferenceBox(AutoExpandingMemory memory, int m return bytesWritten; } - private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int memoryOffset, List items) + private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int memoryOffset, List items) { const ushort numPropPerItem = 1; Span buffer = memory.GetSpan(memoryOffset, 20); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iprp); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iprp); // Write 'ipco' box int ipcoLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.ipco); - foreach (HeicItem item in items) + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.ipco); + foreach (HeifItem item in items) { bytesWritten += WriteSpatialExtentPropertyBox(memory, memoryOffset + bytesWritten, item); } @@ -235,11 +237,11 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int // Write 'ipma' box int ipmaLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heic4CharCode.ipma, 0, 0); + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.ipma, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)(items.Count * numPropPerItem)); bytesWritten += 4; ushort propIndex = 0; - foreach (HeicItem item in items) + foreach (HeifItem item in items) { BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id); bytesWritten += 2; @@ -256,10 +258,10 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int return bytesWritten; } - private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memory, int memoryOffset, HeicItem item) + private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memory, int memoryOffset, HeifItem item) { Span buffer = memory.GetSpan(memoryOffset, 20); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.ispe, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ispe, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height); @@ -272,16 +274,16 @@ private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memor private static int WriteItemDataBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 10); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.idat); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.idat); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); return bytesWritten; } - private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) + private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) { Span buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8)); - int bytesWritten = WriteBoxHeader(buffer, Heic4CharCode.iloc, 1, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iloc, 1, 0); buffer[bytesWritten++] = 0x44; buffer[bytesWritten++] = 0; BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); @@ -293,10 +295,10 @@ private static int WriteItemLocationBox(AutoExpandingMemory memory, int me buffer[bytesWritten++] = 0; } - IEnumerable itemLocs = items.SelectMany(item => item.DataLocations).Where(loc => loc != null); + IEnumerable itemLocs = items.SelectMany(item => item.DataLocations).Where(loc => loc != null); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)itemLocs.Count()); bytesWritten += 2; - foreach (HeicLocation loc in itemLocs) + foreach (HeifLocation loc in itemLocs) { BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Offset); bytesWritten += 4; @@ -311,7 +313,7 @@ private static int WriteItemLocationBox(AutoExpandingMemory memory, int me private void WriteMediaDataBox(Span data, Stream stream) { Span buf = stackalloc byte[12]; - int bytesWritten = WriteBoxHeader(buf, Heic4CharCode.mdat); + int bytesWritten = WriteBoxHeader(buf, Heif4CharCode.mdat); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)(data.Length + bytesWritten)); stream.Write(buf[..bytesWritten]); diff --git a/src/ImageSharp/Formats/Heic/HeicFormat.cs b/src/ImageSharp/Formats/Heif/HeifFormat.cs similarity index 57% rename from src/ImageSharp/Formats/Heic/HeicFormat.cs rename to src/ImageSharp/Formats/Heif/HeifFormat.cs index be47afce00..3a6f23ecb6 100644 --- a/src/ImageSharp/Formats/Heic/HeicFormat.cs +++ b/src/ImageSharp/Formats/Heif/HeifFormat.cs @@ -1,21 +1,21 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// /// Registers the image encoders, decoders and mime type detectors for the HEIC format. /// -public sealed class HeicFormat : IImageFormat +public sealed class HeifFormat : IImageFormat { - private HeicFormat() + private HeifFormat() { } /// /// Gets the shared instance. /// - public static HeicFormat Instance { get; } = new(); + public static HeifFormat Instance { get; } = new(); /// public string Name => "HEIC"; @@ -24,11 +24,11 @@ private HeicFormat() public string DefaultMimeType => "image/heif"; /// - public IEnumerable MimeTypes => HeicConstants.MimeTypes; + public IEnumerable MimeTypes => HeifConstants.MimeTypes; /// - public IEnumerable FileExtensions => HeicConstants.FileExtensions; + public IEnumerable FileExtensions => HeifConstants.FileExtensions; /// - public HeicMetadata CreateDefaultFormatMetadata() => new(); + public HeifMetadata CreateDefaultFormatMetadata() => new(); } diff --git a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs b/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs similarity index 63% rename from src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs rename to src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs index 93aefca223..10dfa86158 100644 --- a/src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs @@ -4,12 +4,12 @@ using System.Buffers.Binary; using System.Diagnostics.CodeAnalysis; -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Detects HEIC file headers. +/// Detects HEIF file headers. /// -public sealed class HeicImageFormatDetector : IImageFormatDetector +public sealed class HeifImageFormatDetector : IImageFormatDetector { /// public int HeaderSize => 12; @@ -17,14 +17,14 @@ public sealed class HeicImageFormatDetector : IImageFormatDetector /// public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out IImageFormat? format) { - format = IsSupportedFileFormat(header) ? HeicFormat.Instance : null; + format = IsSupportedFileFormat(header) ? HeifFormat.Instance : null; return format != null; } private static bool IsSupportedFileFormat(ReadOnlySpan header) { - bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heic4CharCode.ftyp; + bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heif4CharCode.ftyp; uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); - return hasFtyp && (brand == (uint)Heic4CharCode.heic || brand == (uint)Heic4CharCode.heix || brand == (uint)Heic4CharCode.avif); + return hasFtyp && (brand == (uint)Heif4CharCode.heic || brand == (uint)Heif4CharCode.heix || brand == (uint)Heif4CharCode.avif); } } diff --git a/src/ImageSharp/Formats/Heic/HeicItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs similarity index 89% rename from src/ImageSharp/Formats/Heic/HeicItem.cs rename to src/ImageSharp/Formats/Heif/HeifItem.cs index 4127aebb01..60f26df3ff 100644 --- a/src/ImageSharp/Formats/Heic/HeicItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Provides definition for a HEIC Item. +/// Provides definition for a HEIF Item. /// -public class HeicItem(Heic4CharCode type, uint id) +public class HeifItem(Heif4CharCode type, uint id) { /// /// Gets the ID of this Item. @@ -16,7 +16,7 @@ public class HeicItem(Heic4CharCode type, uint id) /// /// Gets the type of this Item. /// - public Heic4CharCode Type { get; } = type; + public Heif4CharCode Type { get; } = type; /// /// Gets or sets the name of this item. @@ -71,7 +71,7 @@ public class HeicItem(Heic4CharCode type, uint id) /// /// Gets the list of data locations for this item. /// - public List DataLocations { get; } = new List(); + public List DataLocations { get; } = new List(); /// /// Set the image extent. diff --git a/src/ImageSharp/Formats/Heic/HeicItemLink.cs b/src/ImageSharp/Formats/Heif/HeifItemLink.cs similarity index 67% rename from src/ImageSharp/Formats/Heic/HeicItemLink.cs rename to src/ImageSharp/Formats/Heif/HeifItemLink.cs index da1578fcfa..95e84a237f 100644 --- a/src/ImageSharp/Formats/Heic/HeicItemLink.cs +++ b/src/ImageSharp/Formats/Heif/HeifItemLink.cs @@ -1,17 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Link between instances within the same HEIC file. +/// Link between instances within the same HEIF file. /// -public class HeicItemLink(Heic4CharCode type, uint sourceId) +public class HeifItemLink(Heif4CharCode type, uint sourceId) { /// /// Gets the type of link. /// - public Heic4CharCode Type { get; } = type; + public Heif4CharCode Type { get; } = type; /// /// Gets the ID of the source item of this link. diff --git a/src/ImageSharp/Formats/Heic/HeicLocation.cs b/src/ImageSharp/Formats/Heif/HeifLocation.cs similarity index 69% rename from src/ImageSharp/Formats/Heic/HeicLocation.cs rename to src/ImageSharp/Formats/Heif/HeifLocation.cs index 7a342656e3..9348ba981e 100644 --- a/src/ImageSharp/Formats/Heic/HeicLocation.cs +++ b/src/ImageSharp/Formats/Heif/HeifLocation.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Location within the file of an . +/// Location within the file of an . /// -public class HeicLocation(long offset, long length) +public class HeifLocation(long offset, long length) { /// /// Gets the file offset of this location. diff --git a/src/ImageSharp/Formats/Heic/HeicMetadata.cs b/src/ImageSharp/Formats/Heif/HeifMetadata.cs similarity index 50% rename from src/ImageSharp/Formats/Heic/HeicMetadata.cs rename to src/ImageSharp/Formats/Heif/HeifMetadata.cs index f6f95a86cd..725389f0e5 100644 --- a/src/ImageSharp/Formats/Heic/HeicMetadata.cs +++ b/src/ImageSharp/Formats/Heif/HeifMetadata.cs @@ -1,32 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Provides HEIC specific metadata information for the image. +/// Provides HEIF specific metadata information for the image. /// -public class HeicMetadata : IDeepCloneable +public class HeifMetadata : IDeepCloneable { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public HeicMetadata() + public HeifMetadata() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private HeicMetadata(HeicMetadata other) + private HeifMetadata(HeifMetadata other) => this.CompressionMethod = other.CompressionMethod; /// /// Gets or sets the compression method used for the primary frame. /// - public HeicCompressionMethod CompressionMethod { get; set; } + public HeifCompressionMethod CompressionMethod { get; set; } /// - public IDeepCloneable DeepClone() => new HeicMetadata(this); + public IDeepCloneable DeepClone() => new HeifMetadata(this); } diff --git a/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs b/src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs similarity index 63% rename from src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs rename to src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs index fb73ae3ab2..c3bde13730 100644 --- a/src/ImageSharp/Formats/Heic/IHeicEncoderOptions.cs +++ b/src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heic; +namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Configuration options for use during HEIC encoding. +/// Configuration options for use during HEIF encoding. /// internal interface IHeicEncoderOptions { diff --git a/src/ImageSharp/Formats/Heic/MetadataExtensions.cs b/src/ImageSharp/Formats/Heif/MetadataExtensions.cs similarity index 58% rename from src/ImageSharp/Formats/Heic/MetadataExtensions.cs rename to src/ImageSharp/Formats/Heif/MetadataExtensions.cs index b708bfc308..5962df213f 100644 --- a/src/ImageSharp/Formats/Heic/MetadataExtensions.cs +++ b/src/ImageSharp/Formats/Heif/MetadataExtensions.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Metadata; namespace SixLabors.ImageSharp; @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp; public static partial class MetadataExtensions { /// - /// Gets the pbm format specific metadata for the image. + /// Gets the HEIF format specific metadata for the image. /// /// The metadata this method extends. - /// The . - public static HeicMetadata GetHeicMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(HeicFormat.Instance); + /// The . + public static HeifMetadata GetHeifMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(HeifFormat.Instance); } diff --git a/src/ImageSharp/Formats/Heic/Readme.md b/src/ImageSharp/Formats/Heif/Readme.md similarity index 100% rename from src/ImageSharp/Formats/Heic/Readme.md rename to src/ImageSharp/Formats/Heif/Readme.md diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 06e7d13336..63bc8131bd 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -52,10 +52,10 @@ - + True True - Heic4CharCode.tt + Heif4CharCode.tt True @@ -155,9 +155,9 @@ - + TextTemplatingFileGenerator - Heic4CharCode.cs + Heif4CharCode.cs TextTemplatingFileGenerator diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs similarity index 51% rename from tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs index e08d5b2208..78ada561c5 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs @@ -1,43 +1,43 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.PixelFormats; -namespace SixLabors.ImageSharp.Tests.Formats.Heic; +namespace SixLabors.ImageSharp.Tests.Formats.Heif; -[Trait("Format", "Heic")] +[Trait("Format", "Heif")] [ValidateDisposedMemoryAllocations] -public class HeicDecoderTests +public class HeifDecoderTests { [Theory] - [InlineData(TestImages.Heic.Image1, HeicCompressionMethod.Hevc)] - [InlineData(TestImages.Heic.Sample640x427, HeicCompressionMethod.Hevc)] - [InlineData(TestImages.Heic.FujiFilmHif, HeicCompressionMethod.LegacyJpeg)] - [InlineData(TestImages.Heic.IrvineAvif, HeicCompressionMethod.Av1)] - public void Identify(string imagePath, HeicCompressionMethod compressionMethod) + [InlineData(TestImages.Heif.Image1, HeifCompressionMethod.Hevc)] + [InlineData(TestImages.Heif.Sample640x427, HeifCompressionMethod.Hevc)] + [InlineData(TestImages.Heif.FujiFilmHif, HeifCompressionMethod.LegacyJpeg)] + [InlineData(TestImages.Heif.IrvineAvif, HeifCompressionMethod.Av1)] + public void Identify(string imagePath, HeifCompressionMethod compressionMethod) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); ImageInfo imageInfo = Image.Identify(stream); - HeicMetadata heicMetadata = imageInfo.Metadata.GetHeicMetadata(); + HeifMetadata heicMetadata = imageInfo.Metadata.GetHeifMetadata(); Assert.NotNull(imageInfo); - Assert.Equal(HeicFormat.Instance, imageInfo.Metadata.DecodedImageFormat); + Assert.Equal(HeifFormat.Instance, imageInfo.Metadata.DecodedImageFormat); Assert.Equal(compressionMethod, heicMetadata.CompressionMethod); } [Theory] - [WithFile(TestImages.Heic.FujiFilmHif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.FujiFilmHif, PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - HeicMetadata heicMetadata = image.Metadata.GetHeicMetadata(); + HeifMetadata heicMetadata = image.Metadata.GetHeifMetadata(); image.DebugSave(provider); image.CompareToReferenceOutput(provider); - Assert.Equal(HeicCompressionMethod.Hevc, heicMetadata.CompressionMethod); + Assert.Equal(HeifCompressionMethod.LegacyJpeg, heicMetadata.CompressionMethod); } } diff --git a/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs similarity index 60% rename from tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs index 3b6087d0a5..c7e0e8832d 100644 --- a/tests/ImageSharp.Tests/Formats/Heic/HeicEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs @@ -1,32 +1,32 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heic; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -namespace SixLabors.ImageSharp.Tests.Formats.Heic; +namespace SixLabors.ImageSharp.Tests.Formats.Heif; -[Trait("Format", "Heic")] +[Trait("Format", "Heif")] [ValidateDisposedMemoryAllocations] -public class HeicEncoderTests +public class HeifEncoderTests { [Theory] - [WithFile(TestImages.Heic.Sample640x427, PixelTypes.Rgba32)] - public static void Encode(TestImageProvider provider) + [WithFile(TestImages.Heif.Sample640x427, PixelTypes.Rgba32, HeifCompressionMethod.LegacyJpeg)] + public static void Encode(TestImageProvider provider, HeifCompressionMethod compressionMethod) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(new MagickReferenceDecoder()); using MemoryStream stream = new(); - HeicEncoder encoder = new(); + HeifEncoder encoder = new(); image.Save(stream, encoder); stream.Position = 0; using Image encodedImage = Image.Load(stream); - HeicMetadata heicMetadata = encodedImage.Metadata.GetHeicMetadata(); + HeifMetadata heifMetadata = encodedImage.Metadata.GetHeifMetadata(); ImageComparer.Exact.CompareImages(image, encodedImage); - //Assert.Equal(heicMetadata.Channels, channels); + Assert.Equal(compressionMethod, heifMetadata.CompressionMethod); } } diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 7d21de6dee..c7cdc4d3a6 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -5,8 +5,8 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; @@ -36,7 +36,7 @@ public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -46,7 +46,7 @@ public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); - Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4b6b1463c0..8f2192af48 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1105,18 +1105,18 @@ public static class Qoi public const string Wikipedia008 = "Qoi/wikipedia_008.qoi"; } - public static class Heic + public static class Heif { - public const string Image1 = "Heic/image1.heic"; - public const string Image2 = "Heic/image2.heic"; - public const string Image3 = "Heic/image3.heic"; - public const string Image4 = "Heic/image4.heic"; - public const string Sample640x427 = "Heic/dwsample-heic-640.heic"; + public const string Image1 = "Heif/image1.heic"; + public const string Image2 = "Heif/image2.heic"; + public const string Image3 = "Heif/image3.heic"; + public const string Image4 = "Heif/image4.heic"; + public const string Sample640x427 = "Heif/dwsample-heic-640.heic"; // Downloaded from: https://github.com/draktable-org/darktable/issues/14473 - public const string FujiFilmHif = "Heic/IMG-20230508-0053.hif"; + public const string FujiFilmHif = "Heif/IMG-20230508-0053.hif"; // Downloaded from: https://github.com/AOMediaCodec/av1-avif/blob/master/testFiles/Microsoft/Irvine_CA.avif - public const string IrvineAvif = "Heic/Irvine_CA.avif"; + public const string IrvineAvif = "Heif/Irvine_CA.avif"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index be3de2a4f8..7788296745 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -4,8 +4,8 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Qoi; @@ -60,7 +60,7 @@ private static Configuration CreateDefaultConfiguration() Configuration cfg = new( new JpegConfigurationModule(), new GifConfigurationModule(), - new HeicConfigurationModule(), + new HeifConfigurationModule(), new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(), diff --git a/tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_IMG-20230508-0053.png similarity index 100% rename from tests/Images/External/ReferenceOutput/HeicDecoderTests/Decode_Rgba32_IMG-20230508-0053.png rename to tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_IMG-20230508-0053.png From 5ad3d671e8ce55b7a0e9d070dbda80f36858771d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 29 Dec 2023 14:37:41 +0100 Subject: [PATCH 030/190] Fix configuration test --- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index c5d61726c8..790ca5d728 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -20,7 +20,7 @@ public class ConfigurationTests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 9; + private readonly int expectedDefaultConfigurationCount = 10; public ConfigurationTests() { From 42ff8f9ff1b0a619ee50e09cebae842ec71a9f0a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 5 Jan 2024 23:44:20 +0100 Subject: [PATCH 031/190] Remove reference to x265 --- src/ImageSharp/Formats/Heif/Readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Readme.md b/src/ImageSharp/Formats/Heif/Readme.md index 330b27fd6a..6c7469528f 100644 --- a/src/ImageSharp/Formats/Heif/Readme.md +++ b/src/ImageSharp/Formats/Heif/Readme.md @@ -4,6 +4,4 @@ [HEIF reference implementation from Nokia](https://github.com/nokiatech/heif) -[Open Source H265 implementation](https://bitbucket.org/multicoreware/x265_git/src) - [Apple's metadata syntax in HEIC images](http://cheeky4n6monkey.blogspot.com/2017/10/monkey-takes-heic.html) From 934dd617a1cf5f731ad7aa4c169444c978dcd330 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 5 Jan 2024 23:57:15 +0100 Subject: [PATCH 032/190] Include SVT reference --- src/ImageSharp/Formats/Heif/Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Formats/Heif/Readme.md b/src/ImageSharp/Formats/Heif/Readme.md index 6c7469528f..5dddf81f10 100644 --- a/src/ImageSharp/Formats/Heif/Readme.md +++ b/src/ImageSharp/Formats/Heif/Readme.md @@ -4,4 +4,6 @@ [HEIF reference implementation from Nokia](https://github.com/nokiatech/heif) +[AVIF reference implementation](http://gitlab.com/AOMediaCodec/SVT-AV1) + [Apple's metadata syntax in HEIC images](http://cheeky4n6monkey.blogspot.com/2017/10/monkey-takes-heic.html) From ccbae050e7f4afae93bd09290c67b0c6cc6e7b01 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 6 Jan 2024 00:12:06 +0100 Subject: [PATCH 033/190] Consistent use of name HEIF --- src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs | 2 +- src/ImageSharp/Formats/Heif/HeifFormat.cs | 4 ++-- .../Heif/{IHeicEncoderOptions.cs => IHeifEncoderOptions.cs} | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/ImageSharp/Formats/Heif/{IHeicEncoderOptions.cs => IHeifEncoderOptions.cs} (85%) diff --git a/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs b/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs index 44d33d2393..18c438f057 100644 --- a/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs +++ b/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Compression algorithms possible inside an HEIF (High Efficiency File Format) based file. +/// Compression algorithms possible inside an HEIF (High Efficiency Image Format) based file. /// public enum HeifCompressionMethod { diff --git a/src/ImageSharp/Formats/Heif/HeifFormat.cs b/src/ImageSharp/Formats/Heif/HeifFormat.cs index 3a6f23ecb6..6a1cdcce3c 100644 --- a/src/ImageSharp/Formats/Heif/HeifFormat.cs +++ b/src/ImageSharp/Formats/Heif/HeifFormat.cs @@ -4,7 +4,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// -/// Registers the image encoders, decoders and mime type detectors for the HEIC format. +/// Registers the image encoders, decoders and mime type detectors for the HEIF format. /// public sealed class HeifFormat : IImageFormat { @@ -18,7 +18,7 @@ private HeifFormat() public static HeifFormat Instance { get; } = new(); /// - public string Name => "HEIC"; + public string Name => "HEIF"; /// public string DefaultMimeType => "image/heif"; diff --git a/src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs b/src/ImageSharp/Formats/Heif/IHeifEncoderOptions.cs similarity index 85% rename from src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs rename to src/ImageSharp/Formats/Heif/IHeifEncoderOptions.cs index c3bde13730..700ccc1e06 100644 --- a/src/ImageSharp/Formats/Heif/IHeicEncoderOptions.cs +++ b/src/ImageSharp/Formats/Heif/IHeifEncoderOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Configuration options for use during HEIF encoding. /// -internal interface IHeicEncoderOptions +internal interface IHeifEncoderOptions { // None defined yet. } From 0301aed66d064efb9303646d7e34171d38e23cf1 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 19 Jan 2024 14:30:31 +0100 Subject: [PATCH 034/190] Decode ICC profile into metadata --- src/ImageSharp/Formats/Heif/Heif4CharCode.cs | 10 ++++++++++ src/ImageSharp/Formats/Heif/Heif4CharCode.tt | 2 ++ src/ImageSharp/Formats/Heif/HeifDecoderCore.cs | 12 +++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index 2b14a62725..d6d31ef1d3 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -248,4 +248,14 @@ public enum Heif4CharCode : uint /// free = 0x66726565U, + /// + /// ICC Color Profile. + /// + rICC = 0x72494343U, + + /// + /// ICC Color Profile. + /// + prof = 0x70726F66U, + } diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt index 6f15a33ab9..9f8555f33b 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -54,6 +54,8 @@ "pict", "Picture handler type", "uuid", "Unique Identifier", "free", "Free space", + "rICC", "ICC Color Profile", + "prof", "ICC Color Profile", }; #> diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index fb81a943c5..8a8c8e29f6 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -442,8 +443,17 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L properties.Add(new KeyValuePair(Heif4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); break; - case Heif4CharCode.altt: case Heif4CharCode.colr: + Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); + if (profileType is Heif4CharCode.rICC or Heif4CharCode.prof) + { + byte[] iccData = new byte[itemLength - 4]; + buffer[4..].CopyTo(iccData); + this.metadata.IccProfile = new IccProfile(iccData); + } + + break; + case Heif4CharCode.altt: case Heif4CharCode.imir: case Heif4CharCode.irot: case Heif4CharCode.iscl: From 5dedc94d9fdea109b57d9d6b2a5340c6f6845d84 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 7 Jan 2024 21:37:09 +0100 Subject: [PATCH 035/190] Initial av1 bitstream parsing --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 150 +++ .../Formats/Heif/Av1/Av1BlockSize.cs | 34 + .../Formats/Heif/Av1/Av1ColorFormat.cs | 12 + .../Formats/Heif/Av1/Av1MainParseContext.cs | 8 + src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 108 +++ src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs | 11 + .../ObuChromoSamplePosition.cs | 22 + .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 52 + .../OpenBitstreamUnit/ObuColorPrimaries.cs | 21 + .../Av1/OpenBitstreamUnit/ObuConstants.cs | 82 ++ ...tDirectionalEnhancementFilterParameters.cs | 13 + .../ObuFilmGrainParameters.cs | 8 + .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 81 ++ .../Av1/OpenBitstreamUnit/ObuFrameSize.cs | 19 + .../Av1/OpenBitstreamUnit/ObuFrameType.cs | 12 + .../Heif/Av1/OpenBitstreamUnit/ObuHeader.cs | 21 + .../ObuLoopFilterParameters.cs | 9 + .../ObuLoopRestorationParameters.cs | 9 + .../ObuMatrixCoefficients.cs | 22 + .../Av1/OpenBitstreamUnit/ObuMetadataType.cs | 13 + .../OpenBitstreamUnit/ObuOperatingPoint.cs | 17 + .../Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs | 13 + .../ObuQuantizationParameters.cs | 19 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 888 ++++++++++++++++++ .../Av1/OpenBitstreamUnit/ObuReferenceMode.cs | 10 + .../OpenBitstreamUnit/ObuRestorationType.cs | 9 + .../ObuSegmentationLevelFeature.cs | 16 + .../ObuSegmentationParameters.cs | 15 + .../OpenBitstreamUnit/ObuSequenceHeader.cs | 71 ++ .../OpenBitstreamUnit/ObuSequenceProfile.cs | 11 + .../Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs | 39 + .../ObuTransferCharacteristics.cs | 25 + .../Heif/Av1/OpenBitstreamUnit/ObuType.cs | 18 + 33 files changed, 1858 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1Math.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs new file mode 100644 index 0000000000..e16553121c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -0,0 +1,150 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal ref struct Av1BitStreamReader(Span data) +{ + private const int WordSize = sizeof(byte); + private const int DoubleWordSize = 2 * WordSize; + private readonly Span data = data; + private int wordPosition = 0; + private int bitOffset = 0; + + public readonly int BitPosition => (this.wordPosition * WordSize) + this.bitOffset; + + public readonly int Length => this.data.Length; + + public void Reset() + { + this.wordPosition = 0; + this.bitOffset = 0; + } + + public void Skip(int bitCount) + { + this.bitOffset += bitCount; + while (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + this.wordPosition++; + } + } + + public uint ReadLiteral(int bitCount) + { + uint bits = (uint)(this.data[this.wordPosition] << this.bitOffset) >> (WordSize - bitCount); + this.bitOffset += bitCount; + while (this.bitOffset > WordSize) + { + uint nextWord = this.data[this.wordPosition + 1]; + bits |= nextWord << (DoubleWordSize - bitCount); + } + + if (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + } + + return bits; + } + + internal bool ReadBoolean() + { + bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset))) > 0; + this.Skip(1); + return bit; + } + + public ulong ReadLittleEndianBytes128(out int length) + { + // See section 4.10.5 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian 128 value only allowed on byte alignment"); + + ulong value = 0; + length = 0; + for (int i = 0; i < 56; i += 7) + { + uint leb128Byte = this.ReadLiteral(8); + value |= (leb128Byte & 0x7FUL) << i; + length++; + if ((leb128Byte & 0x80U) != 0x80U) + { + break; + } + } + + return value; + } + + public uint ReadUnsignedVariableLength() + { + // See section 4.10.3 of the AV1-Specification + int leadingZerosCount = 0; + while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) + { + leadingZerosCount++; + } + + if (leadingZerosCount == 32) + { + return uint.MaxValue; + } + + uint basis = (1U << leadingZerosCount) - 1U; + uint value = this.ReadLiteral(leadingZerosCount); + return basis + value; + } + + public uint ReadNonSymmetric(uint n) + { + // See section 4.10.7 of the AV1-Specification + if (n <= 1) + { + return 0; + } + + int w = (int)(Av1Math.MostSignificantBit(n) + 1); + uint m = (uint)((1 << w) - n); + uint v = this.ReadLiteral(w - 1); + if (v < m) + { + return v; + } + + return (v << 1) - m + this.ReadLiteral(1); + } + + public int ReadSignedFromUnsigned(int n) + { + // See section 4.10.6 of the AV1-Specification + int signedValue; + uint value = this.ReadLiteral(n); + uint signMask = 1U << (n - 1); + if ((value & signMask) == signMask) + { + // Prevent overflow by casting to long; + signedValue = (int)((long)value - (signMask << 1)); + } + else + { + signedValue = (int)value; + } + + return signedValue; + } + + public uint ReadLittleEndian(int n) + { + // See section 4.10.4 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian value only allowed on byte alignment"); + + uint t = 0; + for (int i = 0; i < 8 * n; i += 8) + { + t += this.ReadLiteral(8) << i; + } + + return t; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs new file mode 100644 index 0000000000..6eca5bc638 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1BlockSize +{ + Block4x4, + Block4x8, + Block8x4, + Block8x8, + Block8x16, + Block16x8, + Block16x16, + Block16x32, + Block32x16, + Block32x32, + Block32x64, + Block64x32, + Block64x64, + Block64x128, + Block128x64, + Block128x128, + Block4x16, + Block16x4, + Block8x32, + Block32x8, + Block16x64, + Block64x16, + BlockSizeSAll, + BlockSizeS = Block4x16, + BlockInvalid = 255, + BlockLargest = BlockSizeS - 1, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs new file mode 100644 index 0000000000..07be6a0442 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1ColorFormat.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1ColorFormat +{ + Yuv400, + Yuv420, + Yuv422, + Yuv444, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs b/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs new file mode 100644 index 0000000000..4b13a86cd7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal class Av1MainParseContext +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs new file mode 100644 index 0000000000..c98652e048 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -0,0 +1,108 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1Math +{ + public static uint MostSignificantBit(uint value) => value >> 31; + + public static uint Log2(uint n) + { + uint result = 0U; + while ((n >>= 1) > 0) + { + result++; + } + + return result; + } + + public static int Log2(int n) + { + int result = 0; + while ((n >>= 1) > 0) + { + result++; + } + + return result; + } + + public static uint FloorLog2(uint value) + { + uint s = 0; + while (value != 0U) + { + value >>= 1; + s++; + } + + return s - 1; + } + + public static uint CeilLog2(uint value) + { + if (value < 2) + { + return 0; + } + + uint i = 1; + uint p = 2; + while (p < value) + { + i++; + p <<= 1; + } + + return i; + } + + public static uint Clip1(uint value, int bitDepth) => + Clip3(0, (1U << bitDepth) - 1, value); + + public static uint Clip3(uint x, uint y, uint z) + { + if (z < x) + { + return x; + } + + if (z > y) + { + return y; + } + + return z; + } + + public static uint Round2(uint value, int n) + { + if (n == 0) + { + return value; + } + + return (uint)((value + (1 << (n - 1))) >> n); + } + + public static int Round2(int value, int n) + { + if (value < 0) + { + value = -value; + } + + return (int)Round2((uint)value, n); + } + + internal static int AlignPowerOf2(int value, int n) + { + int mask = (1 << n) - 1; + return (value + mask) & ~mask; + } + + internal static int Clamp(int value, int low, int high) + => value < low ? low : (value > high ? high : value); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs new file mode 100644 index 0000000000..9d73cda42a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Plane.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1Plane : int +{ + Y = 0, + U = 1, + V = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs new file mode 100644 index 0000000000..bd71ea4334 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuChromoSamplePosition.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuChromoSamplePosition : byte +{ + /// + /// Unknown. + /// + Unknown = 0, + + /// + /// Horizontally co-located with luma(0, 0) sample, between two vertical samples. + /// + Vertical = 1, + + /// + /// Co-located with luma(0, 0) sample + /// + Colocated = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs new file mode 100644 index 0000000000..feafbaccd0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -0,0 +1,52 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuColorConfig +{ + internal bool IsColorDescriptionPresent { get; set; } + + internal int ChannelCount { get; set; } + + internal bool Monochrome { get; set; } + + internal ObuColorPrimaries ColorPrimaries { get; set; } + + internal ObuTransferCharacteristics TransferCharacteristics { get; set; } + + internal ObuMatrixCoefficients MatrixCoefficients { get; set; } + + internal bool ColorRange { get; set; } + + internal bool SubSamplingX { get; set; } + + internal bool SubSamplingY { get; set; } + + internal bool HasSeparateUvDelta { get; set; } + + internal ObuChromoSamplePosition ChromaSamplePosition { get; set; } + + internal int BitDepth { get; set; } + + internal bool HasSeparateUvDeltaQ { get; set; } + + public Av1ColorFormat GetColorFormat() + { + Av1ColorFormat format = Av1ColorFormat.Yuv400; + if (this.SubSamplingX && this.SubSamplingY) + { + format = Av1ColorFormat.Yuv420; + } + else if (this.SubSamplingX & !this.SubSamplingY) + { + format = Av1ColorFormat.Yuv422; + } + else if (!this.SubSamplingX && !this.SubSamplingY) + { + format = Av1ColorFormat.Yuv444; + } + + return format; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs new file mode 100644 index 0000000000..3136bba383 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorPrimaries.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuColorPrimaries +{ + None = 0, + Bt709 = 1, + Unspecified = 2, + Bt470M = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + GenericFilm = 8, + Bt2020 = 9, + Xyz = 10, + Smpte431 = 11, + Smpte432 = 12, + Ebu3213 = 22, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs new file mode 100644 index 0000000000..678b49d8b8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -0,0 +1,82 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal static class ObuConstants +{ + public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional; + + public const int LevelBits = -1; + + /// + /// Number of fractional bits for computing position in upscaling. + /// + public const int SuperResolutionScaleBits = 14; + + public const int ScaleNumerator = -1; + + /// + /// Number of reference frames that can be used for inter prediction. + /// + public const int ReferencesPerFrame = 7; + + /// + /// Maximum area of a tile in units of luma samples. + /// + public const int MaxTileArea = 4096 * 2304; + + /// + /// Maximum width of a tile in units of luma samples. + /// + public const int MaxTileWidth = 4096; + + /// + /// Maximum number of tile columns. + /// + public const int MaxTileColumnCount = 64; + + /// + /// Maximum number of tile rows. + /// + public const int MaxTileRowCount = 64; + + /// + /// Number of frames that can be stored for future reference. + /// + public const int ReferenceFrameCount = 8; + + /// + /// Value of 'PrimaryReferenceFrame' indicating that there is no primary reference frame. + /// + public const uint PrimaryReferenceFrameNone = 7; + + public const int PimaryReferenceBits = -1; + + /// + /// Number of segments allowed in segmentation map. + /// + public const int MaxSegmentCount = 8; + + /// + /// Smallest denominator for upscaling ratio. + /// + public const int SuperResolutionScaleDenominatorMinimum = 9; + + /// + /// Base 2 logarithm of maximum size of a superblock in luma samples. + /// + public const int MaxSuperBlockSizeLog2 = 7; + + /// + /// Base 2 logarithm of smallest size of a mode info block. + /// + public const int ModeInfoSizeLog2 = 2; + + public const int MaxQ = 255; + + /// + /// Number of segmentation features. + /// + public const int SegmentationLevelMax = 8; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs new file mode 100644 index 0000000000..57e54a6450 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuConstraintDirectionalEnhancementFilterParameters +{ + public bool BitCount { get; internal set; } + + public int[] YStrength { get; internal set; } + + public int[] UVStrength { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs new file mode 100644 index 0000000000..6b74c979dd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFilmGrainParameters +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs new file mode 100644 index 0000000000..03870ca56c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFrameHeader +{ + public bool ForceIntegerMotionVector { get; set; } + + public bool AllowIntraBlockCopy { get; set; } + + public bool UseReferenceFrameMotionVectors { get; set; } + + public bool AllowHighPrecisionMotionVector { get; set; } + + public ObuTileInfo TilesInfo { get; internal set; } = new ObuTileInfo(); + + public bool CodedLossless { get; internal set; } + + public bool[] LosslessArray { get; internal set; } = new bool[ObuConstants.MaxSegmentCount]; + + public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters(); + + public ObuSegmentationParameters SegmentationParameters { get; set; } = new ObuSegmentationParameters(); + + public bool AllLossless { get; internal set; } + + public bool AllowWarpedMotion { get; internal set; } + + public ObuReferenceMode ReferenceMode { get; internal set; } + + public ObuFilmGrainParameters FilmGrainParameters { get; internal set; } = new ObuFilmGrainParameters(); + + public bool ReducedTxSet { get; internal set; } + + public ObuLoopFilterParameters LoopFilterParameters { get; internal set; } = new ObuLoopFilterParameters(); + + public ObuLoopRestorationParameters[] LoopRestorationParameters { get; internal set; } = new ObuLoopRestorationParameters[3]; + + public ObuConstraintDirectionalEnhancementFilterParameters ConstraintDirectionalEnhancementFilterParameters { get; internal set; } = new ObuConstraintDirectionalEnhancementFilterParameters(); + + public int ModeInfoStride { get; internal set; } + + public bool DisableFrameEndUpdateCdf { get; internal set; } + + internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize(); + + internal int ModeInfoColumnCount { get; set; } + + internal int ModeInfoRowCount { get; set; } + + internal bool ShowExistingFrame { get; set; } + + internal ObuFrameType FrameType { get; set; } + + internal bool[] ReferenceValid { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + + internal bool[] ReferenceOrderHint { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + + internal bool ShowFrame { get; set; } + + internal bool ShowableFrame { get; set; } + + internal bool ErrorResilientMode { get; set; } + + internal bool AllowScreenContentTools { get; set; } + + internal bool DisableCdfUpdate { get; set; } + + internal bool ForeceIntegerMotionVector { get; set; } + + internal uint CurrentFrameId { get; set; } + + internal uint[] ReferenceFrameIndex { get; set; } = new uint[ObuConstants.ReferenceFrameCount]; + + internal uint OrderHint { get; set; } + + internal uint PrimaryReferenceFrame { get; set; } = ObuConstants.PrimaryReferenceFrameNone; + + internal uint RefreshFrameFlags { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs new file mode 100644 index 0000000000..7075b50b82 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameSize.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuFrameSize +{ + internal int FrameWidth { get; set; } + + internal int FrameHeight { get; set; } + + internal int SuperResolutionDenominator { get; set; } + + internal int SuperResolutionUpscaledWidth { get; set; } + + internal int RenderWidth { get; set; } + + internal int RenderHeight { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs new file mode 100644 index 0000000000..eb2414edc7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameType.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuFrameType +{ + KeyFrame = 0, + InterFrame = 1, + IntraOnlyFrame = 2, + SwitchFrame = 3, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs new file mode 100644 index 0000000000..f55d3eb501 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuHeader.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuHeader +{ + public int Size { get; set; } + + public ObuType Type { get; set; } + + public bool HasSize { get; set; } + + public bool HasExtension { get; set; } + + public int TemporalId { get; set; } + + public int SpatialId { get; set; } + + public int PayloadSize { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs new file mode 100644 index 0000000000..961f3b2bc6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuLoopFilterParameters +{ + public bool[] FilterLevel { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs new file mode 100644 index 0000000000..1c04072e3e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuLoopRestorationParameters +{ + public ObuRestorationType FrameRestorationType { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs new file mode 100644 index 0000000000..e9658997fb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMatrixCoefficients.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuMatrixCoefficients +{ + Identity = 0, + Bt407 = 1, + Unspecified = 2, + Fcc = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + SmpteYCgCo = 8, + Bt2020NonConstantLuminance = 9, + Bt2020ConstantLuminance = 10, + Smpte2085 = 11, + ChromaticityDerivedNonConstantLuminance = 12, + ChromaticityDerivedConstandLuminance = 13, + Bt2100ICtCp = 14, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs new file mode 100644 index 0000000000..d6788f1745 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuMetadataType.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuMetadataType +{ + ItutT35, + HdrCll, + HdrMdcv, + Scalability, + Timecode +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs new file mode 100644 index 0000000000..a1401b1aa8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuOperatingPoint +{ + internal int OperatorIndex { get; set; } + + internal int SequenceLevelIndex { get; set; } + + internal int SequenceTier { get; set; } + + internal bool IsDecoderModelPresent { get; set; } + + internal bool IsInitialDisplayDelayPresent { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs new file mode 100644 index 0000000000..4cdcb26550 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuOrderHintInfo +{ + internal bool EnableJointCompound { get; set; } + + internal bool EnableReferenceFrameMotionVectors { get; set; } + + internal int OrderHintBits { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs new file mode 100644 index 0000000000..53aa1c88bb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuQuantizationParameters +{ + public int BaseQIndex { get; set; } + + public int[] QIndex { get; set; } = new int[ObuConstants.MaxSegmentCount]; + + public bool IsUsingQMatrix { get; internal set; } + + public int[] DeltaQDc { get; internal set; } = new int[3]; + + public int[] DeltaQAc { get; internal set; } = new int[3]; + + public int[] QMatrix { get; internal set; } = new int[3]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs new file mode 100644 index 0000000000..c1676febcb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -0,0 +1,888 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuReader +{ + private static readonly int[] WienerTapsMid = new[] { 3, -7, 15 }; + private static readonly int[] SgrprojXqdMid = new[] { -32, 31 }; + + private static ObuHeader ReadObuHeader(Av1BitStreamReader reader) + { + ObuHeader header = new(); + if (!reader.ReadBoolean()) + { + throw new ImageFormatException("Forbidden bit in header should be unset."); + } + + header.Type = (ObuType)reader.ReadLiteral(4); + header.HasExtension = reader.ReadBoolean(); + header.HasSize = reader.ReadBoolean(); + if (!reader.ReadBoolean()) + { + throw new ImageFormatException("Reserved bit in header should be unset."); + } + + if (header.HasExtension) + { + header.Size++; + header.TemporalId = (int)reader.ReadLiteral(3); + header.SpatialId = (int)reader.ReadLiteral(3); + if (reader.ReadLiteral(3) != 0u) + { + throw new ImageFormatException("Reserved bits in header extension should be unset."); + } + } + else + { + header.SpatialId = 0; + header.TemporalId = 0; + } + + return header; + } + + private static void ReadObuSize(Av1BitStreamReader reader, out int obuSize) + { + ulong rawSize = reader.ReadLittleEndianBytes128(out _); + if (rawSize > uint.MaxValue) + { + throw new ImageFormatException("OBU block too large."); + } + + obuSize = (int)rawSize; + } + + /// + /// Read OBU header and size. + /// + private static ObuHeader ReadObuHeaderSize(Av1BitStreamReader reader) + { + ObuHeader header = ReadObuHeader(reader); + if (header.HasSize) + { + ReadObuSize(reader, out int payloadSize); + header.PayloadSize = payloadSize; + } + + return header; + } + + /// + /// Check that the trailing bits start with a 1 and end with 0s. + /// + /// Consumes a byte, if already byte aligned before the check. + private static void ReadTrailingBits(Av1BitStreamReader reader) + { + int bitsBeforeAlignment = 8 - (reader.BitPosition & 0x7); + uint trailing = reader.ReadLiteral(bitsBeforeAlignment); + if (trailing != (1 << (bitsBeforeAlignment - 1))) + { + throw new ImageFormatException("Trailing bits not properly formatted."); + } + } + + private static void AlignToByteBoundary(Av1BitStreamReader reader) + { + while ((reader.BitPosition & 0x7) > 0) + { + if (reader.ReadBoolean()) + { + throw new ImageFormatException("Incorrect byte alignment padding bits."); + } + } + } + + private static void ComputeImageSize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + frameInfo.ModeInfoColumnCount = 2 * ((frameInfo.FrameSize.FrameWidth + 7) >> 3); + frameInfo.ModeInfoRowCount = 2 * ((frameInfo.FrameSize.FrameHeight + 7) >> 3); + frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, ObuConstants.MaxSuperBlockSizeLog2) >> ObuConstants.ModeInfoSizeLog2; + } + + private static bool IsValidObuType(ObuType type) => type switch + { + ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or + ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or + ObuType.TileList or ObuType.Padding => true, + _ => false, + }; + + private static ObuSequenceHeader ReadSequenceHeader(Av1BitStreamReader reader) + { + ObuSequenceHeader sequenceHeader = new(); + sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); + if (sequenceHeader.SequenceProfile > ObuConstants.MaxSequenceProfile) + { + throw new ImageFormatException("Unknown sequence profile."); + } + + sequenceHeader.IsStillPicture = reader.ReadBoolean(); + sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean(); + if (!sequenceHeader.IsStillPicture || !sequenceHeader.IsReducedStillPictureHeader) + { + throw new ImageFormatException("Not a picture header, is this a movie file ??"); + } + + sequenceHeader.TimingInfo = null; + sequenceHeader.DecoderModelInfoPresentFlag = false; + sequenceHeader.InitialDisplayDelayPresentFlag = false; + sequenceHeader.OperatingPoint[0].OperatorIndex = 0; + sequenceHeader.OperatingPoint[0].SequenceLevelIndex = (int)reader.ReadLiteral(ObuConstants.LevelBits); + if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) + { + throw new ImageFormatException("Unknown sequnce level."); + } + + sequenceHeader.OperatingPoint[0].SequenceTier = 0; + sequenceHeader.OperatingPoint[0].IsDecoderModelPresent = false; + sequenceHeader.OperatingPoint[0].IsInitialDisplayDelayPresent = false; + + // Video related flags removed + + // SVT-TODO: int operatingPoint = this.ChooseOperatingPoint(); + // sequenceHeader.OperatingPointIndex = (int)operatingPointIndices[operatingPoint]; + sequenceHeader.FrameWidthBits = (int)reader.ReadLiteral(4) + 1; + sequenceHeader.FrameHeightBits = (int)reader.ReadLiteral(4) + 1; + sequenceHeader.MaxFrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; + sequenceHeader.MaxFrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; + sequenceHeader.IsFrameIdNumbersPresent = false; + + // Video related flags removed + sequenceHeader.Use128x128SuperBlock = reader.ReadBoolean(); + sequenceHeader.SuperBlockSize = sequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + sequenceHeader.ModeInfoSize = sequenceHeader.Use128x128SuperBlock ? 32 : 16; + sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6; + sequenceHeader.FilterIntraLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); + sequenceHeader.EnableInterIntraCompound = false; + sequenceHeader.EnableMaskedCompound = false; + sequenceHeader.EnableWarpedMotion = false; + sequenceHeader.EnableDualFilter = false; + sequenceHeader.OrderHintInfo.EnableJointCompound = false; + sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; + sequenceHeader.SequenceForceScreenContentTools = 2; + sequenceHeader.SequenceForceIntegerMotionVector = 2; + sequenceHeader.OrderHintInfo.OrderHintBits = 0; + + // Video related flags removed + sequenceHeader.EnableSuperResolution = reader.ReadBoolean(); + sequenceHeader.CdefLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableRestoration = reader.ReadBoolean(); + sequenceHeader.ColorConfig = ReadColorConfig(reader, sequenceHeader); + sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean(); + ReadTrailingBits(reader); + return sequenceHeader; + } + + private static ObuColorConfig ReadColorConfig(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) + { + ObuColorConfig colorConfig = new(); + ReadBitDepth(reader, colorConfig, sequenceHeader); + colorConfig.Monochrome = false; + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.High) + { + colorConfig.Monochrome = reader.ReadBoolean(); + } + + colorConfig.ChannelCount = colorConfig.Monochrome ? 1 : 3; + colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); + colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; + colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; + colorConfig.MatrixCoefficients = ObuMatrixCoefficients.Unspecified; + if (colorConfig.IsColorDescriptionPresent) + { + colorConfig.ColorPrimaries = (ObuColorPrimaries)reader.ReadLiteral(8); + colorConfig.TransferCharacteristics = (ObuTransferCharacteristics)reader.ReadLiteral(8); + colorConfig.MatrixCoefficients = (ObuMatrixCoefficients)reader.ReadLiteral(8); + } + + colorConfig.ColorRange = false; + colorConfig.SubSamplingX = false; + colorConfig.SubSamplingY = false; + colorConfig.ChromaSamplePosition = ObuChromoSamplePosition.Unknown; + colorConfig.HasSeparateUvDelta = false; + if (colorConfig.Monochrome) + { + colorConfig.ColorRange = reader.ReadBoolean(); + colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = true; + return colorConfig; + } + else if ( + colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 && + colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb && + colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity) + { + colorConfig.ColorRange = true; + colorConfig.SubSamplingX = false; + colorConfig.SubSamplingY = false; + } + else + { + colorConfig.ColorRange = reader.ReadBoolean(); + if (sequenceHeader.SequenceProfile != ObuSequenceProfile.Main) + { + if (colorConfig.BitDepth == 12) + { + colorConfig.SubSamplingX = reader.ReadBoolean(); + if (colorConfig.SubSamplingX) + { + colorConfig.SubSamplingY = reader.ReadBoolean(); + } + } + else + { + colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = false; + } + } + + if (colorConfig.SubSamplingX && colorConfig.SubSamplingY) + { + colorConfig.ChromaSamplePosition = (ObuChromoSamplePosition)reader.ReadLiteral(2); + } + } + + colorConfig.HasSeparateUvDeltaQ = reader.ReadBoolean(); + return colorConfig; + } + + private static void ReadBitDepth(Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) + { + bool hasHighBitDepth = reader.ReadBoolean(); + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) + { + colorConfig.BitDepth = reader.ReadBoolean() ? 12 : 10; + } + else if (sequenceHeader.SequenceProfile <= ObuSequenceProfile.Professional) + { + colorConfig.BitDepth = hasHighBitDepth ? 10 : 8; + } + } + + private static void ReadSuperResolutionParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + bool useSuperResolution = false; + if (sequenceHeader.EnableSuperResolution) + { + useSuperResolution = reader.ReadBoolean(); + } + + if (useSuperResolution) + { + frameInfo.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(ObuConstants.SuperResolutionScaleBits) + ObuConstants.SuperResolutionScaleDenominatorMinimum; + } + else + { + frameInfo.FrameSize.SuperResolutionDenominator = ObuConstants.ScaleNumerator; + } + + frameInfo.FrameSize.SuperResolutionUpscaledWidth = frameInfo.FrameSize.FrameWidth; + frameInfo.FrameSize.FrameWidth = + (frameInfo.FrameSize.SuperResolutionUpscaledWidth * ObuConstants.ScaleNumerator) + + (frameInfo.FrameSize.SuperResolutionDenominator / 2); + + if (frameInfo.FrameSize.SuperResolutionDenominator != ObuConstants.ScaleNumerator) + { + int manWidth = Math.Min(16, frameInfo.FrameSize.SuperResolutionUpscaledWidth); + frameInfo.FrameSize.FrameWidth = Math.Max(manWidth, frameInfo.FrameSize.FrameWidth); + } + } + + private static void ReadRenderSize(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + { + bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); + if (renderSizeAndFrameSizeDifferent) + { + frameInfo.FrameSize.RenderWidth = (int)reader.ReadLiteral(16) + 1; + frameInfo.FrameSize.RenderHeight = (int)reader.ReadLiteral(16) + 1; + } + else + { + frameInfo.FrameSize.RenderWidth = frameInfo.FrameSize.SuperResolutionUpscaledWidth; + frameInfo.FrameSize.RenderHeight = frameInfo.FrameSize.FrameHeight; + } + } + + private static void ReadFrameSizeWithReferences(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + bool foundReference = false; + for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) + { + foundReference = reader.ReadBoolean(); + if (foundReference) + { + // Take values over from reference frame + break; + } + } + + if (!foundReference) + { + ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(reader, frameInfo); + } + else + { + ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ComputeImageSize(sequenceHeader, frameInfo); + } + } + + private static void ReadFrameSize(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + if (frameSizeOverrideFlag) + { + frameInfo.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; + frameInfo.FrameSize.FrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; + } + else + { + frameInfo.FrameSize.FrameWidth = sequenceHeader.MaxFrameWidth; + frameInfo.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; + } + + ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ComputeImageSize(sequenceHeader, frameInfo); + } + + private static ObuTileInfo ReadTileInfo(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + ObuTileInfo tileInfo = new(); + int superBlockColumnCount; + int superBlockRowCount; + int superBlockShift; + if (sequenceHeader.Use128x128SuperBlock) + { + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; + superBlockShift = 5; + } + else + { + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4; + superBlockShift = 4; + } + + int superBlockSize = superBlockShift + 2; + int maxTileAreaOfSuperBlock = ObuConstants.MaxTileArea >> (2 * superBlockSize); + + tileInfo.MaxTileWidthSuperBlock = ObuConstants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperBlock = (ObuConstants.MaxTileArea / ObuConstants.MaxTileWidth) >> superBlockSize; + tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); + tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount)); + tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); + tileInfo.HasUniformTileSpacing = reader.ReadBoolean(); + if (tileInfo.HasUniformTileSpacing) + { + tileInfo.TileColumnCountLog2 = tileInfo.MinLog2TileColumnCount; + while (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount) + { + if (reader.ReadBoolean()) + { + tileInfo.TileColumnCountLog2++; + } + else + { + break; + } + } + + int tileWidthSuperBlock = (superBlockColumnCount + (1 << tileInfo.TileColumnCountLog2) - 1) >> tileInfo.TileColumnCountLog2; + if (tileWidthSuperBlock > tileInfo.MaxTileWidthSuperBlock) + { + throw new ImageFormatException("Invalid tile width specified."); + } + + int i = 0; + tileInfo.TileColumnStartModeInfo = new int[superBlockColumnCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superBlockColumnCount; startSuperBlock += tileWidthSuperBlock) + { + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; + i++; + } + + tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnCount = i; + + tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - tileInfo.TileColumnCountLog2, 0); + tileInfo.TileRowCountLog2 = tileInfo.MinLog2TileRowCount; + while (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount) + { + if (reader.ReadBoolean()) + { + tileInfo.TileRowCountLog2++; + } + else + { + break; + } + } + + int tileHeightSuperBlock = (superBlockRowCount + (1 << tileInfo.TileRowCountLog2) - 1) >> tileInfo.TileRowCountLog2; + if (tileHeightSuperBlock > tileInfo.MaxTileHeightSuperBlock) + { + throw new ImageFormatException("Invalid tile height specified."); + } + + i = 0; + tileInfo.TileRowStartModeInfo = new int[superBlockRowCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superBlockRowCount; startSuperBlock += tileHeightSuperBlock) + { + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; + i++; + } + + tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowCount = i; + } + else + { + uint widestTileSuperBlock = 0U; + int startSuperBlock = 0; + int i = 0; + for (; startSuperBlock < superBlockColumnCount; i++) + { + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; + uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + uint widthInSuperBlocks = reader.ReadNonSymmetric(maxWidth) + 1; + widestTileSuperBlock = Math.Max(widthInSuperBlocks, widestTileSuperBlock); + startSuperBlock += (int)widthInSuperBlocks; + } + + if (startSuperBlock != superBlockColumnCount) + { + throw new ImageFormatException("Super block tiles width does not add up to total width."); + } + + tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnCount = i; + tileInfo.TileColumnCountLog2 = TileLog2(1, tileInfo.TileColumnCount); + if (tileInfo.MinLog2TileCount > 0) + { + maxTileAreaOfSuperBlock = (superBlockRowCount * superBlockColumnCount) >> (tileInfo.MinLog2TileCount + 1); + } + else + { + maxTileAreaOfSuperBlock = superBlockRowCount * superBlockColumnCount; + } + + DebugGuard.MustBeGreaterThan(widestTileSuperBlock, 0U, nameof(widestTileSuperBlock)); + tileInfo.MaxTileHeightSuperBlock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1); + + startSuperBlock = 0; + for (i = 0; startSuperBlock < superBlockRowCount; i++) + { + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; + uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + uint heightInSuperBlocks = reader.ReadNonSymmetric(maxHeight) + 1; + startSuperBlock += (int)heightInSuperBlocks; + } + + if (startSuperBlock != superBlockRowCount) + { + throw new ImageFormatException("Super block tiles height does not add up to total height."); + } + + tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowCount = i; + tileInfo.TileRowCountLog2 = TileLog2(1, tileInfo.TileRowCount); + } + + if (tileInfo.TileColumnCount > ObuConstants.MaxTileColumnCount || tileInfo.TileRowCount > ObuConstants.MaxTileRowCount) + { + throw new ImageFormatException("Tile width or height too big."); + } + + if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0) + { + tileInfo.ContextUpdateTileId = reader.ReadLiteral(tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2); + tileInfo.TileSizeBytes = (int)reader.ReadLiteral(2) + 1; + } + else + { + tileInfo.ContextUpdateTileId = 0; + } + + if (tileInfo.ContextUpdateTileId >= (tileInfo.TileColumnCount * tileInfo.TileRowCount)) + { + throw new ImageFormatException("Context update Tile ID too large."); + } + + return tileInfo; + } + + private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHeader header, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + int idLength = 0; + uint previousFrameId = 0; + bool isIntraFrame = false; + bool frameSizeOverrideFlag = false; + if (sequenceHeader.IsFrameIdNumbersPresent) + { + idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3; + DebugGuard.MustBeLessThanOrEqualTo(idLength, 16, nameof(idLength)); + } + + if (sequenceHeader.IsReducedStillPictureHeader) + { + frameInfo.ShowExistingFrame = false; + frameInfo.FrameType = ObuFrameType.KeyFrame; + isIntraFrame = true; + frameInfo.ShowFrame = true; + frameInfo.ShowableFrame = false; + frameInfo.ErrorResilientMode = true; + } + + if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) + { + frameInfo.ReferenceValid = new bool[ObuConstants.ReferenceFrameCount]; + frameInfo.ReferenceOrderHint = new bool[ObuConstants.ReferenceFrameCount]; + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + frameInfo.ReferenceValid[i] = false; + frameInfo.ReferenceOrderHint[i] = false; + } + } + + frameInfo.DisableCdfUpdate = reader.ReadBoolean(); + frameInfo.AllowScreenContentTools = sequenceHeader.SequenceForceScreenContentTools == 1; + if (frameInfo.AllowScreenContentTools) + { + frameInfo.AllowScreenContentTools = reader.ReadBoolean(); + } + + if (frameInfo.AllowScreenContentTools) + { + if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + { + frameInfo.ForeceIntegerMotionVector = reader.ReadBoolean(); + } + else + { + frameInfo.ForceIntegerMotionVector = sequenceHeader.SequenceForceIntegerMotionVector != 0; + } + } + else + { + frameInfo.ForeceIntegerMotionVector = false; + } + + if (isIntraFrame) + { + frameInfo.ForeceIntegerMotionVector = true; + } + + bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); + if (havePreviousFrameId) + { + previousFrameId = frameInfo.CurrentFrameId; + } + + if (sequenceHeader.IsFrameIdNumbersPresent) + { + frameInfo.CurrentFrameId = reader.ReadLiteral(idLength); + if (havePreviousFrameId) + { + uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? + frameInfo.CurrentFrameId - previousFrameId : + (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); + if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) + { + throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); + } + } + + int diffLength = sequenceHeader.DeltaFrameIdLength; + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + if (frameInfo.CurrentFrameId > (1U << diffLength)) + { + if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || + frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) + { + frameInfo.ReferenceValid[i] = false; + } + } + else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && + frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength)))) + { + frameInfo.ReferenceValid[i] = false; + } + } + } + else + { + frameInfo.CurrentFrameId = 0; + } + + if (frameInfo.FrameType == ObuFrameType.SwitchFrame) + { + frameSizeOverrideFlag = true; + } + else if (sequenceHeader.IsReducedStillPictureHeader) + { + frameSizeOverrideFlag = false; + } + else + { + frameSizeOverrideFlag = reader.ReadBoolean(); + } + + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + if (isIntraFrame || frameInfo.ErrorResilientMode) + { + frameInfo.PrimaryReferenceFrame = ObuConstants.PrimaryReferenceFrameNone; + } + else + { + frameInfo.PrimaryReferenceFrame = reader.ReadLiteral(ObuConstants.PimaryReferenceBits); + } + + // Skipping, as no decoder info model present + frameInfo.AllowHighPrecisionMotionVector = false; + frameInfo.UseReferenceFrameMotionVectors = false; + frameInfo.AllowIntraBlockCopy = false; + if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + { + frameInfo.RefreshFrameFlags = 0xffU; + } + else + { + frameInfo.RefreshFrameFlags = reader.ReadLiteral(8); + } + + if (frameInfo.FrameType == ObuFrameType.IntraOnlyFrame) + { + DebugGuard.IsTrue(frameInfo.RefreshFrameFlags != 0xFFU, nameof(frameInfo.RefreshFrameFlags)); + } + + if (!isIntraFrame || (frameInfo.RefreshFrameFlags != 0xFFU)) + { + if (frameInfo.ErrorResilientMode && sequenceHeader.OrderHintInfo != null) + { + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + int referenceOrderHint = (int)reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + if (referenceOrderHint != (frameInfo.ReferenceOrderHint[i] ? 1U : 0U)) + { + frameInfo.ReferenceValid[i] = false; + } + } + } + } + + if (isIntraFrame) + { + ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(reader, frameInfo); + if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) + { + if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) + { + frameInfo.AllowIntraBlockCopy = reader.ReadBoolean(); + } + } + } + else + { + // Single image is always Intra. + } + + SetupFrameBufferReferences(sequenceHeader, frameInfo); + CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); + SetupFrameSignBias(sequenceHeader, frameInfo); + + if (sequenceHeader.IsReducedStillPictureHeader || frameInfo.DisableCdfUpdate) + { + frameInfo.DisableFrameEndUpdateCdf = true; + } + + if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + { + SetupPastIndependence(); + } + + GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); + + frameInfo.TilesInfo = ReadTileInfo(reader, sequenceHeader, frameInfo); + frameInfo.QuantizationParameters = ReadQuantizationParameters(reader, sequenceHeader.ColorConfig, planesCount); + ReadSegmentationParameters(reader, sequenceHeader, frameInfo); + ReadDeltaQParameters(reader, frameInfo); + ReadDeltaLoopFilterParameters(reader, frameInfo); + SetupSegmentationDequantization(); + + Av1MainParseContext mainParseContext = new(); + if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + { + ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); + } + + int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; + frameInfo.CodedLossless = true; + for (int segmentId = 0; segmentId < ObuConstants.MaxSegmentCount; segmentId++) + { + int qIndex = GetQIndex(frameInfo.SegmentationParameters, segmentId, frameInfo.QuantizationParameters.BaseQIndex); + frameInfo.QuantizationParameters.QIndex[segmentId] = qIndex; + frameInfo.LosslessArray[segmentId] = qIndex == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && + frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 && + frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 && + frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0; + if (!frameInfo.LosslessArray[segmentId]) + { + frameInfo.CodedLossless = false; + } + + if (frameInfo.QuantizationParameters.IsUsingQMatrix) + { + if (frameInfo.LosslessArray[segmentId]) + { + frameInfo.SegmentationParameters.QMLevel[0, segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[1, segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[2, segmentId] = 15; + } + else + { + frameInfo.SegmentationParameters.QMLevel[0, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; + frameInfo.SegmentationParameters.QMLevel[1, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.U]; + frameInfo.SegmentationParameters.QMLevel[2, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.V]; + } + } + } + + frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; + ReadLoopFilterParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadConstraintDirectionalEnhancementFilterParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadLoopRestorationParameter(reader, sequenceHeader, frameInfo, planesCount); + ReadTransformMode(reader, frameInfo); + + frameInfo.ReferenceMode = ReadFrameReferenceMode(reader, isIntraFrame); + ReadSkipModeParameters(reader, frameInfo, isIntraFrame, sequenceHeader, frameInfo.ReferenceMode); + if (isIntraFrame || frameInfo.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) + { + frameInfo.AllowWarpedMotion = false; + } + + frameInfo.ReducedTxSet = reader.ReadBoolean(); + ReadGlobalMotionParameters(reader, sequenceHeader, frameInfo, isIntraFrame); + frameInfo.FilmGrainParameters = ReadFilmGrainParameters(reader, sequenceHeader, frameInfo); + } + + private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) + => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + + private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) + { + if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) + { + int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; + int qIndex = baseQIndex + data; + return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ); + } + else + { + return baseQIndex; + } + } + + private static void ReadFrameHeader(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuHeader header, bool trailingBit) + { + int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int startBitPosition = reader.BitPosition; + ReadUncompressedFrameHeader(reader, header, sequenceHeader, frameInfo, planeCount); + if (trailingBit) + { + ReadTrailingBits(reader); + } + + AlignToByteBoundary(reader); + + int endPosition = reader.BitPosition; + int headerBytes = (endPosition - startBitPosition) / 8; + header.PayloadSize -= headerBytes; + } + + private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo, ObuHeader header, out bool isLastTileGroup) + { + int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; + int startBitPosition = reader.BitPosition; + bool tileStartAndEndPresentFlag = false; + if (tileCount > 1) + { + tileStartAndEndPresentFlag = reader.ReadBoolean(); + } + + if (header.Type == ObuType.FrameHeader) + { + DebugGuard.IsFalse(tileStartAndEndPresentFlag, nameof(tileStartAndEndPresentFlag), "Frame header should not set 'tileStartAndEndPresentFlag'."); + } + + int tileGroupStart = 0; + int tileGroupEnd = tileCount - 1; + if (tileCount != 1 && tileStartAndEndPresentFlag) + { + int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount); + tileGroupStart = (int)reader.ReadLiteral(tileBits); + tileGroupEnd = (int)reader.ReadLiteral(tileBits); + } + + isLastTileGroup = (tileGroupEnd + 1) == tileCount; + AlignToByteBoundary(reader); + int endBitPosition = reader.BitPosition; + int headerBytes = (endBitPosition - startBitPosition) / 8; + header.PayloadSize -= headerBytes; + + bool noIbc = !frameInfo.AllowIntraBlockCopy; + bool doLoopFilter = noIbc && (frameInfo.LoopFilterParameters.FilterLevel[0] || frameInfo.LoopFilterParameters.FilterLevel[1]); + bool doCdef = noIbc && (!frameInfo.CodedLossless && + (frameInfo.ConstraintDirectionalEnhancementFilterParameters.BitCount || + frameInfo.ConstraintDirectionalEnhancementFilterParameters.YStrength[0] != 0 || + frameInfo.ConstraintDirectionalEnhancementFilterParameters.UVStrength[0] != 0)); + bool doLoopRestoration = noIbc && + (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].FrameRestorationType != ObuRestorationType.RestoreNone || + frameInfo.LoopRestorationParameters[(int)Av1Plane.U].FrameRestorationType != ObuRestorationType.RestoreNone || + frameInfo.LoopRestorationParameters[(int)Av1Plane.V].FrameRestorationType != ObuRestorationType.RestoreNone); + + for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) + { + int tileRow = tileNum / tileInfo.TileColumnCount; + int tileColumn = tileNum % tileInfo.TileColumnCount; + bool isLastTile = tileNum == tileGroupEnd; + int tileSize = header.PayloadSize; + if (!isLastTile) + { + tileSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; + header.PayloadSize -= tileSize + tileInfo.TileSizeBytes; + } + + // TODO: Pass more info to the decoder. + DecodeTile(sequenceHeader, frameInfo, tileInfo, tileNum); + } + + if (tileGroupEnd != tileCount - 1) + { + return; + } + + FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); + } + + private static bool IsValidSequenceLevel(int sequenceLevelIndex) + => sequenceLevelIndex is < 24 or 31; + + private static int TileLog2(int blockSize, int target) + { + int k; + for (k = 0; (blockSize << k) < target; k++) + { + } + + return k; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs new file mode 100644 index 0000000000..a76bc7601e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReferenceMode.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuReferenceMode +{ + ReferenceModeSelect, + SingleReference, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs new file mode 100644 index 0000000000..02ab7cd1e4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs @@ -0,0 +1,9 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuRestorationType +{ + RestoreNone +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs new file mode 100644 index 0000000000..93af8e0a1c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationLevelFeature.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSegmentationLevelFeature +{ + AlternativeQuantizer, + AlternativeLoopFilterYVertical, + AlternativeLoopFilterYHorizontal, + AlternativeLoopFilterU, + AlternativeLoopFilterV, + ReferenceFrame, + Skip, + GlobalMotionVector, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs new file mode 100644 index 0000000000..ed92abf4c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuSegmentationParameters +{ + public int[,] QMLevel { get; internal set; } = new int[3, ObuConstants.MaxSegmentCount]; + + public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + + public bool SegmentationEnabled { get; internal set; } + + public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs new file mode 100644 index 0000000000..652734c01b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -0,0 +1,71 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuSequenceHeader +{ + internal bool IsStillPicture { get; set; } + + internal bool IsReducedStillPictureHeader { get; set; } + + internal ObuSequenceProfile SequenceProfile { get; set; } + + internal ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1]; + + internal bool InitialDisplayDelayPresentFlag { get; set; } + + internal bool DecoderModelInfoPresentFlag { get; set; } + + internal object? TimingInfo { get; set; } + + internal bool IsFrameIdNumbersPresent { get; set; } + + internal int FrameWidthBits { get; set; } + + internal int FrameHeightBits { get; set; } + + internal int MaxFrameWidth { get; set; } + + internal int MaxFrameHeight { get; set; } + + internal bool Use128x128SuperBlock { get; set; } + + internal Av1BlockSize SuperBlockSize { get; set; } + + internal int ModeInfoSize { get; set; } + + internal int SuperBlockSizeLog2 { get; set; } + + internal int FilterIntraLevel { get; set; } + + internal bool EnableIntraEdgeFilter { get; set; } + + internal ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo(); + + internal bool EnableInterIntraCompound { get; set; } + + internal bool EnableMaskedCompound { get; set; } + + internal bool EnableWarpedMotion { get; set; } + + internal bool EnableDualFilter { get; set; } + + internal int SequenceForceIntegerMotionVector { get; set; } + + internal int SequenceForceScreenContentTools { get; set; } + + internal bool EnableSuperResolution { get; set; } + + internal int CdefLevel { get; set; } + + internal bool EnableRestoration { get; set; } + + internal ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig(); + + internal bool AreFilmGrainingParametersPresent { get; set; } + + internal int FrameIdLength { get; set; } + + internal int DeltaFrameIdLength { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs new file mode 100644 index 0000000000..f1ac85edb3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceProfile.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSequenceProfile : uint +{ + Main = 0, + High = 1, + Professional = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs new file mode 100644 index 0000000000..24c8f963ea --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuTileInfo +{ + internal int MaxTileWidthSuperBlock { get; set; } + + internal int MaxTileHeightSuperBlock { get; set; } + + internal int MinLog2TileColumnCount { get; set; } + + internal int MaxLog2TileColumnCount { get; set; } + + internal int MaxLog2TileRowCount { get; set; } + + internal int MinLog2TileCount { get; set; } + + public bool HasUniformTileSpacing { get; set; } + + internal int TileColumnCountLog2 { get; set; } + + internal int TileColumnCount { get; set; } + + internal int[] TileColumnStartModeInfo { get; set; } + + internal int MinLog2TileRowCount { get; set; } + + internal int TileRowCountLog2 { get; set; } + + internal int[] TileRowStartModeInfo { get; set; } + + internal int TileRowCount { get; set; } + + internal uint ContextUpdateTileId { get; set; } + + internal int TileSizeBytes { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs new file mode 100644 index 0000000000..0fa71e5922 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTransferCharacteristics.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuTransferCharacteristics +{ + Bt709 = 1, + Unspecified = 2, + Bt470M = 4, + Bt470BG = 5, + Bt601 = 6, + Smpte240 = 7, + Linear = 8, + Log100 = 9, + Log100Sqrt10 = 10, + Iec61966 = 11, + Bt1361 = 12, + Srgb = 13, + Bt202010Bit = 14, + Bt202012Bit = 15, + Smpte2084 = 16, + Smpte248 = 17, + Hlg = 18, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs new file mode 100644 index 0000000000..34285ef2f0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuType.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuType +{ + None = 0, + SequenceHeader = 1, + TemporalDelimiter = 2, + FrameHeader = 3, + RedundantFrameHeader = 7, + TileGroup = 4, + Metadata = 5, + Frame = 6, + TileList = 8, + Padding = 15, +} From f93492704434c3942a8dc7772b44aaae7881b011 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 8 Jan 2024 21:36:16 +0100 Subject: [PATCH 036/190] Read parameters --- .../Formats/Heif/Av1/Av1TransformMode.cs | 11 + ...tDirectionalEnhancementFilterParameters.cs | 8 +- .../ObuDeltaLoopFilterParameters.cs | 13 + .../OpenBitstreamUnit/ObuDeltaQParameters.cs | 11 + .../ObuFilmGrainParameters.cs | 1 + .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 34 ++- .../ObuLoopFilterParameters.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs | 2 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 278 ++++++++++++++++-- .../ObuSkipModeParameters.cs | 11 + .../Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs | 4 +- 11 files changed, 333 insertions(+), 42 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs b/src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs new file mode 100644 index 0000000000..99d47f61e2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1TransformMode : byte +{ + Only4x4 = 0, + Largest = 1, + Select = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs index 57e54a6450..044e7799e5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs @@ -5,9 +5,11 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuConstraintDirectionalEnhancementFilterParameters { - public bool BitCount { get; internal set; } + public int BitCount { get; internal set; } - public int[] YStrength { get; internal set; } + public int[] YStrength { get; internal set; } = new int[4]; - public int[] UVStrength { get; internal set; } + public int[] UVStrength { get; internal set; } = new int[4]; + + public int Damping { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs new file mode 100644 index 0000000000..76450d968c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuDeltaLoopFilterParameters +{ + public bool IsPresent { get; internal set; } + + public int Resolution { get; internal set; } + + public bool Multi { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs new file mode 100644 index 0000000000..b82bf5cf96 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuDeltaQParameters +{ + public bool IsPresent { get; set; } + + public int Resolution { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs index 6b74c979dd..67b8f352ed 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs @@ -5,4 +5,5 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuFilmGrainParameters { + public bool ApplyGrain { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 03870ca56c..3ccf43a36d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -13,35 +13,43 @@ internal class ObuFrameHeader public bool AllowHighPrecisionMotionVector { get; set; } - public ObuTileInfo TilesInfo { get; internal set; } = new ObuTileInfo(); + public ObuTileInfo TilesInfo { get; set; } = new ObuTileInfo(); - public bool CodedLossless { get; internal set; } + public bool CodedLossless { get; set; } - public bool[] LosslessArray { get; internal set; } = new bool[ObuConstants.MaxSegmentCount]; + public bool[] LosslessArray { get; set; } = new bool[ObuConstants.MaxSegmentCount]; public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters(); public ObuSegmentationParameters SegmentationParameters { get; set; } = new ObuSegmentationParameters(); - public bool AllLossless { get; internal set; } + public bool AllLossless { get; set; } - public bool AllowWarpedMotion { get; internal set; } + public bool AllowWarpedMotion { get; set; } - public ObuReferenceMode ReferenceMode { get; internal set; } + public ObuReferenceMode ReferenceMode { get; set; } - public ObuFilmGrainParameters FilmGrainParameters { get; internal set; } = new ObuFilmGrainParameters(); + public ObuFilmGrainParameters FilmGrainParameters { get; set; } = new ObuFilmGrainParameters(); - public bool ReducedTxSet { get; internal set; } + public bool ReducedTransformSet { get; set; } - public ObuLoopFilterParameters LoopFilterParameters { get; internal set; } = new ObuLoopFilterParameters(); + public ObuLoopFilterParameters LoopFilterParameters { get; set; } = new ObuLoopFilterParameters(); - public ObuLoopRestorationParameters[] LoopRestorationParameters { get; internal set; } = new ObuLoopRestorationParameters[3]; + public ObuLoopRestorationParameters[] LoopRestorationParameters { get; set; } = new ObuLoopRestorationParameters[3]; - public ObuConstraintDirectionalEnhancementFilterParameters ConstraintDirectionalEnhancementFilterParameters { get; internal set; } = new ObuConstraintDirectionalEnhancementFilterParameters(); + public ObuConstraintDirectionalEnhancementFilterParameters CdefParameters { get; set; } = new ObuConstraintDirectionalEnhancementFilterParameters(); - public int ModeInfoStride { get; internal set; } + public int ModeInfoStride { get; set; } - public bool DisableFrameEndUpdateCdf { get; internal set; } + public bool DisableFrameEndUpdateCdf { get; set; } + + public ObuSkipModeParameters SkipModeParameters { get; set; } = new ObuSkipModeParameters(); + + public Av1TransformMode TransformMode { get; set; } + + public ObuDeltaLoopFilterParameters DeltaLoopFilterParameters { get; set; } = new ObuDeltaLoopFilterParameters(); + + public ObuDeltaQParameters DeltaQParameters { get; set; } = new ObuDeltaQParameters(); internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs index 961f3b2bc6..51e11610f6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -5,5 +5,5 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopFilterParameters { - public bool[] FilterLevel { get; internal set; } + public int[] FilterLevel { get; internal set; } = new int[2]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs index 4cdcb26550..f540a297fa 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOrderHintInfo.cs @@ -5,6 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuOrderHintInfo { + public bool EnableOrderHint { get; internal set; } + internal bool EnableJointCompound { get; set; } internal bool EnableReferenceFrameMotionVectors { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index c1676febcb..87574ed059 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -693,10 +693,10 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe // Single image is always Intra. } - SetupFrameBufferReferences(sequenceHeader, frameInfo); - CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); - SetupFrameSignBias(sequenceHeader, frameInfo); + // SetupFrameBufferReferences(sequenceHeader, frameInfo); + // CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); + // SetupFrameSignBias(sequenceHeader, frameInfo); if (sequenceHeader.IsReducedStillPictureHeader || frameInfo.DisableCdfUpdate) { frameInfo.DisableFrameEndUpdateCdf = true; @@ -704,22 +704,21 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) { - SetupPastIndependence(); + // SetupPastIndependence(); } - GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); - + // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); frameInfo.TilesInfo = ReadTileInfo(reader, sequenceHeader, frameInfo); - frameInfo.QuantizationParameters = ReadQuantizationParameters(reader, sequenceHeader.ColorConfig, planesCount); - ReadSegmentationParameters(reader, sequenceHeader, frameInfo); - ReadDeltaQParameters(reader, frameInfo); - ReadDeltaLoopFilterParameters(reader, frameInfo); - SetupSegmentationDequantization(); + ReadQuantizationParameters(reader, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); + ReadSegmentationParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadFrameDeltaQParameters(reader, frameInfo); + ReadFrameDeltaLoopFilterParameters(reader, frameInfo); + // SetupSegmentationDequantization(); Av1MainParseContext mainParseContext = new(); if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) { - ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); + // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); } int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; @@ -758,20 +757,20 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; ReadLoopFilterParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadConstraintDirectionalEnhancementFilterParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadLoopRestorationParameter(reader, sequenceHeader, frameInfo, planesCount); + ReadCdefParameters(reader, sequenceHeader, frameInfo, planesCount); + ReadLoopRestorationParameters(reader, sequenceHeader, frameInfo, planesCount); ReadTransformMode(reader, frameInfo); frameInfo.ReferenceMode = ReadFrameReferenceMode(reader, isIntraFrame); - ReadSkipModeParameters(reader, frameInfo, isIntraFrame, sequenceHeader, frameInfo.ReferenceMode); + ReadSkipModeParameters(reader, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); if (isIntraFrame || frameInfo.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) { frameInfo.AllowWarpedMotion = false; } - frameInfo.ReducedTxSet = reader.ReadBoolean(); + frameInfo.ReducedTransformSet = reader.ReadBoolean(); ReadGlobalMotionParameters(reader, sequenceHeader, frameInfo, isIntraFrame); - frameInfo.FilmGrainParameters = ReadFilmGrainParameters(reader, sequenceHeader, frameInfo); + frameInfo.FilmGrainParameters = ReadFilmGrainFilterParameters(reader, sequenceHeader, frameInfo); } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) @@ -839,11 +838,11 @@ private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader s header.PayloadSize -= headerBytes; bool noIbc = !frameInfo.AllowIntraBlockCopy; - bool doLoopFilter = noIbc && (frameInfo.LoopFilterParameters.FilterLevel[0] || frameInfo.LoopFilterParameters.FilterLevel[1]); + bool doLoopFilter = noIbc && (frameInfo.LoopFilterParameters.FilterLevel[0] != 0 || frameInfo.LoopFilterParameters.FilterLevel[1] != 0); bool doCdef = noIbc && (!frameInfo.CodedLossless && - (frameInfo.ConstraintDirectionalEnhancementFilterParameters.BitCount || - frameInfo.ConstraintDirectionalEnhancementFilterParameters.YStrength[0] != 0 || - frameInfo.ConstraintDirectionalEnhancementFilterParameters.UVStrength[0] != 0)); + (frameInfo.CdefParameters.BitCount != 0 || + frameInfo.CdefParameters.YStrength[0] != 0 || + frameInfo.CdefParameters.UVStrength[0] != 0)); bool doLoopRestoration = noIbc && (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].FrameRestorationType != ObuRestorationType.RestoreNone || frameInfo.LoopRestorationParameters[(int)Av1Plane.U].FrameRestorationType != ObuRestorationType.RestoreNone || @@ -862,7 +861,7 @@ private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader s } // TODO: Pass more info to the decoder. - DecodeTile(sequenceHeader, frameInfo, tileInfo, tileNum); + // DecodeTile(sequenceHeader, frameInfo, tileInfo, tileNum); } if (tileGroupEnd != tileCount - 1) @@ -870,7 +869,240 @@ private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader s return; } - FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); + // FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); + } + + private static int ReadDeltaQ(Av1BitStreamReader reader) + { + int deltaQ = 0; + if (reader.ReadBoolean()) + { + deltaQ = (int)reader.ReadLiteral(7); + } + + return deltaQ; + } + + private static void ReadFrameDeltaQParameters(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + { + frameInfo.DeltaQParameters.Resolution = 0; + frameInfo.DeltaQParameters.IsPresent = false; + if (frameInfo.QuantizationParameters.BaseQIndex > 0) + { + frameInfo.DeltaQParameters.IsPresent = reader.ReadBoolean(); + } + + if (frameInfo.DeltaQParameters.IsPresent) + { + frameInfo.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2); + } + } + + private static void ReadFrameDeltaLoopFilterParameters(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + { + frameInfo.DeltaLoopFilterParameters.IsPresent = false; + frameInfo.DeltaLoopFilterParameters.Resolution = 0; + frameInfo.DeltaLoopFilterParameters.Multi = false; + if (frameInfo.DeltaQParameters.IsPresent) + { + if (!frameInfo.AllowIntraBlockCopy) + { + frameInfo.DeltaLoopFilterParameters.IsPresent = reader.ReadBoolean(); + } + + if (frameInfo.DeltaLoopFilterParameters.IsPresent) + { + frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(4); + frameInfo.DeltaLoopFilterParameters.Multi = reader.ReadBoolean(); + } + } + } + + private static void ReadQuantizationParameters(Av1BitStreamReader reader, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) + { + quantParams.BaseQIndex = (int)reader.ReadLiteral(8); + quantParams.DeltaQDc[(int)Av1Plane.Y] = ReadDeltaQ(reader); + quantParams.DeltaQAc[(int)Av1Plane.Y] = 0; + if (planesCount > 1) + { + bool areUvDeltaDifferent = false; + quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(reader); + quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(reader); + if (areUvDeltaDifferent) + { + quantParams.DeltaQDc[(int)Av1Plane.V] = ReadDeltaQ(reader); + quantParams.DeltaQAc[(int)Av1Plane.V] = ReadDeltaQ(reader); + } + else + { + quantParams.DeltaQDc[(int)Av1Plane.V] = quantParams.DeltaQDc[(int)Av1Plane.U]; + quantParams.DeltaQAc[(int)Av1Plane.V] = quantParams.DeltaQAc[(int)Av1Plane.U]; + } + } + else + { + quantParams.DeltaQDc[(int)Av1Plane.U] = 0; + quantParams.DeltaQAc[(int)Av1Plane.U] = 0; + quantParams.DeltaQDc[(int)Av1Plane.V] = 0; + quantParams.DeltaQAc[(int)Av1Plane.V] = 0; + } + + quantParams.IsUsingQMatrix = reader.ReadBoolean(); + if (quantParams.IsUsingQMatrix) + { + quantParams.QMatrix[(int)Av1Plane.Y] = (int)reader.ReadLiteral(4); + quantParams.QMatrix[(int)Av1Plane.U] = (int)reader.ReadLiteral(4); + if (!colorInfo.HasSeparateUvDeltaQ) + { + quantParams.QMatrix[(int)Av1Plane.V] = quantParams.QMatrix[(int)Av1Plane.U]; + } + else + { + quantParams.QMatrix[(int)Av1Plane.V] = (int)reader.ReadLiteral(4); + } + } + else + { + quantParams.QMatrix[(int)Av1Plane.Y] = 0; + quantParams.QMatrix[(int)Av1Plane.U] = 0; + quantParams.QMatrix[(int)Av1Plane.V] = 0; + } + } + + private static void ReadSegmentationParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean(); + if (!frameInfo.SegmentationParameters.SegmentationEnabled) + { + // CopyFeatureInfo(); + return; + } + + // TODO: Parse more stuff. + } + + private static void ReadLoopFilterParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) + { + frameInfo.LoopFilterParameters.FilterLevel[0] = 0; + frameInfo.LoopFilterParameters.FilterLevel[1] = 0; + return; + } + + // TODO: Parse more stuff. + } + + private static void ReadTransformMode(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + { + if (frameInfo.CodedLossless) + { + frameInfo.TransformMode = Av1TransformMode.Only4x4; + } + else + { + if (reader.ReadBoolean()) + { + frameInfo.TransformMode = Av1TransformMode.Select; + } + else + { + frameInfo.TransformMode = Av1TransformMode.Largest; + } + } + } + + private static void ReadLoopRestorationParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + _ = planesCount; + if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) + { + frameInfo.LoopRestorationParameters[0].FrameRestorationType = ObuRestorationType.RestoreNone; + frameInfo.LoopRestorationParameters[1].FrameRestorationType = ObuRestorationType.RestoreNone; + frameInfo.LoopRestorationParameters[2].FrameRestorationType = ObuRestorationType.RestoreNone; + return; + } + + // TODO: Parse more stuff. + } + + private static void ReadCdefParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + _ = planesCount; + if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) + { + frameInfo.CdefParameters.BitCount = 0; + frameInfo.CdefParameters.YStrength[0] = 0; + frameInfo.CdefParameters.YStrength[4] = 0; + frameInfo.CdefParameters.UVStrength[0] = 0; + frameInfo.CdefParameters.UVStrength[4] = 0; + frameInfo.CdefParameters.Damping = 0; + return; + } + + // TODO: Parse more stuff. + } + + private static void ReadGlobalMotionParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) + { + _ = reader; + _ = sequenceHeader; + _ = frameInfo; + if (isIntraFrame) + { + return; + } + + // TODO: Parse more stuff. + } + + private static ObuReferenceMode ReadFrameReferenceMode(Av1BitStreamReader reader, bool isIntraFrame) + { + if (isIntraFrame) + { + return ObuReferenceMode.SingleReference; + } + + return (ObuReferenceMode)reader.ReadLiteral(1); + } + + private static void ReadSkipModeParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame, ObuReferenceMode referenceSelect) + { + if (isIntraFrame || referenceSelect == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) + { + frameInfo.SkipModeParameters.SkipModeAllowed = false; + } + else + { + // TODO: Parse more stuff. + } + + if (frameInfo.SkipModeParameters.SkipModeAllowed) + { + frameInfo.SkipModeParameters.SkipModeFlag = reader.ReadBoolean(); + } + else + { + frameInfo.SkipModeParameters.SkipModeFlag = false; + } + } + + private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + ObuFilmGrainParameters grainParams = new(); + if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameInfo.ShowFrame && !frameInfo.ShowableFrame)) + { + return grainParams; + } + + grainParams.ApplyGrain = reader.ReadBoolean(); + if (!grainParams.ApplyGrain) + { + return grainParams; + } + + // TODO: Implement parsing. + return grainParams; } private static bool IsValidSequenceLevel(int sequenceLevelIndex) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs new file mode 100644 index 0000000000..29a87a5f23 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSkipModeParameters.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuSkipModeParameters +{ + public bool SkipModeAllowed { get; set; } + + public bool SkipModeFlag { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs index 24c8f963ea..ac5379f88a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs @@ -23,13 +23,13 @@ internal class ObuTileInfo internal int TileColumnCount { get; set; } - internal int[] TileColumnStartModeInfo { get; set; } + internal int[] TileColumnStartModeInfo { get; set; } = new int[ObuConstants.MaxTileRowCount + 1]; internal int MinLog2TileRowCount { get; set; } internal int TileRowCountLog2 { get; set; } - internal int[] TileRowStartModeInfo { get; set; } + internal int[] TileRowStartModeInfo { get; set; } = new int[ObuConstants.MaxTileColumnCount + 1]; internal int TileRowCount { get; set; } From e45a41b54e2d3754f5ad60297d19c5318414ae3b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 12 Jan 2024 12:16:04 +0100 Subject: [PATCH 037/190] Av1 bitstream tests --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 23 ++--- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 56 ++++++++++++ .../Formats/Heif/Av1/Av1BitStreamTests.cs | 86 +++++++++++++++++++ 3 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index e16553121c..8804c31626 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,11 +1,13 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers.Binary; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader(Span data) { - private const int WordSize = sizeof(byte); + private const int WordSize = sizeof(byte) * 8; private const int DoubleWordSize = 2 * WordSize; private readonly Span data = data; private int wordPosition = 0; @@ -33,25 +35,14 @@ public void Skip(int bitCount) public uint ReadLiteral(int bitCount) { - uint bits = (uint)(this.data[this.wordPosition] << this.bitOffset) >> (WordSize - bitCount); - this.bitOffset += bitCount; - while (this.bitOffset > WordSize) - { - uint nextWord = this.data[this.wordPosition + 1]; - bits |= nextWord << (DoubleWordSize - bitCount); - } - - if (this.bitOffset >= WordSize) - { - this.bitOffset -= WordSize; - } - - return bits; + uint bits = BinaryPrimitives.ReadUInt32BigEndian(this.data[this.wordPosition..]); + this.Skip(bitCount); + return bits >> (32 - bitCount); } internal bool ReadBoolean() { - bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset))) > 0; + bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset - 1))) > 0; this.Skip(1); return bit; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs new file mode 100644 index 0000000000..4459c01a00 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal ref struct Av1BitStreamWriter(Stream stream) +{ + private const int WordSize = sizeof(byte) * 8; + private readonly Stream stream = stream; + private byte buffer = 0; + private int bitOffset = 0; + + public readonly int BitPosition => (int)(this.stream.Position * WordSize) + this.bitOffset; + + public readonly int Length => (int)this.stream.Length; + + public void Skip(int bitCount) + { + this.bitOffset += bitCount; + while (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + this.stream.WriteByte(this.buffer); + this.buffer = 0; + } + } + + public void Flush() + { + this.stream.WriteByte(this.buffer); + this.bitOffset = 0; + } + + public void WriteLiteral(uint value, int bitCount) + { + int shift = 24; + uint padded = value << ((32 - bitCount) - this.bitOffset); + while (bitCount >= 8) + { + byte current = (byte)(((padded >> shift) & 0xff) | this.buffer); + this.stream.WriteByte(current); + shift -= 8; + bitCount -= 8; + this.buffer = 0; + this.bitOffset = 0; + } + + if (bitCount > 0) + { + this.buffer = (byte)(((padded >> shift) & 0xff) | this.buffer); + this.bitOffset += bitCount; + } + } + + internal void WriteBoolean(bool value) => this.WriteLiteral(value ? 1U : 0U, 1); +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs new file mode 100644 index 0000000000..7d6c75dded --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers.Binary; +using SixLabors.ImageSharp.Formats.Heif.Av1; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1BitsStreamTests +{ + [Theory] + [InlineData(42, new bool[] { false, false, true, false, true, false, true, false })] + [InlineData(52, new bool[] { false, false, true, true, false, true, false, false })] + public void ReadAsBoolean(int value, bool[] bits) + { + int bitCount = bits.Length; + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteInt32BigEndian(buffer, value << (32 - bitCount)); + Av1BitStreamReader reader = new(buffer); + bool[] actual = new bool[bitCount]; + for (int i = 0; i < bitCount; i++) + { + actual[i] = reader.ReadBoolean(); + } + + Assert.Equal(bits, actual); + } + + [Theory] + [InlineData(6, 4)] + [InlineData(42, 8)] + [InlineData(52, 8)] + [InlineData(4050, 16)] + public void ReadAsLiteral(uint expected, int bitCount) + { + byte[] buffer = new byte[4]; + BinaryPrimitives.WriteUInt32BigEndian(buffer, expected << (32 - bitCount)); + Av1BitStreamReader reader = new(buffer); + uint actual = reader.ReadLiteral(bitCount); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(new bool[] { false, false, true, false, true, false, true, false })] + [InlineData(new bool[] { false, true, false, true })] + public void WriteAsBoolean(bool[] booleans) + { + using MemoryStream stream = new(4); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < booleans.Length; i++) + { + writer.WriteBoolean(booleans[i]); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + bool[] actual = new bool[booleans.Length]; + for (int i = 0; i < booleans.Length; i++) + { + actual[i] = reader.ReadBoolean(); + } + + Assert.Equal(booleans, actual); + } + + [Theory] + [InlineData(6, 4)] + [InlineData(42, 8)] + [InlineData(52, 8)] + [InlineData(4050, 16)] + public void WriteAsLiteral(uint value, int bitCount) + { + using MemoryStream stream = new(4); + Av1BitStreamWriter writer = new(stream); + writer.WriteLiteral(value, bitCount); + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + uint actual = reader.ReadLiteral(bitCount); + Assert.Equal(value, actual); + } +} From 7b961ff3296c6290a4544a82d163a573b7bd2423 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 12 Jan 2024 12:29:40 +0100 Subject: [PATCH 038/190] Optimized WriteBoolean() --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 1 - .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index 8804c31626..e1f80b1af4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -8,7 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader(Span data) { private const int WordSize = sizeof(byte) * 8; - private const int DoubleWordSize = 2 * WordSize; private readonly Span data = data; private int wordPosition = 0; private int bitOffset = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index 4459c01a00..a5c55530ec 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamWriter(Stream stream) { - private const int WordSize = sizeof(byte) * 8; + private const int WordSize = 8; private readonly Stream stream = stream; private byte buffer = 0; private int bitOffset = 0; @@ -35,7 +35,7 @@ public void WriteLiteral(uint value, int bitCount) { int shift = 24; uint padded = value << ((32 - bitCount) - this.bitOffset); - while (bitCount >= 8) + while ((bitCount + this.bitOffset) >= 8) { byte current = (byte)(((padded >> shift) & 0xff) | this.buffer); this.stream.WriteByte(current); @@ -52,5 +52,15 @@ public void WriteLiteral(uint value, int bitCount) } } - internal void WriteBoolean(bool value) => this.WriteLiteral(value ? 1U : 0U, 1); + internal void WriteBoolean(bool value) + { + byte boolByte = value ? (byte)1 : (byte)0; + this.buffer = (byte)(((boolByte << (7 - this.bitOffset)) & 0xff) | this.buffer); + this.bitOffset++; + if (this.bitOffset == WordSize) + { + this.stream.WriteByte(this.buffer); + this.bitOffset = 0; + } + } } From 961d55e9be49ae6b402e45c539e0efb0c6ae812d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 19 Jan 2024 14:27:19 +0100 Subject: [PATCH 039/190] Skeleton --- .../Formats/Heif/Av1/Av1BitDepth.cs | 11 ++ .../Formats/Heif/Av1/Av1BlockModeInfo.cs | 16 ++ src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 15 ++ .../Formats/Heif/Av1/Av1PartitionType.cs | 62 +++++++ .../Av1/OpenBitstreamUnit/ObuConstants.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 + .../Heif/Av1/Prediction/Av1PredictionMode.cs | 26 +++ .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 16 ++ .../Heif/Av1/Transform/Av1Quantization.cs | 152 ++++++++++++++++++ .../Av1/{ => Transform}/Av1TransformMode.cs | 2 +- .../Heif/Av1/Transform/Av1TransformType.cs | 49 ++++++ src/ImageSharp/Formats/Heif/Heif4CharCode.cs | 9 +- src/ImageSharp/Formats/Heif/Heif4CharCode.tt | 5 +- .../Formats/Heif/HeifDecoderCore.cs | 2 + src/ImageSharp/ImageSharp.csproj | 4 + 16 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs rename src/ImageSharp/Formats/Heif/Av1/{ => Transform}/Av1TransformMode.cs (74%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs new file mode 100644 index 0000000000..28d48b13cf --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepth.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1BitDepth : int +{ + EightBit = 0, + TenBit = 1, + TwelveBit = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs new file mode 100644 index 0000000000..43325d539c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal class Av1BlockModeInfo +{ + public Av1BlockSize BlockSize { get; } + + public Av1PredictionMode PredictionMode { get; } + + public Av1PartitionType Partition { get; } + +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index c98652e048..36929e3c65 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -77,6 +77,21 @@ public static uint Clip3(uint x, uint y, uint z) return z; } + public static int Clip3(int x, int y, int z) + { + if (z < x) + { + return x; + } + + if (z > y) + { + return y; + } + + return z; + } + public static uint Round2(uint value, int n) { if (n == 0) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs new file mode 100644 index 0000000000..e5aa1f5b18 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal enum Av1PartitionType +{ + /// + /// Not partitioned any further. + /// + None, + + /// + /// Horizontally split in 2 partitions. + /// + Horizontal, + + /// + /// Vertically split in 2 partitions. + /// + Vertical, + + /// + /// 4 equally sized partitions. + /// + Split, + + /// + /// Horizontal split and the top partition is split again. + /// + HorizontalA, + + /// + /// Horizontal split and the bottom partition is split again. + /// + HorizontalB, + + /// + /// Vertical split and the left partition is split again. + /// + VerticalA, + + /// + /// Vertical split and the right partitino is split again. + /// + VerticalB, + + /// + /// 4:1 horizontal partition. + /// + Horizontal4, + + /// + /// 4:1 vertical partition. + /// + Vertical4, + + /// + /// Invalid value. + /// + Invalid = 255 +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index 678b49d8b8..392a54a5c0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -7,7 +7,7 @@ internal static class ObuConstants { public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional; - public const int LevelBits = -1; + public const int LevelBits = 5; /// /// Number of fractional bits for computing position in upscaling. diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 3ccf43a36d..315f0f18f2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuFrameHeader diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 87574ed059..b9a6425779 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuReader diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs new file mode 100644 index 0000000000..a46733b007 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +// Inter modes are not defined here, as they do not apply to pictures. +internal enum Av1PredictionMode +{ + DC, + Vertical, + Horizontal, + Directional45Degrees, + Directional135Degrees, + Directional113Degrees, + Directional157Degrees, + Directional203Degrees, + Directional67Degrees, + Smooth, + SmoothVertical, + SmoothHorizontal, + Paeth, + IntraModeStart = DC, + IntraModeEnd = Paeth + 1, + IntraModes = Paeth, + IntraInvalid = 25, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs new file mode 100644 index 0000000000..8d66221785 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +internal class Av1InverseQuantizer +{ + + public int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) + { + return 0; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs new file mode 100644 index 0000000000..205ffaaa89 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +internal static class Av1Quantization +{ + public static readonly int[,] AcQLookup = new int[3, 256] + { + { + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, + 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188, + 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260, + 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366, + 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520, + 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743, + 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066, + 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537, + 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, + }, + { + 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, + 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, + 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, + 235, 240, 244, 249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, + 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, + 401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, + 555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749, + 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, + 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463, + 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, + 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971, + 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, + 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, + 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, + }, + { + 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, + 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, + 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735, + 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, 1012, 1030, + 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317, + 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, + 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118, + 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750, + 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619, + 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791, + 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410, + 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635, + 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661, + 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806, + 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, + 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247, + } + }; + + private static readonly int[,] DcQLookup = new int[3, 256] + { + { + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, + 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, + 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, + 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72, 73, + 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92, + 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121, + 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164, + 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, + 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, + 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384, + 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, + 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775, + 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, + }, + { + 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, + 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, + 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, + 204, 208, 212, 215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, + 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, + 337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, + 454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592, + 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782, + 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, + 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, + 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741, + 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363, + 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, + 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, + }, + { + 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, + 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, + 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643, + 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, 874, 889, + 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122, + 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, + 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741, + 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199, + 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788, + 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517, + 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420, + 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517, + 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867, + 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788, + 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409, + 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, + } + }; + + public static int GetDcQuantization(int qIndex, int delta, Av1BitDepth bitDepth) + => DcQLookup[(int)bitDepth, Av1Math.Clip3(0, 255, qIndex + delta)]; + + public static int GetAcQuantization(int qIndex, int delta, Av1BitDepth bitDepth) + => AcQLookup[(int)bitDepth, Av1Math.Clip3(0, 255, qIndex + delta)]; + + public static int GetQzbinFactor(int q, Av1BitDepth bitDepth) + { + int quant = GetDcQuantization(q, 0, bitDepth); + switch (bitDepth) + { + case Av1BitDepth.EightBit: + return q == 0 ? 64 : (quant < 148 ? 84 : 80); + case Av1BitDepth.TenBit: + return q == 0 ? 64 : (quant < 592 ? 84 : 80); + case Av1BitDepth.TwelveBit: + return q == 0 ? 64 : (quant < 2368 ? 84 : 80); + default: + DebugGuard.IsTrue(false, "bit_depth should be EIGHT_BIT, TEN_BIT or TWELVE_BIT"); + return -1; + } + } + + public static void InvertQuantization(out int quantization, out int shift, int d) + { + uint t; + int l, m; + t = (uint)d; + for (l = 0; t > 1; l++) + { + t >>= 1; + } + + m = 1 + ((1 << (16 + l)) / d); + quantization = m - (1 << 16); + shift = 1 << (16 - l); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs similarity index 74% rename from src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs index 99d47f61e2..a838dfc86b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1TransformMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal enum Av1TransformMode : byte { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs new file mode 100644 index 0000000000..93de8f2271 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformType : byte +{ + /// + /// DCT in both horizontal and vertical. + /// + DctDct, + + /// + /// ADST in vertical, DCT in horizontal. + /// + AdstDct, + + /// + /// DCT in vertical, ADST in horizontal. + /// + DctAdst, + + /// + /// ADST in both directions. + /// + AdstAdst, + FlipAdstDct, + DctFlipAdst, + FlipAdstFlipAdst, + AdstFlipAdst, + FlipAdstAdst, + Identity, + VerticalDct, + HorizontalDct, + VerticalAdst, + HorizontalAdst, + VerticalFlipAdst, + HorizontalFlipAdst, + + /// + /// Number of Transform types. + /// + TransformTypes, + + /// + /// Invalid value. + /// + Invalid, +} diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index d6d31ef1d3..3c238f2acf 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -249,12 +249,17 @@ public enum Heif4CharCode : uint free = 0x66726565U, /// - /// ICC Color Profile. + /// Color profile. + /// + nclx = 0x6E636C78U, + + /// + /// ICC Color profile. /// rICC = 0x72494343U, /// - /// ICC Color Profile. + /// ICC Color profile. /// prof = 0x70726F66U, diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt index 9f8555f33b..6c9bd514ca 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -54,8 +54,9 @@ "pict", "Picture handler type", "uuid", "Unique Identifier", "free", "Free space", - "rICC", "ICC Color Profile", - "prof", "ICC Color Profile", + "nclx", "Color profile", + "rICC", "ICC Color profile", + "prof", "ICC Color profile", }; #> diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 8a8c8e29f6..b39f24517d 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Buffers.Binary; using System.Text; +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 63bc8131bd..b76a139019 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -241,5 +241,9 @@ + + + + From 790207f40e6bea40082db990ee7a643a34652e2e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 20 Jan 2024 12:37:52 +0100 Subject: [PATCH 040/190] Prepare quantization --- .../Formats/Heif/Av1/Av1BlockModeInfo.cs | 1 - .../Av1/OpenBitstreamUnit/ObuPartitionInfo.cs | 8 +++++ .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 3 +- .../Heif/Av1/Transform/Av1TransformSize.cs | 30 +++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs index 43325d539c..a0db9473d4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs @@ -12,5 +12,4 @@ internal class Av1BlockModeInfo public Av1PredictionMode PredictionMode { get; } public Av1PartitionType Partition { get; } - } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs new file mode 100644 index 0000000000..35afda11f9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuPartitionInfo +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs index 8d66221785..98443393a9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs @@ -8,8 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; internal class Av1InverseQuantizer { - - public int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) + public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) { return 0; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs new file mode 100644 index 0000000000..267c55266d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +internal enum Av1TransformSize : byte +{ + Size4x4, + Size8x8, + Size16x16, + Size32x32, + Size64x64, + Size4x8, + Size8x4, + Size8x16, + Size16x8, + Size16x32, + Size32x16, + Size32x64, + Size64x32, + Size4x16, + Size16x4, + Size8x32, + Size32x8, + Size16x64, + Size64x16, + AllSizes, + SquareSizes = Size4x8, + Invalid = 255, +} From db44498536e7759e06d4ac907f0e114420c1e1ba Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 14 Mar 2024 20:58:58 +0100 Subject: [PATCH 041/190] Casing in Heif4CharCode --- src/ImageSharp/Formats/Heif/Heif4CharCode.cs | 98 +++++++++---------- src/ImageSharp/Formats/Heif/Heif4CharCode.tt | 13 ++- src/ImageSharp/Formats/Heif/HeifConstants.cs | 2 +- .../Formats/Heif/HeifDecoderCore.cs | 92 ++++++++--------- .../Formats/Heif/HeifEncoderCore.cs | 42 ++++---- .../Formats/Heif/HeifImageFormatDetector.cs | 4 +- 6 files changed, 129 insertions(+), 122 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index d6d31ef1d3..d2324ee8b0 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -10,38 +10,38 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Supported 4 character codes for use in HEIF images. /// -[GeneratedCode("T4", "")] +[GeneratedCode("TextTemplateFileGenerator", "")] public enum Heif4CharCode : uint { /// /// File Type. /// - ftyp = 0x66747970U, + Ftyp = 0x66747970U, /// /// Metadata. /// - meta = 0x6D657461U, + Meta = 0x6D657461U, /// /// Media Data. /// - mdat = 0x6D646174U, + Mdat = 0x6D646174U, /// /// Item Information Entry. /// - infe = 0x696E6665U, + Infe = 0x696E6665U, /// /// Item Data. /// - idat = 0x69646174U, + Idat = 0x69646174U, /// /// Item Location. /// - iloc = 0x696C6F63U, + Iloc = 0x696C6F63U, /// /// EXIF metadata. @@ -51,211 +51,211 @@ public enum Heif4CharCode : uint /// /// Data Reference. /// - dref = 0x64726566U, + Dref = 0x64726566U, /// /// Primary Item. /// - pitm = 0x7069746DU, + Pitm = 0x7069746DU, /// /// Item Spatial Extent. /// - ispe = 0x69737065U, + Ispe = 0x69737065U, /// /// Alternative text. /// - altt = 0x616C7474U, + Altt = 0x616C7474U, /// /// Colour information. /// - colr = 0x636F6C72U, + Colr = 0x636F6C72U, /// /// HVC configuration. /// - hvcC = 0x68766343U, + HvcC = 0x68766343U, /// /// AV1 configuration. /// - av1C = 0x61763143U, + Av1C = 0x61763143U, /// /// Image Mirror. /// - imir = 0x696D6972U, + Imir = 0x696D6972U, /// /// Image Rotation. /// - irot = 0x69726F74U, + Irot = 0x69726F74U, /// /// Image Scaling. /// - iscl = 0x6973636CU, + Iscl = 0x6973636CU, /// /// Pixel Aspect Ratio. /// - pasp = 0x70617370U, + Pasp = 0x70617370U, /// /// Pixel Information. /// - pixi = 0x70697869U, + Pixi = 0x70697869U, /// /// Reference Location. /// - rloc = 0x726C6F63U, + Rloc = 0x726C6F63U, /// /// User Description. /// - udes = 0x75646573U, + Udes = 0x75646573U, /// /// IPMP Control Box. /// - ipmc = 0x69706D63U, + Ipmc = 0x69706D63U, /// /// Item Property Container. /// - ipco = 0x6970636FU, + Ipco = 0x6970636FU, /// /// Item Property Association. /// - ipma = 0x69706D61U, + Ipma = 0x69706D61U, /// /// High Efficient Image Coding brand. /// - heic = 0x68656963U, + Heic = 0x68656963U, /// /// High Efficient Image Coding brand (legacy name). /// - heix = 0x68656978U, + Heix = 0x68656978U, /// /// High Efficient File brand. /// - mif1 = 0x6D696631U, + Mif1 = 0x6D696631U, /// /// AVIF brand. /// - avif = 0x61766966U, + Avif = 0x61766966U, /// /// High Efficiency Coding tile. /// - hvc1 = 0x68766331U, + Hvc1 = 0x68766331U, /// /// Legacy JPEG coded tile. /// - jpeg = 0x6A706567U, + Jpeg = 0x6A706567U, /// /// AOMedia Video Coding tile. /// - av01 = 0x61763031U, + Av01 = 0x61763031U, /// /// Data Information. /// - dinf = 0x64696E66U, + Dinf = 0x64696E66U, /// /// Group list. /// - grpl = 0x6772706CU, + Grpl = 0x6772706CU, /// /// Handler. /// - hdlr = 0x68646C72U, + Hdlr = 0x68646C72U, /// /// Item Information. /// - iinf = 0x69696E66U, + Iinf = 0x69696E66U, /// /// Item Property. /// - iprp = 0x69707270U, + Iprp = 0x69707270U, /// /// Item Protection. /// - ipro = 0x6970726FU, + Ipro = 0x6970726FU, /// /// Item Reference. /// - iref = 0x69726566U, + Iref = 0x69726566U, /// /// Grid. /// - grid = 0x67726964U, + Grid = 0x67726964U, /// /// Derived Image. /// - dimg = 0x64696D67U, + Dimg = 0x64696D67U, /// /// Thumbnail. /// - thmb = 0x74686D62U, + Thmb = 0x74686D62U, /// /// Content Description. /// - cdsc = 0x63647363U, + Cdsc = 0x63647363U, /// /// MIME type. /// - mime = 0x6D696D65U, + Mime = 0x6D696D65U, /// /// URI. /// - uri = 0x75726920U, + Uri = 0x75726920U, /// /// Picture handler type. /// - pict = 0x70696374U, + Pict = 0x70696374U, /// /// Unique Identifier. /// - uuid = 0x75756964U, + Uuid = 0x75756964U, /// /// Free space. /// - free = 0x66726565U, + Free = 0x66726565U, /// /// ICC Color Profile. /// - rICC = 0x72494343U, + RICC = 0x72494343U, /// /// ICC Color Profile. /// - prof = 0x70726F66U, + Prof = 0x70726F66U, } diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt index 9f8555f33b..a6af97004f 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -1,6 +1,6 @@ <#@ template language="C#" #> -<#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.Text" #> // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Supported 4 character codes for use in HEIF images. /// -[GeneratedCode("T4", "")] +[GeneratedCode("TextTemplateFileGenerator", "")] public enum Heif4CharCode : uint { <# @@ -74,12 +74,13 @@ public enum Heif4CharCode : uint { string shortName = codes[i]; string longName = codes[i + 1]; + string pascal = PascalCasing(shortName); string hex = Code2Hex(shortName); #> /// /// <#= longName #>. /// - <#= shortName #> = <#= hex #>, + <#= pascal #> = <#= hex #>, <# } @@ -87,6 +88,12 @@ public enum Heif4CharCode : uint } <#+ +private string PascalCasing(string code) +{ + char firstChar = char.ToUpper(code[0], System.Globalization.CultureInfo.InvariantCulture); + return string.Concat(firstChar, code.Substring(1)); +} + private string Code2Hex(string code) { byte[] b = Encoding.ASCII.GetBytes(code); diff --git a/src/ImageSharp/Formats/Heif/HeifConstants.cs b/src/ImageSharp/Formats/Heif/HeifConstants.cs index 5ef49053dd..f59fd56412 100644 --- a/src/ImageSharp/Formats/Heif/HeifConstants.cs +++ b/src/ImageSharp/Formats/Heif/HeifConstants.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// internal static class HeifConstants { - public const Heif4CharCode HeicBrand = Heif4CharCode.heic; + public const Heif4CharCode HeicBrand = Heif4CharCode.Heic; /// /// The list of mimetypes that equate to a HEIC. diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 8a8c8e29f6..b480c81b3f 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -71,13 +71,13 @@ public Image Decode(BufferedReadStream stream, CancellationToken EnsureBoxBoundary(boxLength, stream); switch (boxType) { - case Heif4CharCode.meta: + case Heif4CharCode.Meta: this.ParseMetadata(stream, boxLength); break; - case Heif4CharCode.mdat: + case Heif4CharCode.Mdat: image = this.ParseMediaData(stream, boxLength); break; - case Heif4CharCode.free: + case Heif4CharCode.Free: SkipBox(stream, boxLength); break; case 0U: @@ -115,7 +115,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat EnsureBoxBoundary(boxLength, stream); switch (boxType) { - case Heif4CharCode.meta: + case Heif4CharCode.Meta: this.ParseMetadata(stream, boxLength); break; default: @@ -141,21 +141,21 @@ private bool CheckFileTypeBox(BufferedReadStream stream) long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); Span buffer = this.ReadIntoBuffer(stream, boxLength); uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); - bool correctBrand = majorBrand is (uint)Heif4CharCode.heic or (uint)Heif4CharCode.heix or (uint)Heif4CharCode.avif; + bool correctBrand = majorBrand is (uint)Heif4CharCode.Heic or (uint)Heif4CharCode.Heix or (uint)Heif4CharCode.Avif; // TODO: Interpret minorVersion and compatible brands. - return boxType == Heif4CharCode.ftyp && correctBrand; + return boxType == Heif4CharCode.Ftyp && correctBrand; } private void UpdateMetadata(ImageMetadata metadata, HeifItem item) { HeifMetadata meta = metadata.GetHeifMetadata(); HeifCompressionMethod compressionMethod = HeifCompressionMethod.Hevc; - if (item.Type == Heif4CharCode.av01) + if (item.Type == Heif4CharCode.Av01) { compressionMethod = HeifCompressionMethod.Av1; } - else if (item.Type == Heif4CharCode.jpeg || this.itemLinks.Any(link => link.Type == Heif4CharCode.thmb)) + else if (item.Type == Heif4CharCode.Jpeg || this.itemLinks.Any(link => link.Type == Heif4CharCode.Thmb)) { compressionMethod = HeifCompressionMethod.LegacyJpeg; } @@ -209,30 +209,30 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) EnsureBoxBoundary(length, boxLength); switch (boxType) { - case Heif4CharCode.iprp: + case Heif4CharCode.Iprp: this.ParseItemProperties(stream, length); break; - case Heif4CharCode.iinf: + case Heif4CharCode.Iinf: this.ParseItemInfo(stream, length); break; - case Heif4CharCode.iref: + case Heif4CharCode.Iref: this.ParseItemReference(stream, length); break; - case Heif4CharCode.pitm: + case Heif4CharCode.Pitm: this.ParsePrimaryItem(stream, length); break; - case Heif4CharCode.hdlr: + case Heif4CharCode.Hdlr: this.ParseHandler(stream, length); break; - case Heif4CharCode.iloc: + case Heif4CharCode.Iloc: this.ParseItemLocation(stream, length); break; - case Heif4CharCode.dinf: - case Heif4CharCode.idat: - case Heif4CharCode.grpl: - case Heif4CharCode.ipro: - case Heif4CharCode.uuid: - case Heif4CharCode.ipmc: + case Heif4CharCode.Dinf: + case Heif4CharCode.Idat: + case Heif4CharCode.Grpl: + case Heif4CharCode.Ipro: + case Heif4CharCode.Uuid: + case Heif4CharCode.Ipmc: // Silently skip these boxes. SkipBox(stream, length); break; @@ -249,7 +249,7 @@ private void ParseHandler(BufferedReadStream stream, long boxLength) // Only read the handler type, to check if this is not a movie file. int bytesRead = 8; Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - if (handlerType != Heif4CharCode.pict) + if (handlerType != Heif4CharCode.Pict) { throw new ImageFormatException("Not a picture file."); } @@ -323,7 +323,7 @@ private int ParseItemInfoEntry(Span buffer) item = new HeifItem(itemType, itemId); item.Name = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.Name.Length + 1; - if (item.Type == Heif4CharCode.mime) + if (item.Type == Heif4CharCode.Mime) { item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.ContentType.Length + 1; @@ -335,7 +335,7 @@ private int ParseItemInfoEntry(Span buffer) bytesRead += item.ContentEncoding.Length + 1; } } - else if (item.Type == Heif4CharCode.uri) + else if (item.Type == Heif4CharCode.Uri) { item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); bytesRead += item.UriType.Length + 1; @@ -392,12 +392,12 @@ private void ParseItemProperties(BufferedReadStream stream, long boxLength) { long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType); EnsureBoxBoundary(containerLength, boxLength); - if (containerType == Heif4CharCode.ipco) + if (containerType == Heif4CharCode.Ipco) { // Parse Item Property Container, which is just an array of property boxes. this.ParsePropertyContainer(stream, containerLength, properties); } - else if (containerType == Heif4CharCode.ipma) + else if (containerType == Heif4CharCode.Ipma) { // Parse Item Property Association this.ParsePropertyAssociation(stream, containerLength, properties); @@ -419,18 +419,18 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L Span buffer = this.ReadIntoBuffer(stream, itemLength); switch (itemType) { - case Heif4CharCode.ispe: + case Heif4CharCode.Ispe: // Skip over version (8 bits) and flags (24 bits). int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); - properties.Add(new KeyValuePair(Heif4CharCode.ispe, new Size(width, height))); + properties.Add(new KeyValuePair(Heif4CharCode.Ispe, new Size(width, height))); break; - case Heif4CharCode.pasp: + case Heif4CharCode.Pasp: int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - properties.Add(new KeyValuePair(Heif4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); + properties.Add(new KeyValuePair(Heif4CharCode.Pasp, new Size(horizontalSpacing, verticalSpacing))); break; - case Heif4CharCode.pixi: + case Heif4CharCode.Pixi: // Skip over version (8 bits) and flags (24 bits). int channelCount = buffer[4]; int offset = 5; @@ -440,12 +440,12 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L bitsPerPixel += buffer[offset + i]; } - properties.Add(new KeyValuePair(Heif4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); + properties.Add(new KeyValuePair(Heif4CharCode.Pixi, new int[] { channelCount, bitsPerPixel })); break; - case Heif4CharCode.colr: + case Heif4CharCode.Colr: Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); - if (profileType is Heif4CharCode.rICC or Heif4CharCode.prof) + if (profileType is Heif4CharCode.RICC or Heif4CharCode.Prof) { byte[] iccData = new byte[itemLength - 4]; buffer[4..].CopyTo(iccData); @@ -453,14 +453,14 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L } break; - case Heif4CharCode.altt: - case Heif4CharCode.imir: - case Heif4CharCode.irot: - case Heif4CharCode.iscl: - case Heif4CharCode.hvcC: - case Heif4CharCode.av1C: - case Heif4CharCode.rloc: - case Heif4CharCode.udes: + case Heif4CharCode.Altt: + case Heif4CharCode.Imir: + case Heif4CharCode.Irot: + case Heif4CharCode.Iscl: + case Heif4CharCode.HvcC: + case Heif4CharCode.Av1C: + case Heif4CharCode.Rloc: + case Heif4CharCode.Udes: // TODO: Implement properties.Add(new KeyValuePair(itemType, new object())); break; @@ -495,13 +495,13 @@ private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, KeyValuePair prop = properties[(int)propId]; switch (prop.Key) { - case Heif4CharCode.ispe: + case Heif4CharCode.Ispe: this.items[itemId].SetExtent((Size)prop.Value); break; - case Heif4CharCode.pasp: + case Heif4CharCode.Pasp: this.items[itemId].PixelAspectRatio = (Size)prop.Value; break; - case Heif4CharCode.pixi: + case Heif4CharCode.Pixi: int[] values = (int[])prop.Value; this.items[itemId].ChannelCount = values[0]; this.items[itemId].BitsPerPixel = values[1]; @@ -606,14 +606,14 @@ private Image ParseMediaData(BufferedReadStream stream, long box where TPixel : unmanaged, IPixel { // FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. - HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.thmb); + HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); if (thumbLink == null) { throw new NotImplementedException("No thumbnail found"); } HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId); - if (thumbItem == null || thumbItem.Type != Heif4CharCode.jpeg) + if (thumbItem == null || thumbItem.Type != Heif4CharCode.Jpeg) { throw new NotImplementedException("No HVC decoding implemented yet"); } diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 8f83d9c6d8..4842cfcf8d 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -16,7 +16,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals /// /// The global configuration. /// - private Configuration configuration; + private readonly Configuration configuration; /// /// The encoder with options. @@ -65,7 +65,7 @@ public async void Encode(Image image, Stream stream, Cancellatio private static void GenerateItems(Image image, byte[] pixels, List items, List links) where TPixel : unmanaged, IPixel { - HeifItem primaryItem = new(Heif4CharCode.jpeg, 1u); + HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); primaryItem.DataLocations.Add(new HeifLocation(0L, pixels.LongLength)); primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; @@ -73,7 +73,7 @@ private static void GenerateItems(Image image, byte[] pixels, Li items.Add(primaryItem); // Create a fake thumbnail, to make our own Decoder happy. - HeifItemLink thumbnail = new(Heif4CharCode.thmb, 1u); + HeifItemLink thumbnail = new(Heif4CharCode.Thmb, 1u); thumbnail.DestinationIds.Add(1u); links.Add(thumbnail); } @@ -108,14 +108,14 @@ private static int WriteBoxHeader(Span buffer, Heif4CharCode type, byte ve private void WriteFileTypeBox(Stream stream) { Span buffer = stackalloc byte[24]; - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ftyp); - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Ftyp); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Heic); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.mif1); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Mif1); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Heic); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); @@ -126,7 +126,7 @@ private void WriteMetadataBox(List items, List links, St { using AutoExpandingMemory memory = new(this.configuration, 0x1000); Span buffer = memory.GetSpan(12); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.meta, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Meta, 0, 0); bytesWritten += WriteHandlerBox(memory, bytesWritten); bytesWritten += WritePrimaryItemBox(memory, bytesWritten); bytesWritten += WriteItemInfoBox(memory, bytesWritten, items); @@ -143,10 +143,10 @@ private void WriteMetadataBox(List items, List links, St private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 33); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.hdlr, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Hdlr, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.pict); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Pict); bytesWritten += 4; for (int i = 0; i < 13; i++) { @@ -160,7 +160,7 @@ private static int WriteHandlerBox(AutoExpandingMemory memory, int memoryO private static int WritePrimaryItemBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 14); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.pitm, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Pitm, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); bytesWritten += 2; @@ -171,13 +171,13 @@ private static int WritePrimaryItemBox(AutoExpandingMemory memory, int mem private static int WriteItemInfoBox(AutoExpandingMemory memory, int memoryOffset, List items) { Span buffer = memory.GetSpan(memoryOffset, 14 + (items.Count * 21)); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iinf, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iinf, 0, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count); bytesWritten += 2; foreach (HeifItem item in items) { int itemLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.infe, 2, 0); + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Infe, 2, 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id); bytesWritten += 2; BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0); @@ -196,7 +196,7 @@ private static int WriteItemInfoBox(AutoExpandingMemory memory, int memory private static int WriteItemReferenceBox(AutoExpandingMemory memory, int memoryOffset, List items, List links) { Span buffer = memory.GetSpan(memoryOffset, 12 + (links.Count * (12 + (items.Count * 2)))); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iref, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iref, 0, 0); foreach (HeifItemLink link in links) { int itemLengthOffset = bytesWritten; @@ -222,11 +222,11 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int { const ushort numPropPerItem = 1; Span buffer = memory.GetSpan(memoryOffset, 20); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iprp); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iprp); // Write 'ipco' box int ipcoLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.ipco); + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipco); foreach (HeifItem item in items) { bytesWritten += WriteSpatialExtentPropertyBox(memory, memoryOffset + bytesWritten, item); @@ -237,7 +237,7 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int // Write 'ipma' box int ipmaLengthOffset = bytesWritten; - bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.ipma, 0, 0); + bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipma, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)(items.Count * numPropPerItem)); bytesWritten += 4; ushort propIndex = 0; @@ -261,7 +261,7 @@ private static int WriteItemPropertiesBox(AutoExpandingMemory memory, int private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memory, int memoryOffset, HeifItem item) { Span buffer = memory.GetSpan(memoryOffset, 20); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ispe, 0, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Ispe, 0, 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height); @@ -274,7 +274,7 @@ private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory memor private static int WriteItemDataBox(AutoExpandingMemory memory, int memoryOffset) { Span buffer = memory.GetSpan(memoryOffset, 10); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.idat); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Idat); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); return bytesWritten; @@ -283,7 +283,7 @@ private static int WriteItemDataBox(AutoExpandingMemory memory, int memory private static int WriteItemLocationBox(AutoExpandingMemory memory, int memoryOffset, List items) { Span buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8)); - int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iloc, 1, 0); + int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iloc, 1, 0); buffer[bytesWritten++] = 0x44; buffer[bytesWritten++] = 0; BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); @@ -313,7 +313,7 @@ private static int WriteItemLocationBox(AutoExpandingMemory memory, int me private void WriteMediaDataBox(Span data, Stream stream) { Span buf = stackalloc byte[12]; - int bytesWritten = WriteBoxHeader(buf, Heif4CharCode.mdat); + int bytesWritten = WriteBoxHeader(buf, Heif4CharCode.Mdat); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)(data.Length + bytesWritten)); stream.Write(buf[..bytesWritten]); diff --git a/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs b/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs index 10dfa86158..b53aacc874 100644 --- a/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs @@ -23,8 +23,8 @@ public bool TryDetectFormat(ReadOnlySpan header, [NotNullWhen(true)] out I private static bool IsSupportedFileFormat(ReadOnlySpan header) { - bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heif4CharCode.ftyp; + bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heif4CharCode.Ftyp; uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); - return hasFtyp && (brand == (uint)Heif4CharCode.heic || brand == (uint)Heif4CharCode.heix || brand == (uint)Heif4CharCode.avif); + return hasFtyp && (brand == (uint)Heif4CharCode.Heic || brand == (uint)Heif4CharCode.Heix || brand == (uint)Heif4CharCode.Avif); } } From 73bdbd800df6e6c9276ea2484457d34bb6d6d992 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 14 Mar 2024 21:01:56 +0100 Subject: [PATCH 042/190] Make some classes internal --- src/ImageSharp/Formats/Heif/HeifItem.cs | 2 +- src/ImageSharp/Formats/Heif/HeifItemLink.cs | 2 +- src/ImageSharp/Formats/Heif/HeifLocation.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index 60f26df3ff..25e6bb2988 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Provides definition for a HEIF Item. /// -public class HeifItem(Heif4CharCode type, uint id) +internal class HeifItem(Heif4CharCode type, uint id) { /// /// Gets the ID of this Item. diff --git a/src/ImageSharp/Formats/Heif/HeifItemLink.cs b/src/ImageSharp/Formats/Heif/HeifItemLink.cs index 95e84a237f..7ed2f45402 100644 --- a/src/ImageSharp/Formats/Heif/HeifItemLink.cs +++ b/src/ImageSharp/Formats/Heif/HeifItemLink.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Link between instances within the same HEIF file. /// -public class HeifItemLink(Heif4CharCode type, uint sourceId) +internal class HeifItemLink(Heif4CharCode type, uint sourceId) { /// /// Gets the type of link. diff --git a/src/ImageSharp/Formats/Heif/HeifLocation.cs b/src/ImageSharp/Formats/Heif/HeifLocation.cs index 9348ba981e..d739cb633c 100644 --- a/src/ImageSharp/Formats/Heif/HeifLocation.cs +++ b/src/ImageSharp/Formats/Heif/HeifLocation.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Location within the file of an . /// -public class HeifLocation(long offset, long length) +internal class HeifLocation(long offset, long length) { /// /// Gets the file offset of this location. From de392d8c91b4b30277004035683f37795496a92a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 15 Mar 2024 17:54:18 +0100 Subject: [PATCH 043/190] Read header from stack allocated buffer --- .../Formats/Heif/HeifDecoderCore.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index b480c81b3f..5fdf6fe231 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.Buffers.Binary; using System.Text; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -166,7 +167,13 @@ private void UpdateMetadata(ImageMetadata metadata, HeifItem item) private long ReadBoxHeader(BufferedReadStream stream, out Heif4CharCode boxType) { // Read 4 bytes of length of box - Span buf = this.ReadIntoBuffer(stream, 8); + Span buf = stackalloc byte[8]; + int bytesRead = stream.Read(buf); + if (bytesRead != 8) + { + throw new InvalidImageContentException("Not enough data to read the Box header"); + } + long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); long headerSize = 8; @@ -175,7 +182,12 @@ private long ReadBoxHeader(BufferedReadStream stream, out Heif4CharCode boxType) if (boxSize == 1) { - buf = this.ReadIntoBuffer(stream, 8); + bytesRead = stream.Read(buf); + if (bytesRead != 8) + { + throw new InvalidImageContentException("Not enough data to read the Large Box header"); + } + boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); headerSize += 8; } @@ -638,13 +650,13 @@ private Span ReadIntoBuffer(BufferedReadStream stream, long length) { if (length <= this.buffer.Length) { - stream.Read(this.buffer, 0, (int)length); - return this.buffer; + int bytesRead = stream.Read(this.buffer, 0, (int)length); + return this.buffer.AsSpan(0, bytesRead); } else { Span temp = new byte[length]; - stream.Read(temp); + int bytesRead = stream.Read(temp); return temp; } } From d146069bc52cf8e0688d0d41ff82ca2ab7103da0 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 17 Mar 2024 13:44:18 +0100 Subject: [PATCH 044/190] Check Stream.Read return value --- .../Formats/Heif/HeifDecoderCore.cs | 137 +++++++++--------- 1 file changed, 70 insertions(+), 67 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 5fdf6fe231..79b65a296d 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -34,8 +34,6 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private readonly List itemLinks; - private readonly byte[] buffer; - /// /// Initializes a new instance of the class. /// @@ -47,7 +45,6 @@ public HeifDecoderCore(DecoderOptions options) this.metadata = new ImageMetadata(); this.items = new List(); this.itemLinks = new List(); - this.buffer = new byte[80]; } /// @@ -140,8 +137,9 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat private bool CheckFileTypeBox(BufferedReadStream stream) { long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); - Span buffer = this.ReadIntoBuffer(stream, boxLength); - uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); bool correctBrand = majorBrand is (uint)Heif4CharCode.Heic or (uint)Heif4CharCode.Heix or (uint)Heif4CharCode.Avif; // TODO: Interpret minorVersion and compatible brands. @@ -218,7 +216,7 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) while (stream.Position < endPosition) { long length = this.ReadBoxHeader(stream, out Heif4CharCode boxType); - EnsureBoxBoundary(length, boxLength); + EnsureBoxInsideParent(length, boxLength); switch (boxType) { case Heif4CharCode.Iprp: @@ -256,11 +254,12 @@ private void ParseMetadata(BufferedReadStream stream, long boxLength) private void ParseHandler(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); // Only read the handler type, to check if this is not a movie file. int bytesRead = 8; - Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); + Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[bytesRead..]); if (handlerType != Heif4CharCode.Pict) { throw new ImageFormatException("Not a picture file."); @@ -269,16 +268,17 @@ private void ParseHandler(BufferedReadStream stream, long boxLength) private void ParseItemInfo(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); uint entryCount; int bytesRead = 0; - byte version = buffer[bytesRead]; + byte version = boxBuffer[bytesRead]; bytesRead += 4; - entryCount = ReadUInt16Or32(buffer, version != 0, ref bytesRead); + entryCount = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead); for (uint i = 0; i < entryCount; i++) { - bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]); + bytesRead += this.ParseItemInfoEntry(boxBuffer[bytesRead..]); } } @@ -364,21 +364,22 @@ private int ParseItemInfoEntry(Span buffer) private void ParseItemReference(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); int bytesRead = 0; - bool largeIds = buffer[bytesRead] != 0; + bool largeIds = boxBuffer[bytesRead] != 0; bytesRead += 4; while (bytesRead < boxLength) { - bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); - uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); + bytesRead += ParseBoxHeader(boxBuffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); + uint sourceId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead); HeifItemLink link = new(linkType, sourceId); - int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + int count = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint i = 0; i < count; i++) { - uint destId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); + uint destId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead); link.DestinationIds.Add(destId); } @@ -389,10 +390,11 @@ private void ParseItemReference(BufferedReadStream stream, long boxLength) private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) { // BoxLength should be 6 or 8. - Span buffer = this.ReadIntoBuffer(stream, boxLength); - byte version = buffer[0]; + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + byte version = boxBuffer[0]; int bytesRead = 4; - this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); + this.primaryItem = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead); } private void ParseItemProperties(BufferedReadStream stream, long boxLength) @@ -403,7 +405,7 @@ private void ParseItemProperties(BufferedReadStream stream, long boxLength) while (stream.Position < endBoxPosition) { long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType); - EnsureBoxBoundary(containerLength, boxLength); + EnsureBoxInsideParent(containerLength, boxLength); if (containerType == Heif4CharCode.Ipco) { // Parse Item Property Container, which is just an array of property boxes. @@ -427,40 +429,41 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L while (stream.Position < endPosition) { int itemLength = (int)this.ReadBoxHeader(stream, out Heif4CharCode itemType); - EnsureBoxBoundary(itemLength, boxLength); - Span buffer = this.ReadIntoBuffer(stream, itemLength); + EnsureBoxInsideParent(itemLength, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, itemLength); + Span boxBuffer = boxMemory.GetSpan(); switch (itemType) { case Heif4CharCode.Ispe: // Skip over version (8 bits) and flags (24 bits). - int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); - int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); + int width = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]); + int height = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[8..]); properties.Add(new KeyValuePair(Heif4CharCode.Ispe, new Size(width, height))); break; case Heif4CharCode.Pasp: - int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); - int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); + int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); + int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]); properties.Add(new KeyValuePair(Heif4CharCode.Pasp, new Size(horizontalSpacing, verticalSpacing))); break; case Heif4CharCode.Pixi: // Skip over version (8 bits) and flags (24 bits). - int channelCount = buffer[4]; + int channelCount = boxBuffer[4]; int offset = 5; int bitsPerPixel = 0; for (int i = 0; i < channelCount; i++) { - bitsPerPixel += buffer[offset + i]; + bitsPerPixel += boxBuffer[offset + i]; } properties.Add(new KeyValuePair(Heif4CharCode.Pixi, new int[] { channelCount, bitsPerPixel })); break; case Heif4CharCode.Colr: - Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); + Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); if (profileType is Heif4CharCode.RICC or Heif4CharCode.Prof) { byte[] iccData = new byte[itemLength - 4]; - buffer[4..].CopyTo(iccData); + boxBuffer[4..].CopyTo(iccData); this.metadata.IccProfile = new IccProfile(iccData); } @@ -484,24 +487,25 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); - byte version = buffer[0]; - byte flags = buffer[3]; + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); + byte version = boxBuffer[0]; + byte flags = boxBuffer[3]; int bytesRead = 4; - int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); + int itemId = (int)ReadUInt16Or32(boxBuffer, version >= 1, ref bytesRead); - int associationCount = buffer[bytesRead++]; + int associationCount = boxBuffer[bytesRead++]; for (int i = 0; i < associationCount; i++) { uint propId; if (flags == 1) { - propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; + propId = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]) & 0x4FFFU; bytesRead += 2; } else { - propId = buffer[bytesRead++] & 0x4FU; + propId = boxBuffer[bytesRead++] & 0x4FU; } KeyValuePair prop = properties[(int)propId]; @@ -524,12 +528,13 @@ private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, private void ParseItemLocation(BufferedReadStream stream, long boxLength) { - Span buffer = this.ReadIntoBuffer(stream, boxLength); + using IMemoryOwner boxMemory = this.ReadIntoBuffer(stream, boxLength); + Span boxBuffer = boxMemory.GetSpan(); int bytesRead = 0; - byte version = buffer[bytesRead]; + byte version = boxBuffer[bytesRead]; bytesRead += 4; - byte b1 = buffer[bytesRead++]; - byte b2 = buffer[bytesRead++]; + byte b1 = boxBuffer[bytesRead++]; + byte b2 = boxBuffer[bytesRead++]; int offsetSize = (b1 >> 4) & 0x0f; int lengthSize = b1 & 0x0f; int baseOffsetSize = (b2 >> 4) & 0x0f; @@ -539,32 +544,32 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) indexSize = b2 & 0x0f; } - uint itemCount = ReadUInt16Or32(buffer, version == 2, ref bytesRead); + uint itemCount = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); for (uint i = 0; i < itemCount; i++) { - uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); + uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); HeifItem? item = this.FindItemById(itemId); if (version is 1 or 2) { bytesRead++; - byte b3 = buffer[bytesRead++]; + byte b3 = boxBuffer[bytesRead++]; int constructionMethod = b3 & 0x0f; } - uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; - ulong baseOffset = ReadUIntVariable(buffer, baseOffsetSize, ref bytesRead); - uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); + ulong baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); + uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint j = 0; j < extentCount; j++) { if (version is 1 or 2 && indexSize > 0) { - _ = ReadUIntVariable(buffer, indexSize, ref bytesRead); + _ = ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); } - ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); - ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); + ulong extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); + ulong extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); item?.DataLocations.Add(loc); } @@ -617,7 +622,8 @@ private static ulong ReadUIntVariable(Span buffer, int numBytes, ref int b private Image ParseMediaData(BufferedReadStream stream, long boxLength) where TPixel : unmanaged, IPixel { - // FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. + EnsureBoxBoundary(boxLength, stream); + // FIXME: No specific decoding yet, so parse only a JPEG thumbnail. HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); if (thumbLink == null) { @@ -633,9 +639,9 @@ private Image ParseMediaData(BufferedReadStream stream, long box int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; int thumbFileLength = (int)thumbItem.DataLocations[0].Length; stream.Skip((int)(thumbFileOffset - stream.Position)); - using IMemoryOwner thumbMemory = this.configuration.MemoryAllocator.Allocate(thumbFileLength); + EnsureBoxBoundary(thumbFileLength, stream); + using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbFileLength); Span thumbSpan = thumbMemory.GetSpan(); - stream.Read(thumbSpan); HeifMetadata meta = this.metadata.GetHeifMetadata(); meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; @@ -646,25 +652,22 @@ private Image ParseMediaData(BufferedReadStream stream, long box private static void SkipBox(BufferedReadStream stream, long boxLength) => stream.Skip((int)boxLength); - private Span ReadIntoBuffer(BufferedReadStream stream, long length) + private IMemoryOwner ReadIntoBuffer(BufferedReadStream stream, long length) { - if (length <= this.buffer.Length) + IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate((int)length); + int bytesRead = stream.Read(buffer.GetSpan()); + if (bytesRead != length) { - int bytesRead = stream.Read(this.buffer, 0, (int)length); - return this.buffer.AsSpan(0, bytesRead); - } - else - { - Span temp = new byte[length]; - int bytesRead = stream.Read(temp); - return temp; + throw new InvalidImageContentException("Stream length not sufficient for box content"); } + + return buffer; } private static void EnsureBoxBoundary(long boxLength, Stream stream) - => EnsureBoxBoundary(boxLength, stream.Length - stream.Position); + => EnsureBoxInsideParent(boxLength, stream.Length - stream.Position); - private static void EnsureBoxBoundary(long boxLength, long parentLength) + private static void EnsureBoxInsideParent(long boxLength, long parentLength) { if (boxLength > parentLength) { From c847f5d72d0ebc93dde3bc9caef166f8323fc6e2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 17 Mar 2024 13:48:02 +0100 Subject: [PATCH 045/190] Fix build --- src/ImageSharp/Formats/Heif/HeifDecoderCore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 79b65a296d..5201793606 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -623,6 +623,7 @@ private Image ParseMediaData(BufferedReadStream stream, long box where TPixel : unmanaged, IPixel { EnsureBoxBoundary(boxLength, stream); + // FIXME: No specific decoding yet, so parse only a JPEG thumbnail. HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); if (thumbLink == null) From 80140c4cefa9d6e43d41dff6d587afc1a0d9ca13 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 6 Apr 2024 10:18:46 +0200 Subject: [PATCH 046/190] Inverse quantization --- .../Formats/Heif/Av1/Av1BlockModeInfo.cs | 2 + .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 62 ++++++++++++++- ...Quantization.cs => Av1InverseTransform.cs} | 22 +++--- .../Heif/Av1/Transform/Av1ScanOrder.cs | 27 +++++++ .../Av1/Transform/Av1ScanOrderConstants.cs | 75 +++++++++++++++++++ .../Transform/Av1TransformSizeExtensions.cs | 16 ++++ .../Formats/Heif/HeifDecoderCore.cs | 1 - 7 files changed, 189 insertions(+), 16 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/Transform/{Av1Quantization.cs => Av1InverseTransform.cs} (95%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs index a0db9473d4..c288919cec 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs @@ -12,4 +12,6 @@ internal class Av1BlockModeInfo public Av1PredictionMode PredictionMode { get; } public Av1PartitionType Partition { get; } + + public int SegmentId { get; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs index 98443393a9..b52473dbd9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs @@ -8,8 +8,66 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; internal class Av1InverseQuantizer { - public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) + public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] level, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) { - return 0; + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(txSize, txType); + short[] scanIndices = scanOrder.Scan; + int maxValue = (1 << (7 + sequenceHeader.ColorConfig.BitDepth)) - 1; + int minValue = -(1 << (7 + sequenceHeader.ColorConfig.BitDepth)); + bool usingQuantizationMatrix = frameHeader.QuantizationParameters.IsUsingQMatrix; + bool lossless = frameHeader.LosslessArray[mode.SegmentId]; + short[] dequant = []; // Get from DecModCtx + int qmLevel = (lossless || !usingQuantizationMatrix) ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : frameHeader.QuantizationParameters.QMatrix[(int)plane]; + byte[] iqMatrix = []; // txType.Is2dTransform() ? Get from DecModCtx + int shift = txSize.GetScale(); + + int coefficientCount = level[0]; + Span levelSpan = level.AsSpan(1); + int lev = levelSpan[0]; + int qCoefficient; + if (lev != 0) + { + int pos = scanIndices[0]; + qCoefficient = (int)(((long)Math.Abs(lev) * GetDequantizationValue(dequant[0], pos, iqMatrix)) & 0xffffff); + qCoefficient >>= shift; + + if (lev < 0) + { + qCoefficient = -qCoefficient; + } + + qCoefficients[0] = Av1Math.Clamp(qCoefficient, minValue, maxValue); + } + + for (int i = 1; i < coefficientCount; i++) + { + lev = levelSpan[i]; + if (lev != 0) + { + int pos = scanIndices[i]; + qCoefficient = (int)(((long)Math.Abs(lev) * GetDequantizationValue(dequant[1], pos, iqMatrix)) & 0xffffff); + qCoefficient >>= shift; + + if (lev < 0) + { + qCoefficient = -qCoefficient; + } + + qCoefficients[pos] = Av1Math.Clamp(qCoefficient, minValue, maxValue); + } + } + + return coefficientCount; + } + + private static int GetDequantizationValue(short dequant, int coefficientIndex, byte[] iqMatrix) + { + int dqv = dequant; + if (iqMatrix != null) + { + dqv = ((iqMatrix[coefficientIndex] * dqv) + (1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1))) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount; + } + + return dqv; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs similarity index 95% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs index 205ffaaa89..4856c47194 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Quantization.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; -internal static class Av1Quantization +internal static class Av1InverseTransform { public static readonly int[,] AcQLookup = new int[3, 256] { @@ -121,18 +121,14 @@ public static int GetAcQuantization(int qIndex, int delta, Av1BitDepth bitDepth) public static int GetQzbinFactor(int q, Av1BitDepth bitDepth) { int quant = GetDcQuantization(q, 0, bitDepth); - switch (bitDepth) - { - case Av1BitDepth.EightBit: - return q == 0 ? 64 : (quant < 148 ? 84 : 80); - case Av1BitDepth.TenBit: - return q == 0 ? 64 : (quant < 592 ? 84 : 80); - case Av1BitDepth.TwelveBit: - return q == 0 ? 64 : (quant < 2368 ? 84 : 80); - default: - DebugGuard.IsTrue(false, "bit_depth should be EIGHT_BIT, TEN_BIT or TWELVE_BIT"); - return -1; - } + + // Bit hack to get to: + // EightBit => 148 + // TenBit => 592 + // TwelveBit => 2368 + int shift = (int)bitDepth << 1; + int threshold = (1 << shift) * 148; + return q == 0 ? 64 : (quant < threshold ? 84 : 80); } public static void InvertQuantization(out int quantization, out int shift, int d) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs new file mode 100644 index 0000000000..a773b5479b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal readonly struct Av1ScanOrder +{ + public Av1ScanOrder(short[]? scan) + { + this.Scan = scan ?? []; + this.IScan = []; + this.Neighbors = []; + } + + public Av1ScanOrder(short[] scan, short[] iscan, short[] neighbors) + { + this.Scan = scan; + this.IScan = iscan; + this.Neighbors = neighbors; + } + + public short[] Scan { get; } + + public short[] IScan { get; } + + public short[] Neighbors { get; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs new file mode 100644 index 0000000000..a014ad2245 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1ScanOrderConstants +{ + public const int QuantizationMatrixLevelBitCount = 4; + public const int QuantizationMatrixLevelCount = 1 << QuantizationMatrixLevelBitCount; + + private static readonly Av1ScanOrder[][] ScanOrders = + [ + + // Transform size 4x4 + [ + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(DefaultScan4x4), + new Av1ScanOrder(MatrixRowScan4x4), + new Av1ScanOrder(MatrixColumnScan4x4), + new Av1ScanOrder(MatrixRowScan4x4), + new Av1ScanOrder(MatrixColumnScan4x4), + new Av1ScanOrder(MatrixRowScan4x4), + new Av1ScanOrder(MatrixColumnScan4x4), + ], + + // Transform size 8x8 + [ + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(DefaultScan8x8), + new Av1ScanOrder(MatrixRowScan8x8), + new Av1ScanOrder(MatrixColumnScan8x8), + new Av1ScanOrder(MatrixRowScan8x8), + new Av1ScanOrder(MatrixColumnScan8x8), + new Av1ScanOrder(MatrixRowScan8x8), + new Av1ScanOrder(MatrixColumnScan8x8), + ], + ]; + + private static readonly short[] DefaultScan4x4 = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + private static readonly short[] MatrixColumnScan4x4 = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]; + private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + + private static readonly short[] DefaultScan8x8 = [0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, + 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, + 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]; + + private static readonly short[] MatrixColumnScan8x8 = [0, 8, 16, 24, 32, 40, 48, 56, 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, + 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 4, 12, 20, 28, 36, 44, 52, 60, 5, 13, 21, 29, + 37, 45, 53, 61, 6, 14, 22, 30, 38, 46, 54, 62, 7, 15, 23, 31, 39, 47, 55, 63]; + + private static readonly short[] MatrixRowScan8x8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; + + public static Av1ScanOrder GetScanOrder(Av1TransformSize txSize, Av1TransformMode txMode) + => ScanOrders[(int)txSize][(int)txMode]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs new file mode 100644 index 0000000000..3db631e0b3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +internal static class Av1TransformSizeExtensions +{ + private static readonly int[] Size2d = [ + 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024]; + + public static int GetScale(this Av1TransformSize size) + { + int pels = Size2d[(int)size]; + return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0; + } +} diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index c76f7bac3f..7aeeac56b4 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -6,7 +6,6 @@ using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; From 40e9d682e0152f639ec7b0594159c749e8d72553 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 17 Apr 2024 23:13:10 +0200 Subject: [PATCH 047/190] Fix reading of larger literals --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 66 ++++++++++++++----- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 12 ++-- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index e1f80b1af4..b355a9a5e9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,19 +1,35 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Buffers.Binary; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal ref struct Av1BitStreamReader(Span data) +internal ref struct Av1BitStreamReader { - private const int WordSize = sizeof(byte) * 8; - private readonly Span data = data; + private const int WordSize = 32; + + private readonly Span data; + private uint currentWord; + private uint nextWord; private int wordPosition = 0; private int bitOffset = 0; - public readonly int BitPosition => (this.wordPosition * WordSize) + this.bitOffset; + public Av1BitStreamReader(Span data) + { + this.data = MemoryMarshal.Cast(data); + this.wordPosition = -1; + this.AdvanceToNextWord(); + this.AdvanceToNextWord(); + } + public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; + + /// + /// Gets the number of bytes in the readers buffer. + /// public readonly int Length => this.data.Length; public void Reset() @@ -34,22 +50,34 @@ public void Skip(int bitCount) public uint ReadLiteral(int bitCount) { - uint bits = BinaryPrimitives.ReadUInt32BigEndian(this.data[this.wordPosition..]); - this.Skip(bitCount); - return bits >> (32 - bitCount); - } + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); - internal bool ReadBoolean() - { - bool bit = (this.data[this.wordPosition] & (1 << (WordSize - this.bitOffset - 1))) > 0; - this.Skip(1); - return bit; + uint bits = (this.currentWord << this.bitOffset) >> (WordSize - bitCount); + this.bitOffset += bitCount; + if (this.bitOffset > WordSize) + { + int overshoot = WordSize + WordSize - this.bitOffset; + if (overshoot < 32) + { + bits |= this.nextWord >> overshoot; + } + } + + if (this.bitOffset >= WordSize) + { + this.AdvanceToNextWord(); + this.bitOffset -= WordSize; + } + + return bits; } + internal bool ReadBoolean() => this.ReadLiteral(1) > 0; + public ulong ReadLittleEndianBytes128(out int length) { // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian 128 value only allowed on byte alignment"); + DebugGuard.IsTrue((this.bitOffset & 0xF7) == 0, "Reading of Little Endian 128 value only allowed on byte alignment"); ulong value = 0; length = 0; @@ -58,7 +86,7 @@ public ulong ReadLittleEndianBytes128(out int length) uint leb128Byte = this.ReadLiteral(8); value |= (leb128Byte & 0x7FUL) << i; length++; - if ((leb128Byte & 0x80U) != 0x80U) + if ((leb128Byte & 0x80U) == 0) { break; } @@ -137,4 +165,12 @@ public uint ReadLittleEndian(int n) return t; } + + public void AdvanceToNextWord() + { + this.currentWord = this.nextWord; + this.wordPosition++; + uint temp = this.data[this.wordPosition]; + this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24); + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 7d6c75dded..263182a6dd 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -12,11 +12,11 @@ public class Av1BitsStreamTests [Theory] [InlineData(42, new bool[] { false, false, true, false, true, false, true, false })] [InlineData(52, new bool[] { false, false, true, true, false, true, false, false })] - public void ReadAsBoolean(int value, bool[] bits) + public void ReadAsBoolean(byte value, bool[] bits) { int bitCount = bits.Length; - byte[] buffer = new byte[4]; - BinaryPrimitives.WriteInt32BigEndian(buffer, value << (32 - bitCount)); + byte[] buffer = new byte[8]; + buffer[0] = value; Av1BitStreamReader reader = new(buffer); bool[] actual = new bool[bitCount]; for (int i = 0; i < bitCount; i++) @@ -34,7 +34,7 @@ public void ReadAsBoolean(int value, bool[] bits) [InlineData(4050, 16)] public void ReadAsLiteral(uint expected, int bitCount) { - byte[] buffer = new byte[4]; + byte[] buffer = new byte[8]; BinaryPrimitives.WriteUInt32BigEndian(buffer, expected << (32 - bitCount)); Av1BitStreamReader reader = new(buffer); uint actual = reader.ReadLiteral(bitCount); @@ -46,7 +46,7 @@ public void ReadAsLiteral(uint expected, int bitCount) [InlineData(new bool[] { false, true, false, true })] public void WriteAsBoolean(bool[] booleans) { - using MemoryStream stream = new(4); + using MemoryStream stream = new(8); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < booleans.Length; i++) { @@ -73,7 +73,7 @@ public void WriteAsBoolean(bool[] booleans) [InlineData(4050, 16)] public void WriteAsLiteral(uint value, int bitCount) { - using MemoryStream stream = new(4); + using MemoryStream stream = new(8); Av1BitStreamWriter writer = new(stream); writer.WriteLiteral(value, bitCount); writer.Flush(); From 894bbf237bc068f02cd204a3cccf3c10812ffa00 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 17 Apr 2024 23:33:05 +0200 Subject: [PATCH 048/190] Update infra --- .gitattributes | 3 +++ ImageSharp.sln | 17 +++++++++++++++-- shared-infrastructure | 2 +- tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Heif/jpeg444_xnconvert.avif | 3 +++ 5 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 tests/Images/Input/Heif/jpeg444_xnconvert.avif diff --git a/.gitattributes b/.gitattributes index b5f742ab47..1ab893e0a8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -136,3 +136,6 @@ *.ico filter=lfs diff=lfs merge=lfs -text *.cur filter=lfs diff=lfs merge=lfs -text *.ani filter=lfs diff=lfs merge=lfs -text +*.heic filter=lfs diff=lfs merge=lfs -text +*.hif filter=lfs diff=lfs merge=lfs -text +*.avif filter=lfs diff=lfs merge=lfs -text diff --git a/ImageSharp.sln b/ImageSharp.sln index 162de84168..249c71d6a6 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -37,8 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{815C0625-CD3 ProjectSection(SolutionItems) = preProject src\Directory.Build.props = src\Directory.Build.props src\Directory.Build.targets = src\Directory.Build.targets - src\README.md = src\README.md src\ImageSharp.ruleset = src\ImageSharp.ruleset + src\README.md = src\README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageSharp\ImageSharp.csproj", "{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}" @@ -215,6 +215,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68 ProjectSection(SolutionItems) = preProject tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg = tests\Images\Input\Jpg\issues\issue-1076-invalid-subsampling.jpg tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg = tests\Images\Input\Jpg\issues\issue-1221-identify-multi-frame.jpg + tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg = tests\Images\Input\Jpg\issues\issue1006-incorrect-resize.jpg tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg = tests\Images\Input\Jpg\issues\issue1049-exif-resize.jpg tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg = tests\Images\Input\Jpg\issues\Issue159-MissingFF00-Progressive-Bedroom.jpg @@ -238,7 +239,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{5C9B68 tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg = tests\Images\Input\Jpg\issues\issue750-exif-tranform.jpg tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg = tests\Images\Input\Jpg\issues\Issue845-Incorrect-Quality99.jpg tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg = tests\Images\Input\Jpg\issues\issue855-incorrect-colorspace.jpg - tests\Images\Input\Jpg\issues\issue-2067-comment.jpg = tests\Images\Input\Jpg\issues\issue-2067-comment.jpg EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "fuzz", "fuzz", "{516A3532-6AC2-417B-AD79-9BD5D0D378A0}" @@ -661,6 +661,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Qoi", "Qoi", "{E801B508-493 tests\Images\Input\Qoi\wikipedia_008.qoi = tests\Images\Input\Qoi\wikipedia_008.qoi EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Heif", "Heif", "{BA5D603A-C84C-43E5-B300-8BB886B02936}" + ProjectSection(SolutionItems) = preProject + tests\Images\Input\Heif\dwsample-heic-640.heic = tests\Images\Input\Heif\dwsample-heic-640.heic + tests\Images\Input\Heif\image1.heic = tests\Images\Input\Heif\image1.heic + tests\Images\Input\Heif\image2.heic = tests\Images\Input\Heif\image2.heic + tests\Images\Input\Heif\image3.heic = tests\Images\Input\Heif\image3.heic + tests\Images\Input\Heif\image4.heic = tests\Images\Input\Heif\image4.heic + tests\Images\Input\Heif\IMG-20230508-0053.hif = tests\Images\Input\Heif\IMG-20230508-0053.hif + tests\Images\Input\Heif\Irvine_CA.avif = tests\Images\Input\Heif\Irvine_CA.avif + tests\Images\Input\Heif\jpeg444_xnconvert.avif = tests\Images\Input\Heif\jpeg444_xnconvert.avif + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -714,6 +726,7 @@ Global {670DD46C-82E9-499A-B2D2-00A802ED0141} = {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} {5DFC394F-136F-4B76-9BCA-3BA786515EFC} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {E801B508-4935-41CD-BA85-CF11BFF55A45} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} + {BA5D603A-C84C-43E5-B300-8BB886B02936} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795} diff --git a/shared-infrastructure b/shared-infrastructure index 1dbfb576c8..922c5b21e5 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 1dbfb576c83507645265c79e03369b66cdc0379f +Subproject commit 922c5b21e5dfa02d4ef0d95334ab01c87a7a4309 diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index acff77c1a2..8c845eae7d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1135,5 +1135,7 @@ public static class Heif // Downloaded from: https://github.com/AOMediaCodec/av1-avif/blob/master/testFiles/Microsoft/Irvine_CA.avif public const string IrvineAvif = "Heif/Irvine_CA.avif"; + + public const string XnConvert = "Heif/jpeg444_xnconvert.avif"; } } diff --git a/tests/Images/Input/Heif/jpeg444_xnconvert.avif b/tests/Images/Input/Heif/jpeg444_xnconvert.avif new file mode 100644 index 0000000000..5080be098d --- /dev/null +++ b/tests/Images/Input/Heif/jpeg444_xnconvert.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73c0521c669759df918cd59865537cd9cef3b7502ce3da825d8d24d9997e0e0d +size 1242 From 47dc7746fca3bc7390eb2eaae626cf343eb2204e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Apr 2024 14:37:55 +0200 Subject: [PATCH 049/190] Obu header decoding --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 8 +- .../Formats/Heif/Av1/Av1DecoderHandle.cs | 28 ++ ...tDirectionalEnhancementFilterParameters.cs | 4 +- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 - .../ObuLoopRestorationParameters.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 321 ++++++++++++------ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 32 ++ 7 files changed, 287 insertions(+), 110 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index b355a9a5e9..d378f8e034 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Buffers.Binary; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -50,7 +48,7 @@ public void Skip(int bitCount) public uint ReadLiteral(int bitCount) { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); uint bits = (this.currentWord << this.bitOffset) >> (WordSize - bitCount); this.bitOffset += bitCount; @@ -77,7 +75,7 @@ public uint ReadLiteral(int bitCount) public ulong ReadLittleEndianBytes128(out int length) { // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & 0xF7) == 0, "Reading of Little Endian 128 value only allowed on byte alignment"); + DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); ulong value = 0; length = 0; @@ -122,7 +120,7 @@ public uint ReadNonSymmetric(uint n) return 0; } - int w = (int)(Av1Math.MostSignificantBit(n) + 1); + int w = (int)(Av1Math.FloorLog2(n) + 1); uint m = (uint)((1 << w) - n); uint v = this.ReadLiteral(w - 1); if (v < m) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs b/src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs new file mode 100644 index 0000000000..cdad5a8905 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal class Av1DecoderHandle +{ + public Av1DecoderHandle() + { + this.FrameInfo = new ObuFrameHeader(); + this.SequenceHeader = new ObuSequenceHeader(); + this.TileInfo = new ObuTileInfo(); + } + + public bool SequenceHeaderDone { get; set; } + + public bool ShowExistingFrame { get; set; } + + public bool SeenFrameHeader { get; set; } + + public ObuFrameHeader FrameInfo { get; } + + public ObuSequenceHeader SequenceHeader { get; } + + public ObuTileInfo TileInfo { get; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs index 044e7799e5..ffd561476a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs @@ -7,9 +7,9 @@ internal class ObuConstraintDirectionalEnhancementFilterParameters { public int BitCount { get; internal set; } - public int[] YStrength { get; internal set; } = new int[4]; + public int[] YStrength { get; internal set; } = new int[5]; - public int[] UVStrength { get; internal set; } = new int[4]; + public int[] UVStrength { get; internal set; } = new int[5]; public int Damping { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 315f0f18f2..7ebca0c235 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -77,8 +77,6 @@ internal class ObuFrameHeader internal bool DisableCdfUpdate { get; set; } - internal bool ForeceIntegerMotionVector { get; set; } - internal uint CurrentFrameId { get; set; } internal uint[] ReferenceFrameIndex { get; set; } = new uint[ObuConstants.ReferenceFrameCount]; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs index 1c04072e3e..4a08041ec2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs @@ -5,5 +5,5 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopRestorationParameters { - public ObuRestorationType FrameRestorationType { get; internal set; } + public ObuRestorationType FrameRestorationType { get; internal set; } = ObuRestorationType.RestoreNone; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index b9a6425779..485fee58dc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -7,21 +7,117 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuReader { - private static readonly int[] WienerTapsMid = new[] { 3, -7, 15 }; - private static readonly int[] SgrprojXqdMid = new[] { -32, 31 }; + /// + /// Decode all OBU's in a frame. + /// + public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1DecoderHandle decoderHandle, bool isAnnexB) + { + bool frameDecodingFinished = false; + while (!frameDecodingFinished) + { + int lengthSize = 0; + int payloadSize = 0; + if (isAnnexB) + { + ReadObuSize(ref reader, out payloadSize, out lengthSize); + } + + ObuHeader header = ReadObuHeaderSize(ref reader, out lengthSize); + if (isAnnexB) + { + header.PayloadSize -= header.Size; + dataSize -= lengthSize; + lengthSize = 0; + } + + payloadSize = header.PayloadSize; + dataSize -= header.Size + lengthSize; + if (isAnnexB && dataSize < payloadSize) + { + throw new InvalidImageContentException("Corrupt frame"); + } + + switch (header.Type) + { + case ObuType.SequenceHeader: + ReadSequenceHeader(ref reader, decoderHandle.SequenceHeader); + if (decoderHandle.SequenceHeader.ColorConfig.BitDepth == 12) + { + // TODO: Initialize 12 bit predictors + } + + decoderHandle.SequenceHeaderDone = true; + break; + case ObuType.FrameHeader: + case ObuType.RedundantFrameHeader: + case ObuType.Frame: + if (header.Type != ObuType.Frame) + { + decoderHandle.ShowExistingFrame = false; + } + else if (header.Type != ObuType.FrameHeader) + { + Guard.IsFalse(decoderHandle.SeenFrameHeader, nameof(Av1DecoderHandle.SeenFrameHeader), "Frame header expected"); + } + else + { + Guard.IsTrue(decoderHandle.SeenFrameHeader, nameof(Av1DecoderHandle.SeenFrameHeader), "Already decoded a frame header"); + } + + if (!decoderHandle.SeenFrameHeader) + { + decoderHandle.SeenFrameHeader = true; + ReadFrameHeader(ref reader, decoderHandle.SequenceHeader, decoderHandle.FrameInfo, header, header.Type != ObuType.Frame); + } + + if (header.Type != ObuType.Frame) + { + break; // For OBU_TILE_GROUP comes under OBU_FRAME + } + + goto TILE_GROUP; + case ObuType.TileGroup: + TILE_GROUP: + if (!decoderHandle.SeenFrameHeader) + { + throw new InvalidImageContentException("Corrupt frame"); + } + + ReadTileGroup(ref reader, decoderHandle.SequenceHeader, decoderHandle.FrameInfo, decoderHandle.TileInfo, header, out frameDecodingFinished); + if (frameDecodingFinished) + { + decoderHandle.SeenFrameHeader = false; + } - private static ObuHeader ReadObuHeader(Av1BitStreamReader reader) + break; + case ObuType.TemporalDelimiter: + default: + // Ignore unknown OBU types. + // throw new InvalidImageContentException($"Unknown OBU header found: {header.Type.ToString()}"); + break; + } + + dataSize -= payloadSize; + if (dataSize <= 0) + { + frameDecodingFinished = true; + } + } + } + + private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader) { ObuHeader header = new(); - if (!reader.ReadBoolean()) + if (reader.ReadBoolean()) { throw new ImageFormatException("Forbidden bit in header should be unset."); } + header.Size = 1; header.Type = (ObuType)reader.ReadLiteral(4); header.HasExtension = reader.ReadBoolean(); header.HasSize = reader.ReadBoolean(); - if (!reader.ReadBoolean()) + if (reader.ReadBoolean()) { throw new ImageFormatException("Reserved bit in header should be unset."); } @@ -45,9 +141,9 @@ private static ObuHeader ReadObuHeader(Av1BitStreamReader reader) return header; } - private static void ReadObuSize(Av1BitStreamReader reader, out int obuSize) + private static void ReadObuSize(ref Av1BitStreamReader reader, out int obuSize, out int lengthSize) { - ulong rawSize = reader.ReadLittleEndianBytes128(out _); + ulong rawSize = reader.ReadLittleEndianBytes128(out lengthSize); if (rawSize > uint.MaxValue) { throw new ImageFormatException("OBU block too large."); @@ -59,12 +155,13 @@ private static void ReadObuSize(Av1BitStreamReader reader, out int obuSize) /// /// Read OBU header and size. /// - private static ObuHeader ReadObuHeaderSize(Av1BitStreamReader reader) + private static ObuHeader ReadObuHeaderSize(ref Av1BitStreamReader reader, out int lengthSize) { - ObuHeader header = ReadObuHeader(reader); + ObuHeader header = ReadObuHeader(ref reader); + lengthSize = 0; if (header.HasSize) { - ReadObuSize(reader, out int payloadSize); + ReadObuSize(ref reader, out int payloadSize, out lengthSize); header.PayloadSize = payloadSize; } @@ -75,17 +172,17 @@ private static ObuHeader ReadObuHeaderSize(Av1BitStreamReader reader) /// Check that the trailing bits start with a 1 and end with 0s. /// /// Consumes a byte, if already byte aligned before the check. - private static void ReadTrailingBits(Av1BitStreamReader reader) + private static void ReadTrailingBits(ref Av1BitStreamReader reader) { int bitsBeforeAlignment = 8 - (reader.BitPosition & 0x7); uint trailing = reader.ReadLiteral(bitsBeforeAlignment); - if (trailing != (1 << (bitsBeforeAlignment - 1))) + if (trailing != (1U << (bitsBeforeAlignment - 1))) { throw new ImageFormatException("Trailing bits not properly formatted."); } } - private static void AlignToByteBoundary(Av1BitStreamReader reader) + private static void AlignToByteBoundary(ref Av1BitStreamReader reader) { while ((reader.BitPosition & 0x7) > 0) { @@ -111,9 +208,8 @@ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrame _ => false, }; - private static ObuSequenceHeader ReadSequenceHeader(Av1BitStreamReader reader) + private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { - ObuSequenceHeader sequenceHeader = new(); sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); if (sequenceHeader.SequenceProfile > ObuConstants.MaxSequenceProfile) { @@ -130,16 +226,19 @@ private static ObuSequenceHeader ReadSequenceHeader(Av1BitStreamReader reader) sequenceHeader.TimingInfo = null; sequenceHeader.DecoderModelInfoPresentFlag = false; sequenceHeader.InitialDisplayDelayPresentFlag = false; - sequenceHeader.OperatingPoint[0].OperatorIndex = 0; - sequenceHeader.OperatingPoint[0].SequenceLevelIndex = (int)reader.ReadLiteral(ObuConstants.LevelBits); + sequenceHeader.OperatingPoint = new ObuOperatingPoint[1]; + ObuOperatingPoint operatingPoint = new(); + sequenceHeader.OperatingPoint[0] = operatingPoint; + operatingPoint.OperatorIndex = 0; + operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(ObuConstants.LevelBits); if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) { - throw new ImageFormatException("Unknown sequnce level."); + throw new ImageFormatException("Invalid sequence level."); } - sequenceHeader.OperatingPoint[0].SequenceTier = 0; - sequenceHeader.OperatingPoint[0].IsDecoderModelPresent = false; - sequenceHeader.OperatingPoint[0].IsInitialDisplayDelayPresent = false; + operatingPoint.SequenceTier = 0; + operatingPoint.IsDecoderModelPresent = false; + operatingPoint.IsInitialDisplayDelayPresent = false; // Video related flags removed @@ -172,18 +271,17 @@ private static ObuSequenceHeader ReadSequenceHeader(Av1BitStreamReader reader) sequenceHeader.EnableSuperResolution = reader.ReadBoolean(); sequenceHeader.CdefLevel = (int)reader.ReadLiteral(1); sequenceHeader.EnableRestoration = reader.ReadBoolean(); - sequenceHeader.ColorConfig = ReadColorConfig(reader, sequenceHeader); + sequenceHeader.ColorConfig = ReadColorConfig(ref reader, sequenceHeader); sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean(); - ReadTrailingBits(reader); - return sequenceHeader; + ReadTrailingBits(ref reader); } - private static ObuColorConfig ReadColorConfig(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) + private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { ObuColorConfig colorConfig = new(); - ReadBitDepth(reader, colorConfig, sequenceHeader); + ReadBitDepth(ref reader, colorConfig, sequenceHeader); colorConfig.Monochrome = false; - if (sequenceHeader.SequenceProfile == ObuSequenceProfile.High) + if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) { colorConfig.Monochrome = reader.ReadBoolean(); } @@ -224,21 +322,33 @@ private static ObuColorConfig ReadColorConfig(Av1BitStreamReader reader, ObuSequ else { colorConfig.ColorRange = reader.ReadBoolean(); - if (sequenceHeader.SequenceProfile != ObuSequenceProfile.Main) + switch (sequenceHeader.SequenceProfile) { - if (colorConfig.BitDepth == 12) - { - colorConfig.SubSamplingX = reader.ReadBoolean(); - if (colorConfig.SubSamplingX) - { - colorConfig.SubSamplingY = reader.ReadBoolean(); - } - } - else - { + case ObuSequenceProfile.Main: colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = true; + break; + case ObuSequenceProfile.High: + colorConfig.SubSamplingX = false; colorConfig.SubSamplingY = false; - } + break; + case ObuSequenceProfile.Professional: + default: + if (colorConfig.BitDepth == 12) + { + colorConfig.SubSamplingX = reader.ReadBoolean(); + if (colorConfig.SubSamplingX) + { + colorConfig.SubSamplingY = reader.ReadBoolean(); + } + } + else + { + colorConfig.SubSamplingX = true; + colorConfig.SubSamplingY = false; + } + + break; } if (colorConfig.SubSamplingX && colorConfig.SubSamplingY) @@ -251,7 +361,7 @@ private static ObuColorConfig ReadColorConfig(Av1BitStreamReader reader, ObuSequ return colorConfig; } - private static void ReadBitDepth(Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) + private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) { bool hasHighBitDepth = reader.ReadBoolean(); if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) @@ -264,7 +374,7 @@ private static void ReadBitDepth(Av1BitStreamReader reader, ObuColorConfig color } } - private static void ReadSuperResolutionParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { bool useSuperResolution = false; if (sequenceHeader.EnableSuperResolution) @@ -283,8 +393,9 @@ private static void ReadSuperResolutionParameters(Av1BitStreamReader reader, Obu frameInfo.FrameSize.SuperResolutionUpscaledWidth = frameInfo.FrameSize.FrameWidth; frameInfo.FrameSize.FrameWidth = - (frameInfo.FrameSize.SuperResolutionUpscaledWidth * ObuConstants.ScaleNumerator) + - (frameInfo.FrameSize.SuperResolutionDenominator / 2); + ((frameInfo.FrameSize.SuperResolutionUpscaledWidth * ObuConstants.ScaleNumerator) + + (frameInfo.FrameSize.SuperResolutionDenominator / 2)) / + frameInfo.FrameSize.SuperResolutionDenominator; if (frameInfo.FrameSize.SuperResolutionDenominator != ObuConstants.ScaleNumerator) { @@ -293,7 +404,7 @@ private static void ReadSuperResolutionParameters(Av1BitStreamReader reader, Obu } } - private static void ReadRenderSize(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); if (renderSizeAndFrameSizeDifferent) @@ -308,7 +419,7 @@ private static void ReadRenderSize(Av1BitStreamReader reader, ObuFrameHeader fra } } - private static void ReadFrameSizeWithReferences(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { bool foundReference = false; for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) @@ -323,17 +434,17 @@ private static void ReadFrameSizeWithReferences(Av1BitStreamReader reader, ObuSe if (!foundReference) { - ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); - ReadRenderSize(reader, frameInfo); + ReadFrameSize(ref reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(ref reader, frameInfo); } else { - ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ReadSuperResolutionParameters(ref reader, sequenceHeader, frameInfo); ComputeImageSize(sequenceHeader, frameInfo); } } - private static void ReadFrameSize(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { if (frameSizeOverrideFlag) { @@ -346,11 +457,11 @@ private static void ReadFrameSize(Av1BitStreamReader reader, ObuSequenceHeader s frameInfo.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; } - ReadSuperResolutionParameters(reader, sequenceHeader, frameInfo); + ReadSuperResolutionParameters(ref reader, sequenceHeader, frameInfo); ComputeImageSize(sequenceHeader, frameInfo); } - private static ObuTileInfo ReadTileInfo(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { ObuTileInfo tileInfo = new(); int superBlockColumnCount; @@ -518,7 +629,7 @@ private static ObuTileInfo ReadTileInfo(Av1BitStreamReader reader, ObuSequenceHe return tileInfo; } - private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHeader header, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { int idLength = 0; uint previousFrameId = 0; @@ -562,7 +673,7 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe { if (sequenceHeader.SequenceForceIntegerMotionVector == 1) { - frameInfo.ForeceIntegerMotionVector = reader.ReadBoolean(); + frameInfo.ForceIntegerMotionVector = reader.ReadBoolean(); } else { @@ -571,12 +682,12 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe } else { - frameInfo.ForeceIntegerMotionVector = false; + frameInfo.ForceIntegerMotionVector = false; } if (isIntraFrame) { - frameInfo.ForeceIntegerMotionVector = true; + frameInfo.ForceIntegerMotionVector = true; } bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); @@ -635,7 +746,12 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe frameSizeOverrideFlag = reader.ReadBoolean(); } - frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + frameInfo.OrderHint = 0; + if (sequenceHeader.OrderHintInfo.OrderHintBits > 0) + { + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + } + if (isIntraFrame || frameInfo.ErrorResilientMode) { frameInfo.PrimaryReferenceFrame = ObuConstants.PrimaryReferenceFrameNone; @@ -651,7 +767,7 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe frameInfo.AllowIntraBlockCopy = false; if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) { - frameInfo.RefreshFrameFlags = 0xffU; + frameInfo.RefreshFrameFlags = 0xFFU; } else { @@ -680,8 +796,8 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe if (isIntraFrame) { - ReadFrameSize(reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); - ReadRenderSize(reader, frameInfo); + ReadFrameSize(ref reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); + ReadRenderSize(ref reader, frameInfo); if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) { if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) @@ -706,15 +822,15 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) { - // SetupPastIndependence(); + SetupPastIndependence(frameInfo); } // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); - frameInfo.TilesInfo = ReadTileInfo(reader, sequenceHeader, frameInfo); - ReadQuantizationParameters(reader, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); - ReadSegmentationParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadFrameDeltaQParameters(reader, frameInfo); - ReadFrameDeltaLoopFilterParameters(reader, frameInfo); + frameInfo.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameInfo); + ReadQuantizationParameters(ref reader, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); + ReadSegmentationParameters(ref reader, sequenceHeader, frameInfo, planesCount); + ReadFrameDeltaQParameters(ref reader, frameInfo); + ReadFrameDeltaLoopFilterParameters(ref reader, frameInfo); // SetupSegmentationDequantization(); Av1MainParseContext mainParseContext = new(); @@ -758,21 +874,26 @@ private static void ReadUncompressedFrameHeader(Av1BitStreamReader reader, ObuHe } frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; - ReadLoopFilterParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadCdefParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadLoopRestorationParameters(reader, sequenceHeader, frameInfo, planesCount); - ReadTransformMode(reader, frameInfo); + ReadLoopFilterParameters(ref reader, sequenceHeader, frameInfo, planesCount); + ReadCdefParameters(ref reader, sequenceHeader, frameInfo, planesCount); + ReadLoopRestorationParameters(ref reader, sequenceHeader, frameInfo, planesCount); + ReadTransformMode(ref reader, frameInfo); - frameInfo.ReferenceMode = ReadFrameReferenceMode(reader, isIntraFrame); - ReadSkipModeParameters(reader, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); + frameInfo.ReferenceMode = ReadFrameReferenceMode(ref reader, isIntraFrame); + ReadSkipModeParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); if (isIntraFrame || frameInfo.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) { frameInfo.AllowWarpedMotion = false; } frameInfo.ReducedTransformSet = reader.ReadBoolean(); - ReadGlobalMotionParameters(reader, sequenceHeader, frameInfo, isIntraFrame); - frameInfo.FilmGrainParameters = ReadFilmGrainFilterParameters(reader, sequenceHeader, frameInfo); + ReadGlobalMotionParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame); + frameInfo.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameInfo); + } + + private static void SetupPastIndependence(ObuFrameHeader frameInfo) + { + // TODO: Initialize the loop filter parameters. } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) @@ -792,24 +913,24 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static void ReadFrameHeader(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuHeader header, bool trailingBit) + private static void ReadFrameHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuHeader header, bool trailingBit) { int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; int startBitPosition = reader.BitPosition; - ReadUncompressedFrameHeader(reader, header, sequenceHeader, frameInfo, planeCount); + ReadUncompressedFrameHeader(ref reader, header, sequenceHeader, frameInfo, planeCount); if (trailingBit) { - ReadTrailingBits(reader); + ReadTrailingBits(ref reader); } - AlignToByteBoundary(reader); + AlignToByteBoundary(ref reader); int endPosition = reader.BitPosition; int headerBytes = (endPosition - startBitPosition) / 8; header.PayloadSize -= headerBytes; } - private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo, ObuHeader header, out bool isLastTileGroup) + private static void ReadTileGroup(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo, ObuHeader header, out bool isLastTileGroup) { int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; @@ -834,7 +955,7 @@ private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader s } isLastTileGroup = (tileGroupEnd + 1) == tileCount; - AlignToByteBoundary(reader); + AlignToByteBoundary(ref reader); int endBitPosition = reader.BitPosition; int headerBytes = (endBitPosition - startBitPosition) / 8; header.PayloadSize -= headerBytes; @@ -874,7 +995,7 @@ private static void ReadTileGroup(Av1BitStreamReader reader, ObuSequenceHeader s // FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); } - private static int ReadDeltaQ(Av1BitStreamReader reader) + private static int ReadDeltaQ(ref Av1BitStreamReader reader) { int deltaQ = 0; if (reader.ReadBoolean()) @@ -885,7 +1006,7 @@ private static int ReadDeltaQ(Av1BitStreamReader reader) return deltaQ; } - private static void ReadFrameDeltaQParameters(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { frameInfo.DeltaQParameters.Resolution = 0; frameInfo.DeltaQParameters.IsPresent = false; @@ -900,7 +1021,7 @@ private static void ReadFrameDeltaQParameters(Av1BitStreamReader reader, ObuFram } } - private static void ReadFrameDeltaLoopFilterParameters(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { frameInfo.DeltaLoopFilterParameters.IsPresent = false; frameInfo.DeltaLoopFilterParameters.Resolution = 0; @@ -920,20 +1041,20 @@ private static void ReadFrameDeltaLoopFilterParameters(Av1BitStreamReader reader } } - private static void ReadQuantizationParameters(Av1BitStreamReader reader, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) + private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) { quantParams.BaseQIndex = (int)reader.ReadLiteral(8); - quantParams.DeltaQDc[(int)Av1Plane.Y] = ReadDeltaQ(reader); + quantParams.DeltaQDc[(int)Av1Plane.Y] = ReadDeltaQ(ref reader); quantParams.DeltaQAc[(int)Av1Plane.Y] = 0; if (planesCount > 1) { bool areUvDeltaDifferent = false; - quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(reader); - quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(reader); + quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); + quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); if (areUvDeltaDifferent) { - quantParams.DeltaQDc[(int)Av1Plane.V] = ReadDeltaQ(reader); - quantParams.DeltaQAc[(int)Av1Plane.V] = ReadDeltaQ(reader); + quantParams.DeltaQDc[(int)Av1Plane.V] = ReadDeltaQ(ref reader); + quantParams.DeltaQAc[(int)Av1Plane.V] = ReadDeltaQ(ref reader); } else { @@ -971,7 +1092,7 @@ private static void ReadQuantizationParameters(Av1BitStreamReader reader, ObuQua } } - private static void ReadSegmentationParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean(); if (!frameInfo.SegmentationParameters.SegmentationEnabled) @@ -983,7 +1104,7 @@ private static void ReadSegmentationParameters(Av1BitStreamReader reader, ObuSeq // TODO: Parse more stuff. } - private static void ReadLoopFilterParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) { @@ -995,7 +1116,7 @@ private static void ReadLoopFilterParameters(Av1BitStreamReader reader, ObuSeque // TODO: Parse more stuff. } - private static void ReadTransformMode(Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { if (frameInfo.CodedLossless) { @@ -1014,21 +1135,21 @@ private static void ReadTransformMode(Av1BitStreamReader reader, ObuFrameHeader } } - private static void ReadLoopRestorationParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { _ = planesCount; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) { - frameInfo.LoopRestorationParameters[0].FrameRestorationType = ObuRestorationType.RestoreNone; - frameInfo.LoopRestorationParameters[1].FrameRestorationType = ObuRestorationType.RestoreNone; - frameInfo.LoopRestorationParameters[2].FrameRestorationType = ObuRestorationType.RestoreNone; + frameInfo.LoopRestorationParameters[0] = new ObuLoopRestorationParameters(); + frameInfo.LoopRestorationParameters[1] = new ObuLoopRestorationParameters(); + frameInfo.LoopRestorationParameters[2] = new ObuLoopRestorationParameters(); return; } // TODO: Parse more stuff. } - private static void ReadCdefParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { _ = planesCount; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) @@ -1045,7 +1166,7 @@ private static void ReadCdefParameters(Av1BitStreamReader reader, ObuSequenceHea // TODO: Parse more stuff. } - private static void ReadGlobalMotionParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) + private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) { _ = reader; _ = sequenceHeader; @@ -1058,7 +1179,7 @@ private static void ReadGlobalMotionParameters(Av1BitStreamReader reader, ObuSeq // TODO: Parse more stuff. } - private static ObuReferenceMode ReadFrameReferenceMode(Av1BitStreamReader reader, bool isIntraFrame) + private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame) { if (isIntraFrame) { @@ -1068,7 +1189,7 @@ private static ObuReferenceMode ReadFrameReferenceMode(Av1BitStreamReader reader return (ObuReferenceMode)reader.ReadLiteral(1); } - private static void ReadSkipModeParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame, ObuReferenceMode referenceSelect) + private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame, ObuReferenceMode referenceSelect) { if (isIntraFrame || referenceSelect == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) { @@ -1089,7 +1210,7 @@ private static void ReadSkipModeParameters(Av1BitStreamReader reader, ObuSequenc } } - private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { ObuFilmGrainParameters grainParams = new(); if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameInfo.ShowFrame && !frameInfo.ShowableFrame)) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs new file mode 100644 index 0000000000..fc02e17c22 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class ObuFrameHeaderTests +{ + [Theory] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D, false)] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1, false)] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x0017, false)] + public void ReadFrameHeader(string filename, int fileOffset, int blockSize, bool isAnnexB) + { + // Assign + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + byte[] content = File.ReadAllBytes(filePath); + Av1BitStreamReader reader = new(content.AsSpan(fileOffset)); + Av1DecoderHandle decoder = new(); + + // Act + ObuReader.Read(ref reader, blockSize, decoder, isAnnexB); + + // Assert + Assert.True(decoder.SequenceHeaderDone); + Assert.False(decoder.SeenFrameHeader); + } + +} From 6aa74f20b972e548a0d3df191e5f9fac0a1655b6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Apr 2024 14:51:55 +0200 Subject: [PATCH 050/190] Rename to Av1Decoder --- .../Heif/Av1/{Av1DecoderHandle.cs => Av1Decoder.cs} | 10 ++++++++-- .../Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +++--- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 8 ++++---- 3 files changed, 15 insertions(+), 9 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/{Av1DecoderHandle.cs => Av1Decoder.cs} (74%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs similarity index 74% rename from src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs rename to src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index cdad5a8905..7653fea9ce 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1DecoderHandle.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -5,9 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal class Av1DecoderHandle +internal class Av1Decoder { - public Av1DecoderHandle() + public Av1Decoder() { this.FrameInfo = new ObuFrameHeader(); this.SequenceHeader = new ObuSequenceHeader(); @@ -25,4 +25,10 @@ public Av1DecoderHandle() public ObuSequenceHeader SequenceHeader { get; } public ObuTileInfo TileInfo { get; } + + public void Decode(Span buffer) + { + Av1BitStreamReader reader = new(buffer); + ObuReader.Read(ref reader, buffer.Length, this, false); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 485fee58dc..656f3c57ea 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -10,7 +10,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1DecoderHandle decoderHandle, bool isAnnexB) + public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder decoderHandle, bool isAnnexB) { bool frameDecodingFinished = false; while (!frameDecodingFinished) @@ -57,11 +57,11 @@ public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1DecoderH } else if (header.Type != ObuType.FrameHeader) { - Guard.IsFalse(decoderHandle.SeenFrameHeader, nameof(Av1DecoderHandle.SeenFrameHeader), "Frame header expected"); + Guard.IsFalse(decoderHandle.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Frame header expected"); } else { - Guard.IsTrue(decoderHandle.SeenFrameHeader, nameof(Av1DecoderHandle.SeenFrameHeader), "Already decoded a frame header"); + Guard.IsTrue(decoderHandle.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Already decoded a frame header"); } if (!decoderHandle.SeenFrameHeader) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index fc02e17c22..398f84b853 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -12,17 +12,17 @@ public class ObuFrameHeaderTests [Theory] // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D, false)] // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1, false)] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x0017, false)] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, false)] public void ReadFrameHeader(string filename, int fileOffset, int blockSize, bool isAnnexB) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); - Av1BitStreamReader reader = new(content.AsSpan(fileOffset)); - Av1DecoderHandle decoder = new(); + Span span = content.AsSpan(fileOffset, blockSize); + Av1Decoder decoder = new(); // Act - ObuReader.Read(ref reader, blockSize, decoder, isAnnexB); + decoder.Decode(span); // Assert Assert.True(decoder.SequenceHeaderDone); From f2bfd5a25ae9223c365c4287cb315e6249fd53a9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 23 Apr 2024 17:52:42 +0200 Subject: [PATCH 051/190] Tile decoding interface --- .../Formats/Heif/Av1/Av1BlockSize.cs | 10 +++++ src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 17 +++++++ src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 21 ++++++++- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 45 +++++++++++-------- src/ImageSharp/ImageSharp.csproj | 4 -- 5 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index 6eca5bc638..f043208e98 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -32,3 +32,13 @@ internal enum Av1BlockSize BlockInvalid = 255, BlockLargest = BlockSizeS - 1, } + +internal static class Av1BlockSizeExtensions +{ + private static readonly int[] SizeWide = { 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16 }; + private static readonly int[] SizeHigh = { 1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4 }; + + public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; + + public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 7653fea9ce..c8bec52428 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -31,4 +31,21 @@ public void Decode(Span buffer) Av1BitStreamReader reader = new(buffer); ObuReader.Read(ref reader, buffer.Length, this, false); } + + internal void DecodeTile(ref Av1BitStreamReader reader, int tileNum) + { + // TODO: Implement + } + + internal void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex) + { + int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); + int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); + + } + + internal void FinishDecodeTiles(ref Av1BitStreamReader reader, bool doCdef, bool doLoopRestoration) + { + // TODO: Implement + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 36929e3c65..ce5dd66269 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -5,7 +5,26 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1Math { - public static uint MostSignificantBit(uint value) => value >> 31; + public static int MostSignificantBit(uint value) + { + int log = 0; + int i; + + Guard.IsTrue(value != 0, nameof(value), "Must have al least 1 bit set"); + + for (i = 4; i >= 0; --i) + { + int shift = 1 << i; + uint x = value >> shift; + if (x != 0) + { + value = x; + log += shift; + } + } + + return log; + } public static uint Log2(uint n) { diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 656f3c57ea..6afd89a24c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -10,7 +10,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder decoderHandle, bool isAnnexB) + public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder decoder, bool isAnnexB) { bool frameDecodingFinished = false; while (!frameDecodingFinished) @@ -40,34 +40,34 @@ public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder switch (header.Type) { case ObuType.SequenceHeader: - ReadSequenceHeader(ref reader, decoderHandle.SequenceHeader); - if (decoderHandle.SequenceHeader.ColorConfig.BitDepth == 12) + ReadSequenceHeader(ref reader, decoder.SequenceHeader); + if (decoder.SequenceHeader.ColorConfig.BitDepth == 12) { // TODO: Initialize 12 bit predictors } - decoderHandle.SequenceHeaderDone = true; + decoder.SequenceHeaderDone = true; break; case ObuType.FrameHeader: case ObuType.RedundantFrameHeader: case ObuType.Frame: if (header.Type != ObuType.Frame) { - decoderHandle.ShowExistingFrame = false; + decoder.ShowExistingFrame = false; } else if (header.Type != ObuType.FrameHeader) { - Guard.IsFalse(decoderHandle.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Frame header expected"); + Guard.IsFalse(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Frame header expected"); } else { - Guard.IsTrue(decoderHandle.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Already decoded a frame header"); + Guard.IsTrue(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Already decoded a frame header"); } - if (!decoderHandle.SeenFrameHeader) + if (!decoder.SeenFrameHeader) { - decoderHandle.SeenFrameHeader = true; - ReadFrameHeader(ref reader, decoderHandle.SequenceHeader, decoderHandle.FrameInfo, header, header.Type != ObuType.Frame); + decoder.SeenFrameHeader = true; + ReadFrameHeader(ref reader, decoder, header, header.Type != ObuType.Frame); } if (header.Type != ObuType.Frame) @@ -78,15 +78,15 @@ public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder goto TILE_GROUP; case ObuType.TileGroup: TILE_GROUP: - if (!decoderHandle.SeenFrameHeader) + if (!decoder.SeenFrameHeader) { throw new InvalidImageContentException("Corrupt frame"); } - ReadTileGroup(ref reader, decoderHandle.SequenceHeader, decoderHandle.FrameInfo, decoderHandle.TileInfo, header, out frameDecodingFinished); + ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); if (frameDecodingFinished) { - decoderHandle.SeenFrameHeader = false; + decoder.SeenFrameHeader = false; } break; @@ -629,8 +629,10 @@ private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequen return tileInfo; } - private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, int planesCount) { + ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; + ObuFrameHeader frameInfo = decoder.FrameInfo; int idLength = 0; uint previousFrameId = 0; bool isIntraFrame = false; @@ -913,11 +915,13 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static void ReadFrameHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuHeader header, bool trailingBit) + private static void ReadFrameHeader(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, bool trailingBit) { + ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; + ObuFrameHeader frameInfo = decoder.FrameInfo; int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; int startBitPosition = reader.BitPosition; - ReadUncompressedFrameHeader(ref reader, header, sequenceHeader, frameInfo, planeCount); + ReadUncompressedFrameHeader(ref reader, decoder, header, planeCount); if (trailingBit) { ReadTrailingBits(ref reader); @@ -930,8 +934,11 @@ private static void ReadFrameHeader(ref Av1BitStreamReader reader, ObuSequenceHe header.PayloadSize -= headerBytes; } - private static void ReadTileGroup(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo, ObuHeader header, out bool isLastTileGroup) + private static void ReadTileGroup(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, out bool isLastTileGroup) { + ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; + ObuFrameHeader frameInfo = decoder.FrameInfo; + ObuTileInfo tileInfo = decoder.TileInfo; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; bool tileStartAndEndPresentFlag = false; @@ -984,7 +991,7 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, ObuSequenceHead } // TODO: Pass more info to the decoder. - // DecodeTile(sequenceHeader, frameInfo, tileInfo, tileNum); + decoder.DecodeTile(ref reader, tileNum); } if (tileGroupEnd != tileCount - 1) @@ -992,7 +999,7 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, ObuSequenceHead return; } - // FinishDecodeTiles(sequenceHeader, frameInfo, doCdef, doLoopRestoration); + decoder.FinishDecodeTiles(ref reader, doCdef, doLoopRestoration); } private static int ReadDeltaQ(ref Av1BitStreamReader reader) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index b76a139019..63bc8131bd 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -241,9 +241,5 @@ - - - - From a1a1a5a17690d27cf32b8418fe00c5af0efb74e9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 23 Apr 2024 18:09:06 +0200 Subject: [PATCH 052/190] Fix build --- src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs | 10 ---------- .../Formats/Heif/Av1/Av1BlockSizeExtensions.cs | 14 ++++++++++++++ src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 15 +++++++-------- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index f043208e98..6eca5bc638 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -32,13 +32,3 @@ internal enum Av1BlockSize BlockInvalid = 255, BlockLargest = BlockSizeS - 1, } - -internal static class Av1BlockSizeExtensions -{ - private static readonly int[] SizeWide = { 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16 }; - private static readonly int[] SizeHigh = { 1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4 }; - - public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; - - public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs new file mode 100644 index 0000000000..16190dd6b2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1BlockSizeExtensions +{ + private static readonly int[] SizeWide = { 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16 }; + private static readonly int[] SizeHigh = { 1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4 }; + + public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; + + public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index c8bec52428..8841697388 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal class Av1Decoder +internal class Av1Decoder : IAv1TileDecoder { public Av1Decoder() { @@ -32,20 +32,19 @@ public void Decode(Span buffer) ObuReader.Read(ref reader, buffer.Length, this, false); } - internal void DecodeTile(ref Av1BitStreamReader reader, int tileNum) + public void DecodeTile(ref Av1BitStreamReader reader, int tileNum) { // TODO: Implement } - internal void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex) + public void FinishDecodeTiles(ref Av1BitStreamReader reader, bool doCdef, bool doLoopRestoration) { - int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); - int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); - + // TODO: Implement } - internal void FinishDecodeTiles(ref Av1BitStreamReader reader, bool doCdef, bool doLoopRestoration) + private static void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex) { - // TODO: Implement + int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); + int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); } } From 07138c4ff8a0e3d857f96885a0b422e5c65db255 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 23 Apr 2024 18:27:12 +0200 Subject: [PATCH 053/190] Use Tile Decoder interface --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 4 +- .../Formats/Heif/Av1/IAv1TileDecoder.cs | 55 +++++++++++++++++++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 12 ++-- 3 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 8841697388..edd4940bf3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -32,12 +32,12 @@ public void Decode(Span buffer) ObuReader.Read(ref reader, buffer.Length, this, false); } - public void DecodeTile(ref Av1BitStreamReader reader, int tileNum) + public void DecodeTile(int tileNum) { // TODO: Implement } - public void FinishDecodeTiles(ref Av1BitStreamReader reader, bool doCdef, bool doLoopRestoration) + public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) { // TODO: Implement } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs new file mode 100644 index 0000000000..345cd29fe0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -0,0 +1,55 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +/// +/// Interface for decoding of image tiles. +/// +internal interface IAv1TileDecoder +{ + /// + /// Gets or sets a value indicating whether a sequence header has been read. + /// + bool SequenceHeaderDone { get; set; } + + /// + /// Gets or sets a value indicating whether to show the existing frame. + /// + bool ShowExistingFrame { get; set; } + + /// + /// Gets or sets a value indicating whether a FrameHeader has just been read. + /// + bool SeenFrameHeader { get; set; } + + /// + /// Gets Information about the frame. + /// + ObuFrameHeader FrameInfo { get; } + + /// + /// Gets Information about the sequence of frames. + /// + ObuSequenceHeader SequenceHeader { get; } + + /// + /// Gets information required to decode the tiles of a frame. + /// + ObuTileInfo TileInfo { get; } + + /// + /// Decode a single tile. + /// + /// The index of the tile that is to be decoded. + void DecodeTile(int tileNum); + + /// + /// Finshed decoding all tiles of a frame. + /// + /// Apply the CDF filter. + /// Apply the loop filters. + void FinishDecodeTiles(bool doCdef, bool doLoopRestoration); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 6afd89a24c..e0066e7c1d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -10,7 +10,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, Av1Decoder decoder, bool isAnnexB) + public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB) { bool frameDecodingFinished = false; while (!frameDecodingFinished) @@ -629,7 +629,7 @@ private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequen return tileInfo; } - private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, int planesCount) + private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, int planesCount) { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; @@ -915,7 +915,7 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static void ReadFrameHeader(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, bool trailingBit) + private static void ReadFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, bool trailingBit) { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; @@ -934,7 +934,7 @@ private static void ReadFrameHeader(ref Av1BitStreamReader reader, Av1Decoder de header.PayloadSize -= headerBytes; } - private static void ReadTileGroup(ref Av1BitStreamReader reader, Av1Decoder decoder, ObuHeader header, out bool isLastTileGroup) + private static void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; @@ -991,7 +991,7 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, Av1Decoder deco } // TODO: Pass more info to the decoder. - decoder.DecodeTile(ref reader, tileNum); + decoder.DecodeTile(tileNum); } if (tileGroupEnd != tileCount - 1) @@ -999,7 +999,7 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, Av1Decoder deco return; } - decoder.FinishDecodeTiles(ref reader, doCdef, doLoopRestoration); + decoder.FinishDecodeTiles(doCdef, doLoopRestoration); } private static int ReadDeltaQ(ref Av1BitStreamReader reader) From a1cb2ddc5ee15bb3ba8103072981f055c89fbdc6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 28 Apr 2024 16:18:56 +0200 Subject: [PATCH 054/190] Introduce ObuWriter --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 24 + .../Av1/OpenBitstreamUnit/ObuConstants.cs | 7 +- ...tDirectionalEnhancementFilterParameters.cs | 6 +- .../ObuLoopRestorationParameters.cs | 4 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 110 +++- .../OpenBitstreamUnit/ObuRestorationType.cs | 7 +- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 4 + .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 583 ++++++++++++++++++ 8 files changed, 705 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index a5c55530ec..28fa923be9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -63,4 +63,28 @@ internal void WriteBoolean(bool value) this.bitOffset = 0; } } + + public void WriteSignedFromUnsigned(int signedValue, int n) + { + // See section 4.10.6 of the AV1-Specification + uint value = unchecked((uint)signedValue); + this.WriteLiteral(value, n + 1); + } + + public void WriteLittleEndianBytes128(uint value) + { + if (value < 128) + { + this.WriteLiteral(value, 8); + } + else if (value < 0x8000U) + { + this.WriteLiteral(value >> 7, 8); + this.WriteLiteral(value & 0x80, 8); + } + else + { + throw new NotImplementedException("No such large values yet."); + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index 392a54a5c0..c89e747f3a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -51,7 +51,7 @@ internal static class ObuConstants /// public const uint PrimaryReferenceFrameNone = 7; - public const int PimaryReferenceBits = -1; + public const int PimaryReferenceBits = 3; /// /// Number of segments allowed in segmentation map. @@ -79,4 +79,9 @@ internal static class ObuConstants /// Number of segmentation features. /// public const int SegmentationLevelMax = 8; + + /// + /// Maximum size of a loop restoration tile. + /// + public const int RestorationMaxTileSize = 256; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs index ffd561476a..f952fae216 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstraintDirectionalEnhancementFilterParameters.cs @@ -7,9 +7,9 @@ internal class ObuConstraintDirectionalEnhancementFilterParameters { public int BitCount { get; internal set; } - public int[] YStrength { get; internal set; } = new int[5]; + public int Damping { get; internal set; } - public int[] UVStrength { get; internal set; } = new int[5]; + public int[] YStrength { get; set; } = new int[16]; - public int Damping { get; internal set; } + public int[] UvStrength { get; set; } = new int[16]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs index 4a08041ec2..205beba370 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs @@ -5,5 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopRestorationParameters { - public ObuRestorationType FrameRestorationType { get; internal set; } = ObuRestorationType.RestoreNone; + internal int Size { get; set; } + + internal ObuRestorationType Type { get; set; } = ObuRestorationType.None; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index e0066e7c1d..f4a134c526 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -10,7 +10,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB) + public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) { bool frameDecodingFinished = false; while (!frameDecodingFinished) @@ -255,7 +255,7 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.SuperBlockSize = sequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; sequenceHeader.ModeInfoSize = sequenceHeader.Use128x128SuperBlock ? 32 : 16; sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6; - sequenceHeader.FilterIntraLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableFilterIntra = reader.ReadBoolean(); sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); sequenceHeader.EnableInterIntraCompound = false; sequenceHeader.EnableMaskedCompound = false; @@ -269,7 +269,7 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc // Video related flags removed sequenceHeader.EnableSuperResolution = reader.ReadBoolean(); - sequenceHeader.CdefLevel = (int)reader.ReadLiteral(1); + sequenceHeader.EnableCdef = reader.ReadBoolean(); sequenceHeader.EnableRestoration = reader.ReadBoolean(); sequenceHeader.ColorConfig = ReadColorConfig(ref reader, sequenceHeader); sequenceHeader.AreFilmGrainingParametersPresent = reader.ReadBoolean(); @@ -748,11 +748,7 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I frameSizeOverrideFlag = reader.ReadBoolean(); } - frameInfo.OrderHint = 0; - if (sequenceHeader.OrderHintInfo.OrderHintBits > 0) - { - frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); - } + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); if (isIntraFrame || frameInfo.ErrorResilientMode) { @@ -972,11 +968,11 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder bool doCdef = noIbc && (!frameInfo.CodedLossless && (frameInfo.CdefParameters.BitCount != 0 || frameInfo.CdefParameters.YStrength[0] != 0 || - frameInfo.CdefParameters.UVStrength[0] != 0)); + frameInfo.CdefParameters.UvStrength[0] != 0)); bool doLoopRestoration = noIbc && - (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].FrameRestorationType != ObuRestorationType.RestoreNone || - frameInfo.LoopRestorationParameters[(int)Av1Plane.U].FrameRestorationType != ObuRestorationType.RestoreNone || - frameInfo.LoopRestorationParameters[(int)Av1Plane.V].FrameRestorationType != ObuRestorationType.RestoreNone); + (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].Type != ObuRestorationType.None || + frameInfo.LoopRestorationParameters[(int)Av1Plane.U].Type != ObuRestorationType.None || + frameInfo.LoopRestorationParameters[(int)Av1Plane.V].Type != ObuRestorationType.None); for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) { @@ -1007,7 +1003,7 @@ private static int ReadDeltaQ(ref Av1BitStreamReader reader) int deltaQ = 0; if (reader.ReadBoolean()) { - deltaQ = (int)reader.ReadLiteral(7); + deltaQ = reader.ReadSignedFromUnsigned(6); } return deltaQ; @@ -1042,7 +1038,7 @@ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader re if (frameInfo.DeltaLoopFilterParameters.IsPresent) { - frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(4); + frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2); frameInfo.DeltaLoopFilterParameters.Multi = reader.ReadBoolean(); } } @@ -1102,11 +1098,7 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean(); - if (!frameInfo.SegmentationParameters.SegmentationEnabled) - { - // CopyFeatureInfo(); - return; - } + Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentation not supported yet."); // TODO: Parse more stuff. } @@ -1142,6 +1134,9 @@ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHea } } + /// + /// See section 5.9.20. + /// private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { _ = planesCount; @@ -1153,37 +1148,86 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, return; } - // TODO: Parse more stuff. + bool usesLoopRestoration = false; + bool usesChromaLoopRestoration = false; + for (int i = 0; i < planesCount; i++) + { + frameInfo.LoopRestorationParameters[i].Type = (ObuRestorationType)reader.ReadLiteral(2); + if (frameInfo.LoopRestorationParameters[i].Type != ObuRestorationType.None) + { + usesLoopRestoration = true; + if (i > 0) + { + usesChromaLoopRestoration = true; + } + } + } + + if (usesLoopRestoration) + { + uint loopRestorationShift = reader.ReadLiteral(1); + if (sequenceHeader.Use128x128SuperBlock) + { + loopRestorationShift++; + } + else + { + if (reader.ReadBoolean()) + { + loopRestorationShift += reader.ReadLiteral(1); + } + } + + frameInfo.LoopRestorationParameters[0].Size = ObuConstants.RestorationMaxTileSize >> (int)(2 - loopRestorationShift); + int uvShift = 0; + if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && usesChromaLoopRestoration) + { + uvShift = (int)reader.ReadLiteral(1); + } + + frameInfo.LoopRestorationParameters[1].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift; + frameInfo.LoopRestorationParameters[2].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift; + } } + /// + /// See section 5.9.19. + /// private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - _ = planesCount; + ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameInfo.CdefParameters; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) { - frameInfo.CdefParameters.BitCount = 0; - frameInfo.CdefParameters.YStrength[0] = 0; - frameInfo.CdefParameters.YStrength[4] = 0; - frameInfo.CdefParameters.UVStrength[0] = 0; - frameInfo.CdefParameters.UVStrength[4] = 0; - frameInfo.CdefParameters.Damping = 0; + cdefInfo.BitCount = 0; + cdefInfo.YStrength[0] = 0; + cdefInfo.YStrength[4] = 0; + cdefInfo.UvStrength[0] = 0; + cdefInfo.UvStrength[4] = 0; + cdefInfo.Damping = 0; return; } - // TODO: Parse more stuff. + cdefInfo.Damping = (int)reader.ReadLiteral(2) + 3; + cdefInfo.BitCount = (int)reader.ReadLiteral(2); + for (int i = 0; i < (1 << frameInfo.CdefParameters.BitCount); i++) + { + cdefInfo.YStrength[i] = (int)reader.ReadLiteral(6); + + if (planesCount > 1) + { + cdefInfo.UvStrength[i] = (int)reader.ReadLiteral(6); + } + } } private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) { - _ = reader; - _ = sequenceHeader; - _ = frameInfo; if (isIntraFrame) { return; } - // TODO: Parse more stuff. + // Not applicable for INTRA frames. } private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame) @@ -1204,7 +1248,7 @@ private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSeq } else { - // TODO: Parse more stuff. + // Not applicable for INTRA frames. } if (frameInfo.SkipModeParameters.SkipModeAllowed) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs index 02ab7cd1e4..bf90f5021d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuRestorationType.cs @@ -3,7 +3,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -internal enum ObuRestorationType +internal enum ObuRestorationType : uint { - RestoreNone + None = 0, + Switchable = 1, + Weiner = 2, + SgrProj = 3, } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 652734c01b..4f94e26503 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -5,6 +5,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuSequenceHeader { + internal bool EnableFilterIntra { get; set; } + + internal bool EnableCdef { get; set; } + internal bool IsStillPicture { get; set; } internal bool IsReducedStillPictureHeader { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs new file mode 100644 index 0000000000..0843e3967c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -0,0 +1,583 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuWriter +{ + /// + /// Encode a single frame into OBU's. + /// + public static void Write(Stream stream, IAv1TileDecoder decoder) + { + MemoryStream bufferStream = new(100); + Av1BitStreamWriter writer = new(bufferStream); + WriteSequenceHeader(ref writer, decoder.SequenceHeader); + WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); + + bufferStream.Position = 0; + WriteFrameHeader(ref writer, decoder, true); + WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); + + bufferStream.Position = 0; + WriteTileGroup(ref writer, decoder.TileInfo); + WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); + } + + private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type) + { + writer.WriteBoolean(false); // Forbidden bit + writer.WriteLiteral((uint)type, 4); + writer.WriteBoolean(false); // Extension + writer.WriteBoolean(true); // HasSize + writer.WriteBoolean(false); // Reserved + } + + /// + /// Read OBU header and size. + /// + private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload, int length) + { + Av1BitStreamWriter writer = new(stream); + WriteObuHeader(ref writer, type); + _ = writer.WriteLittleEndianBytes128(length); + stream.Write(payload, 0, length); + } + + /// + /// Write trsainling bits to end on a byte boundary, these trailing bits start with a 1 and end with 0s. + /// + /// Write an additional byte, if already byte aligned before. + private static void WriteTrailingBits(ref Av1BitStreamWriter writer) + { + int bitsBeforeAlignment = 8 - (writer.BitPosition & 0x7); + writer.WriteLiteral(0, bitsBeforeAlignment); + } + + private static void AlignToByteBoundary(ref Av1BitStreamWriter writer) + { + while ((writer.BitPosition & 0x7) > 0) + { + writer.WriteBoolean(false); + } + } + + private static bool IsValidObuType(ObuType type) => type switch + { + ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or + ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or + ObuType.TileList or ObuType.Padding => true, + _ => false, + }; + + private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader) + { + writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3); + writer.WriteBoolean(true); // IsStillPicture + writer.WriteBoolean(true); // IsReducedStillPicture + writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, ObuConstants.LevelBits); + + // Frame width and Height + writer.WriteLiteral((uint)sequenceHeader.FrameWidthBits - 1, 4); + writer.WriteLiteral((uint)sequenceHeader.FrameHeightBits - 1, 4); + writer.WriteLiteral((uint)sequenceHeader.MaxFrameWidth - 1, sequenceHeader.FrameWidthBits); + writer.WriteLiteral((uint)sequenceHeader.MaxFrameHeight - 1, sequenceHeader.FrameHeightBits); + + // Video related flags removed + writer.WriteBoolean(sequenceHeader.Use128x128SuperBlock); + writer.WriteBoolean(sequenceHeader.EnableFilterIntra); + writer.WriteBoolean(sequenceHeader.EnableIntraEdgeFilter); + + // Video related flags removed + writer.WriteBoolean(sequenceHeader.EnableSuperResolution); + writer.WriteBoolean(sequenceHeader.EnableCdef); + writer.WriteBoolean(sequenceHeader.EnableRestoration); + WriteColorConfig(ref writer, sequenceHeader); + writer.WriteBoolean(sequenceHeader.AreFilmGrainingParametersPresent); + WriteTrailingBits(ref writer); + } + + private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader) + { + ObuColorConfig colorConfig = new(); + WriteBitDepth(ref writer, colorConfig, sequenceHeader); + if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) + { + writer.WriteBoolean(colorConfig.Monochrome); + } + + writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent + if (colorConfig.Monochrome) + { + writer.WriteBoolean(colorConfig.ColorRange); + return; + } + else if ( + colorConfig.ColorPrimaries == ObuColorPrimaries.Bt709 && + colorConfig.TransferCharacteristics == ObuTransferCharacteristics.Srgb && + colorConfig.MatrixCoefficients == ObuMatrixCoefficients.Identity) + { + colorConfig.ColorRange = true; + colorConfig.SubSamplingX = false; + colorConfig.SubSamplingY = false; + } + else + { + writer.WriteBoolean(colorConfig.ColorRange); + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == 12) + { + writer.WriteBoolean(colorConfig.SubSamplingX); + if (colorConfig.SubSamplingX) + { + writer.WriteBoolean(colorConfig.SubSamplingY); + } + } + + if (colorConfig.SubSamplingX && colorConfig.SubSamplingY) + { + writer.WriteLiteral((uint)colorConfig.ChromaSamplePosition, 2); + } + } + + writer.WriteBoolean(colorConfig.HasSeparateUvDeltaQ); + } + + private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) + { + bool hasHighBitDepth = colorConfig.BitDepth > 8; + writer.WriteBoolean(hasHighBitDepth); + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) + { + writer.WriteBoolean(colorConfig.BitDepth == 12); + } + } + + private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + { + bool useSuperResolution = false; + if (sequenceHeader.EnableSuperResolution) + { + writer.WriteBoolean(useSuperResolution); + } + + if (useSuperResolution) + { + writer.WriteLiteral((uint)frameInfo.FrameSize.SuperResolutionDenominator - ObuConstants.SuperResolutionScaleDenominatorMinimum, ObuConstants.SuperResolutionScaleBits); + } + } + + private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + { + bool renderSizeAndFrameSizeDifferent = false; + writer.WriteBoolean(false); + if (renderSizeAndFrameSizeDifferent) + { + writer.WriteLiteral((uint)frameInfo.FrameSize.RenderWidth - 1, 16); + writer.WriteLiteral((uint)frameInfo.FrameSize.RenderHeight - 1, 16); + } + } + + private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + bool foundReference = false; + for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) + { + writer.WriteBoolean(foundReference); + if (foundReference) + { + // Take values over from reference frame + break; + } + } + + if (!foundReference) + { + WriteFrameSize(ref writer, sequenceHeader, frameInfo, frameSizeOverrideFlag); + WriteRenderSize(ref writer, frameInfo); + } + else + { + WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); + } + } + + private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + { + if (frameSizeOverrideFlag) + { + writer.WriteLiteral((uint)frameInfo.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1); + writer.WriteLiteral((uint)frameInfo.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1); + } + + WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); + } + + private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo) + { + Guard.IsTrue(tileInfo.HasUniformTileSpacing, nameof(tileInfo.HasUniformTileSpacing), "NON uniform_tile_spacing_flag not supported yet."); + + writer.WriteBoolean(tileInfo.HasUniformTileSpacing); + if (tileInfo.HasUniformTileSpacing) + { + for (int i = 0; i < tileInfo.TileColumnCountLog2; i++) + { + writer.WriteBoolean(true); + } + + if (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount) + { + writer.WriteBoolean(false); + } + + for (int i = 0; i < tileInfo.TileRowCountLog2; i++) + { + writer.WriteBoolean(true); + } + + if (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount) + { + writer.WriteBoolean(false); + } + } + else + { + throw new NotImplementedException("NON uniform_tile_spacing_flag not supported yet."); + } + + if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0) + { + writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2); + writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2); + } + } + + private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + uint previousFrameId = 0; + bool isIntraFrame = true; + int idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3; + writer.WriteBoolean(frameInfo.DisableCdfUpdate); + if (frameInfo.AllowScreenContentTools) + { + writer.WriteBoolean(frameInfo.AllowScreenContentTools); + } + + if (frameInfo.AllowScreenContentTools) + { + if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + { + writer.WriteBoolean(frameInfo.ForceIntegerMotionVector); + } + } + + bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); + if (havePreviousFrameId) + { + previousFrameId = frameInfo.CurrentFrameId; + } + + if (sequenceHeader.IsFrameIdNumbersPresent) + { + writer.WriteLiteral(frameInfo.CurrentFrameId, idLength); + if (havePreviousFrameId) + { + uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? + frameInfo.CurrentFrameId - previousFrameId : + (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); + if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) + { + throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); + } + } + + int diffLength = sequenceHeader.DeltaFrameIdLength; + for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + { + if (frameInfo.CurrentFrameId > (1U << diffLength)) + { + if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || + frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) + { + frameInfo.ReferenceValid[i] = false; + } + } + else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && + frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength)))) + { + frameInfo.ReferenceValid[i] = false; + } + } + } + + writer.WriteLiteral(frameInfo.OrderHint, sequenceHeader.OrderHintInfo.OrderHintBits); + + if (!isIntraFrame && !frameInfo.ErrorResilientMode) + { + writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, ObuConstants.PimaryReferenceBits); + } + + // Skipping, as no decoder info model present + frameInfo.AllowHighPrecisionMotionVector = false; + frameInfo.UseReferenceFrameMotionVectors = false; + frameInfo.AllowIntraBlockCopy = false; + if (frameInfo.FrameType != ObuFrameType.SwitchFrame && !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + { + writer.WriteLiteral(frameInfo.RefreshFrameFlags, 8); + } + + if (isIntraFrame) + { + WriteFrameSize(ref writer, sequenceHeader, frameInfo, false); + WriteRenderSize(ref writer, frameInfo); + if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) + { + if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) + { + writer.WriteBoolean(frameInfo.AllowIntraBlockCopy); + } + } + } + + if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + { + SetupPastIndependence(frameInfo); + } + + // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); + WriteTileInfo(ref writer, frameInfo.TilesInfo); + WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); + WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount); + WriteFrameDeltaQParameters(ref writer, frameInfo); + WriteFrameDeltaLoopFilterParameters(ref writer, frameInfo); + + WriteLoopFilterParameters(ref writer, sequenceHeader, frameInfo, planesCount); + WriteCdefParameters(ref writer, sequenceHeader, frameInfo, planesCount); + WriteLoopRestorationParameters(ref writer, sequenceHeader, frameInfo, planesCount); + WriteTransformMode(ref writer, frameInfo); + + // Not applicable for INTRA frames. + // WriteFrameReferenceMode(ref writer, frameInfo.ReferenceMode, isIntraFrame); + // WriteSkipModeParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); + writer.WriteBoolean(frameInfo.ReducedTransformSet); + + // Not applicable for INTRA frames. + // WriteGlobalMotionParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame); + WriteFilmGrainFilterParameters(ref writer, frameInfo.FilmGrainParameters); + } + + private static void SetupPastIndependence(ObuFrameHeader frameInfo) + { + // TODO: Initialize the loop filter parameters. + } + + private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) + => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + + private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) + { + if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) + { + int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; + int qIndex = baseQIndex + data; + return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ); + } + else + { + return baseQIndex; + } + } + + private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecoder decoder, bool writeTrailingBits) + { + ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; + ObuFrameHeader frameInfo = decoder.FrameInfo; + int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int startBitPosition = writer.BitPosition; + WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); + if (writeTrailingBits) + { + WriteTrailingBits(ref writer); + } + + AlignToByteBoundary(ref writer); + + int endPosition = writer.BitPosition; + int headerBytes = (endPosition - startBitPosition) / 8; + return headerBytes; + } + + private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo) + { + int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; + int startBitPosition = writer.BitPosition; + bool tileStartAndEndPresentFlag = tileCount != 0; + writer.WriteBoolean(tileStartAndEndPresentFlag); + + uint tileGroupStart = 0U; + uint tileGroupEnd = (uint)tileCount - 1U; + if (tileCount != 1) + { + int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount); + writer.WriteLiteral(tileGroupStart, tileBits); + writer.WriteLiteral(tileGroupEnd, tileBits); + } + + AlignToByteBoundary(ref writer); + int endBitPosition = writer.BitPosition; + int headerBytes = (endBitPosition - startBitPosition) / 8; + return headerBytes; + } + + private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) + { + bool isCoded = deltaQ == 0; + writer.WriteBoolean(isCoded); + if (isCoded) + { + writer.WriteSignedFromUnsigned(deltaQ, 7); + } + + return deltaQ; + } + + private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + { + if (frameInfo.QuantizationParameters.BaseQIndex > 0) + { + writer.WriteBoolean(frameInfo.DeltaQParameters.IsPresent); + } + + if (frameInfo.DeltaQParameters.IsPresent) + { + writer.WriteLiteral((uint)frameInfo.DeltaQParameters.Resolution, 2); + } + } + + private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + { + if (frameInfo.DeltaQParameters.IsPresent) + { + if (!frameInfo.AllowIntraBlockCopy) + { + writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.IsPresent); + } + + if (frameInfo.DeltaLoopFilterParameters.IsPresent) + { + writer.WriteLiteral((uint)frameInfo.DeltaLoopFilterParameters.Resolution, 2); + writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.Multi); + } + } + } + + /// + /// See section 5.9.12. + /// + private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) + { + writer.WriteLiteral((uint)quantParams.BaseQIndex, 8); + WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]); + if (planesCount > 1) + { + bool areUvDeltaDifferent = false; + WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]); + WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]); + if (areUvDeltaDifferent) + { + WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.V]); + WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.V]); + } + } + + writer.WriteBoolean(quantParams.IsUsingQMatrix); + if (quantParams.IsUsingQMatrix) + { + writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4); + writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4); + if (colorInfo.HasSeparateUvDeltaQ) + { + writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4); + } + } + } + + private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentatino not supported yet."); + writer.WriteBoolean(false); + } + + private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + _ = writer; + _ = sequenceHeader; + _ = frameInfo; + _ = planesCount; + + // TODO: Parse more stuff. + } + + private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + { + if (!frameInfo.CodedLossless) + { + writer.WriteBoolean(frameInfo.TransformMode == Av1TransformMode.Select); + } + } + + private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + _ = writer; + _ = sequenceHeader; + _ = frameInfo; + _ = planesCount; + + // TODO: Parse more stuff. + } + + private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + { + _ = writer; + _ = sequenceHeader; + _ = frameInfo; + _ = planesCount; + + // TODO: Parse more stuff. + } + + private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) + { + _ = writer; + _ = sequenceHeader; + _ = frameInfo; + + // Nothing to be written for INTRA frames. + Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + } + + private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, bool isIntraFrame) + { + _ = writer; + + // Nothing to be written for INTRA frames. + Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + } + + private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, bool isIntraFrame) + { + _ = writer; + + // Nothing to be written for INTRA frames. + Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + } + + private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuFilmGrainParameters filmGrainInfo) + { + _ = writer; + _ = filmGrainInfo; + + // Film grain filter not supported yet + } +} From 74677ed7299b2aef2990208e00970738b60006f9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 1 May 2024 21:21:43 +0200 Subject: [PATCH 055/190] Fix bitstream writer --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 71 ++++--- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 182 ++++++++++++++++++ 2 files changed, 228 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index 28fa923be9..3cac9f2d4a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -33,42 +33,28 @@ public void Flush() public void WriteLiteral(uint value, int bitCount) { - int shift = 24; - uint padded = value << ((32 - bitCount) - this.bitOffset); - while ((bitCount + this.bitOffset) >= 8) + for (int bit = bitCount - 1; bit >= 0; bit--) { - byte current = (byte)(((padded >> shift) & 0xff) | this.buffer); - this.stream.WriteByte(current); - shift -= 8; - bitCount -= 8; - this.buffer = 0; - this.bitOffset = 0; - } - - if (bitCount > 0) - { - this.buffer = (byte)(((padded >> shift) & 0xff) | this.buffer); - this.bitOffset += bitCount; + this.WriteBit((byte)((value >> bit) & 0x1)); } } internal void WriteBoolean(bool value) { byte boolByte = value ? (byte)1 : (byte)0; - this.buffer = (byte)(((boolByte << (7 - this.bitOffset)) & 0xff) | this.buffer); - this.bitOffset++; - if (this.bitOffset == WordSize) - { - this.stream.WriteByte(this.buffer); - this.bitOffset = 0; - } + this.WriteBit(boolByte); } public void WriteSignedFromUnsigned(int signedValue, int n) { // See section 4.10.6 of the AV1-Specification - uint value = unchecked((uint)signedValue); - this.WriteLiteral(value, n + 1); + ulong value = (ulong)signedValue; + if (signedValue < 0) + { + value += 1UL << n; + } + + this.WriteLiteral((uint)value, n); } public void WriteLittleEndianBytes128(uint value) @@ -79,12 +65,47 @@ public void WriteLittleEndianBytes128(uint value) } else if (value < 0x8000U) { + this.WriteLiteral((value & 0x7FU) | 0x80U, 8); this.WriteLiteral(value >> 7, 8); - this.WriteLiteral(value & 0x80, 8); } else { throw new NotImplementedException("No such large values yet."); } } + + internal void WriteNonSymmetric(uint value, uint numberOfSymbols) + { + // See section 4.10.7 of the AV1-Specification + if (numberOfSymbols <= 1) + { + return; + } + + int w = (int)(Av1Math.FloorLog2(numberOfSymbols) + 1); + uint m = (uint)((1 << w) - numberOfSymbols); + if (value < m) + { + this.WriteLiteral(value, w - 1); + } + else + { + uint extraBit = ((value + m) >> 1) - value; + uint k = (value + m - extraBit) >> 1; + this.WriteLiteral(k, w - 1); + this.WriteLiteral(extraBit, 1); + } + } + + private void WriteBit(byte value) + { + this.buffer = (byte)(((value << (7 - this.bitOffset)) & 0xff) | this.buffer); + this.bitOffset++; + if (this.bitOffset == WordSize) + { + this.stream.WriteByte(this.buffer); + this.buffer = 0; + this.bitOffset = 0; + } + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 263182a6dd..2519cdf491 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -83,4 +83,186 @@ public void WriteAsLiteral(uint value, int bitCount) uint actual = reader.ReadLiteral(bitCount); Assert.Equal(value, actual); } + + [Theory] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(6)] + [InlineData(7)] + [InlineData(8)] + [InlineData(16)] + public void ReadLiteralRainbowArray(int bitCount) + { + uint[] values = Enumerable.Range(0, (1 << bitCount) - 1).Select(i => (uint)i).ToArray(); + using MemoryStream stream = new(280); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteLiteral(values[i], bitCount); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + uint[] actuals = new uint[values.Length]; + for (int i = 0; i < values.Length; i++) + { + uint actual = reader.ReadLiteral(bitCount); + actuals[i] = actual; + } + + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData(4, 6, 4, 9, 14)] + [InlineData(8, 42, 8, 189, 63)] + [InlineData(8, 52, 18, 255, 241)] + [InlineData(16, 4050, 16003, 503, 814)] + public void ReadWriteAsLiteralArray(int bitCount, uint val1, uint val2, uint val3, uint val4) + { + uint[] values = [val1, val2, val3, val4]; + using MemoryStream stream = new(80); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteLiteral(values[i], bitCount); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + for (int i = 0; i < values.Length; i++) + { + uint actual = reader.ReadLiteral(bitCount); + Assert.NotEqual(0U, actual); + Assert.Equal(values[i], actual); + } + } + + [Theory] + + [InlineData(4, 0, 1, 2, 3)] + [InlineData(5, 0, 1, 2, 3)] + [InlineData(8, 0, 1, 2, 3)] + [InlineData(8, 4, 5, 6, 7)] + [InlineData(16, 15, 0, 5, 8)] + public void ReadWriteAsNonSymmetricArray(uint numberOfSymbols, uint val1, uint val2, uint val3, uint val4) + { + uint[] values = [val1, val2, val3, val4]; + using MemoryStream stream = new(80); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteNonSymmetric(values[i], numberOfSymbols); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + uint[] actuals = new uint[4]; + for (int i = 0; i < values.Length; i++) + { + ulong actual = reader.ReadNonSymmetric(numberOfSymbols); + actuals[i] = (uint)actual; + // Assert.NotEqual(0UL, actual); + } + + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + [InlineData(7)] + [InlineData(8)] + public void ReadSignedRainbowArray(int bitCount) + { + int maxValue = (1 << (bitCount - 1)) - 1; + int[] values = Enumerable.Range(-maxValue, maxValue).ToArray(); + using MemoryStream stream = new(280); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteSignedFromUnsigned(values[i], bitCount); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + int[] actuals = new int[values.Length]; + for (int i = 0; i < values.Length; i++) + { + int actual = reader.ReadSignedFromUnsigned(bitCount); + actuals[i] = actual; + } + + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData(5, 6, 4, -7, -2)] + [InlineData(7, 26, -8, -19, -26)] + [InlineData(8, 52, 127, -127, -21)] + [InlineData(16, -4050, -16003, -503, 8414)] + public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int val4) + { + int[] values = [val1, val2, val3, val4]; + using MemoryStream stream = new(80); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteSignedFromUnsigned(values[i], bitCount); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + int[] actuals = new int[4]; + for (int i = 0; i < values.Length; i++) + { + int actual = reader.ReadSignedFromUnsigned(bitCount); + actuals[i] = actual; + // Assert.NotEqual(0, actual); + } + + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData(4, 6, 4, 9, 14)] + [InlineData(8, 42, 8, 189, 63)] + [InlineData(8, 52, 18, 255, 241)] + [InlineData(16, 4050, 16003, 503, 8414)] + public void ReadWriteLittleEndianBytes128Array(uint val0, uint val1, uint val2, uint val3, uint val4) + { + uint[] values = [val0, val1, val2, val3, val4]; + using MemoryStream stream = new(80); + Av1BitStreamWriter writer = new(stream); + for (int i = 0; i < values.Length; i++) + { + writer.WriteLittleEndianBytes128(values[i]); + } + + writer.Flush(); + + // Read the written value back. + Av1BitStreamReader reader = new(stream.GetBuffer()); + uint[] actuals = new uint[5]; + for (int i = 0; i < values.Length; i++) + { + ulong actual = reader.ReadLittleEndianBytes128(out int length); + actuals[i] = (uint)actual; + Assert.NotEqual(0UL, actual); + } + + Assert.Equal(values, actuals); + } } From ef572ac98bda1df29a527692d14de3eac7f08b4b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 3 May 2024 11:35:01 +0200 Subject: [PATCH 056/190] ObuHeader round trips --- .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 26 +++--- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 68 +++++++------- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 67 ++++++++++++-- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 2 +- .../Formats/Heif/Av1/Av1TileDecoderStub.cs | 30 +++++++ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 90 ++++++++++++++++++- 7 files changed, 230 insertions(+), 59 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index feafbaccd0..c70c189a55 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -5,31 +5,31 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuColorConfig { - internal bool IsColorDescriptionPresent { get; set; } + public bool IsColorDescriptionPresent { get; set; } - internal int ChannelCount { get; set; } + public int ChannelCount { get; set; } - internal bool Monochrome { get; set; } + public bool Monochrome { get; set; } - internal ObuColorPrimaries ColorPrimaries { get; set; } + public ObuColorPrimaries ColorPrimaries { get; set; } - internal ObuTransferCharacteristics TransferCharacteristics { get; set; } + public ObuTransferCharacteristics TransferCharacteristics { get; set; } - internal ObuMatrixCoefficients MatrixCoefficients { get; set; } + public ObuMatrixCoefficients MatrixCoefficients { get; set; } - internal bool ColorRange { get; set; } + public bool ColorRange { get; set; } - internal bool SubSamplingX { get; set; } + public bool SubSamplingX { get; set; } - internal bool SubSamplingY { get; set; } + public bool SubSamplingY { get; set; } - internal bool HasSeparateUvDelta { get; set; } + public bool HasSeparateUvDelta { get; set; } - internal ObuChromoSamplePosition ChromaSamplePosition { get; set; } + public ObuChromoSamplePosition ChromaSamplePosition { get; set; } - internal int BitDepth { get; set; } + public int BitDepth { get; set; } - internal bool HasSeparateUvDeltaQ { get; set; } + public bool HasSeparateUvDeltaQ { get; set; } public Av1ColorFormat GetColorFormat() { diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index f4a134c526..2c018101de 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -372,6 +372,10 @@ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig c { colorConfig.BitDepth = hasHighBitDepth ? 10 : 8; } + else + { + colorConfig.BitDepth = 8; + } } private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) @@ -1282,7 +1286,7 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt private static bool IsValidSequenceLevel(int sequenceLevelIndex) => sequenceLevelIndex is < 24 or 31; - private static int TileLog2(int blockSize, int target) + public static int TileLog2(int blockSize, int target) { int k; for (k = 0; (blockSize << k) < target; k++) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 4f94e26503..8bb6a11215 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -5,71 +5,71 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuSequenceHeader { - internal bool EnableFilterIntra { get; set; } + public bool EnableFilterIntra { get; set; } - internal bool EnableCdef { get; set; } + public bool EnableCdef { get; set; } - internal bool IsStillPicture { get; set; } + public bool IsStillPicture { get; set; } - internal bool IsReducedStillPictureHeader { get; set; } + public bool IsReducedStillPictureHeader { get; set; } - internal ObuSequenceProfile SequenceProfile { get; set; } + public ObuSequenceProfile SequenceProfile { get; set; } - internal ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1]; + public ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1]; - internal bool InitialDisplayDelayPresentFlag { get; set; } + public bool InitialDisplayDelayPresentFlag { get; set; } - internal bool DecoderModelInfoPresentFlag { get; set; } + public bool DecoderModelInfoPresentFlag { get; set; } - internal object? TimingInfo { get; set; } + public object? TimingInfo { get; set; } - internal bool IsFrameIdNumbersPresent { get; set; } + public bool IsFrameIdNumbersPresent { get; set; } - internal int FrameWidthBits { get; set; } + public int FrameWidthBits { get; set; } - internal int FrameHeightBits { get; set; } + public int FrameHeightBits { get; set; } - internal int MaxFrameWidth { get; set; } + public int MaxFrameWidth { get; set; } - internal int MaxFrameHeight { get; set; } + public int MaxFrameHeight { get; set; } - internal bool Use128x128SuperBlock { get; set; } + public bool Use128x128SuperBlock { get; set; } - internal Av1BlockSize SuperBlockSize { get; set; } + public Av1BlockSize SuperBlockSize { get; set; } - internal int ModeInfoSize { get; set; } + public int ModeInfoSize { get; set; } - internal int SuperBlockSizeLog2 { get; set; } + public int SuperBlockSizeLog2 { get; set; } - internal int FilterIntraLevel { get; set; } + public int FilterIntraLevel { get; set; } - internal bool EnableIntraEdgeFilter { get; set; } + public bool EnableIntraEdgeFilter { get; set; } - internal ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo(); + public ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo(); - internal bool EnableInterIntraCompound { get; set; } + public bool EnableInterIntraCompound { get; set; } - internal bool EnableMaskedCompound { get; set; } + public bool EnableMaskedCompound { get; set; } - internal bool EnableWarpedMotion { get; set; } + public bool EnableWarpedMotion { get; set; } - internal bool EnableDualFilter { get; set; } + public bool EnableDualFilter { get; set; } - internal int SequenceForceIntegerMotionVector { get; set; } + public int SequenceForceIntegerMotionVector { get; set; } - internal int SequenceForceScreenContentTools { get; set; } + public int SequenceForceScreenContentTools { get; set; } - internal bool EnableSuperResolution { get; set; } + public bool EnableSuperResolution { get; set; } - internal int CdefLevel { get; set; } + public int CdefLevel { get; set; } - internal bool EnableRestoration { get; set; } + public bool EnableRestoration { get; set; } - internal ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig(); + public ObuColorConfig ColorConfig { get; set; } = new ObuColorConfig(); - internal bool AreFilmGrainingParametersPresent { get; set; } + public bool AreFilmGrainingParametersPresent { get; set; } - internal int FrameIdLength { get; set; } + public int FrameIdLength { get; set; } - internal int DeltaFrameIdLength { get; set; } + public int DeltaFrameIdLength { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 0843e3967c..9437cc6ea4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -14,15 +14,20 @@ public static void Write(Stream stream, IAv1TileDecoder decoder) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); + WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); + WriteSequenceHeader(ref writer, decoder.SequenceHeader); + writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; WriteFrameHeader(ref writer, decoder, true); + writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; WriteTileGroup(ref writer, decoder.TileInfo); + writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); } @@ -42,7 +47,7 @@ private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span> 5; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; + superBlockShift = 5; + } + else + { + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4; + superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4; + superBlockShift = 4; + } + + int superBlockSize = superBlockShift + 2; + int maxTileAreaOfSuperBlock = ObuConstants.MaxTileArea >> (2 * superBlockSize); + + tileInfo.MaxTileWidthSuperBlock = ObuConstants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperBlock = (ObuConstants.MaxTileArea / ObuConstants.MaxTileWidth) >> superBlockSize; + tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); + tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount)); + tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); writer.WriteBoolean(tileInfo.HasUniformTileSpacing); if (tileInfo.HasUniformTileSpacing) @@ -243,7 +271,34 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuTileInfo til } else { - throw new NotImplementedException("NON uniform_tile_spacing_flag not supported yet."); + int startSuperBlock = 0; + int i = 0; + for (; startSuperBlock < superBlockColumnCount; i++) + { + uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superBlockShift) - startSuperBlock); + uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth); + startSuperBlock += (int)widthInSuperBlocks; + } + + if (startSuperBlock != superBlockColumnCount) + { + throw new ImageFormatException("Super block tiles width does not add up to total width."); + } + + startSuperBlock = 0; + for (i = 0; startSuperBlock < superBlockRowCount; i++) + { + uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superBlockShift) - startSuperBlock); + uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight); + startSuperBlock += (int)heightInSuperBlocks; + } + + if (startSuperBlock != superBlockRowCount) + { + throw new ImageFormatException("Super block tiles height does not add up to total height."); + } } if (tileInfo.TileColumnCountLog2 > 0 || tileInfo.TileRowCountLog2 > 0) @@ -346,7 +401,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, } // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); - WriteTileInfo(ref writer, frameInfo.TilesInfo); + WriteTileInfo(ref writer, sequenceHeader, frameInfo, frameInfo.TilesInfo); WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount); WriteFrameDeltaQParameters(ref writer, frameInfo); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 2519cdf491..c2f9071990 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -237,7 +237,7 @@ public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int } [Theory] - [InlineData(4, 6, 4, 9, 14)] + [InlineData(4, 6, 7, 9, 14)] [InlineData(8, 42, 8, 189, 63)] [InlineData(8, 52, 18, 255, 241)] [InlineData(16, 4050, 16003, 503, 8414)] diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs new file mode 100644 index 0000000000..36ae4cfe84 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class Av1TileDecoderStub : IAv1TileDecoder +{ + public bool SequenceHeaderDone { get; set; } + + public bool ShowExistingFrame { get; set; } + + public bool SeenFrameHeader { get; set; } + + public ObuFrameHeader FrameInfo { get; } = new ObuFrameHeader(); + + public ObuSequenceHeader SequenceHeader { get; } = new ObuSequenceHeader(); + + public ObuTileInfo TileInfo { get; } = new ObuTileInfo(); + + public void DecodeTile(int tileNum) + { + } + + public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) + { + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 398f84b853..69f1bf63e0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Reflection; +using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -10,10 +12,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; public class ObuFrameHeaderTests { [Theory] - // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D, false)] - // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1, false)] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, false)] - public void ReadFrameHeader(string filename, int fileOffset, int blockSize, bool isAnnexB) + // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + public void ReadFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); @@ -29,4 +31,84 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize, bool Assert.False(decoder.SeenFrameHeader); } + /* [Theory] + // [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + // public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) + // { + // // Assign + // string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + // byte[] content = File.ReadAllBytes(filePath); + // Span span = content.AsSpan(fileOffset, blockSize); + // IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + // Av1BitStreamReader reader = new(span); + + // // Act 1 + // ObuReader.Read(ref reader, blockSize, tileDecoder); + + // // Assign 2 + // MemoryStream encoded = new(); + + // // Act 2 + // ObuWriter.Write(encoded, tileDecoder); + + // // Assert + // Assert.Equal(span, encoded.ToArray()); + //} + */ + + [Theory] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize) + { + // Assign + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + byte[] content = File.ReadAllBytes(filePath); + Span span = content.AsSpan(fileOffset, blockSize); + IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + Av1BitStreamReader reader = new(span); + + // Act 1 + ObuReader.Read(ref reader, blockSize, tileDecoder); + + // Assign 2 + MemoryStream encoded = new(); + + // Act 2 + ObuWriter.Write(encoded, tileDecoder); + + // Assign 2 + Span encodedBuffer = encoded.ToArray(); + IAv1TileDecoder tileDecoder2 = new Av1TileDecoderStub(); + Av1BitStreamReader reader2 = new(span); + + // Act 2 + ObuReader.Read(ref reader2, encodedBuffer.Length, tileDecoder2); + + // Assert + Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader.ColorConfig), PrettyPrintProperties(tileDecoder2.SequenceHeader.ColorConfig)); + Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader), PrettyPrintProperties(tileDecoder2.SequenceHeader)); + Assert.Equal(PrettyPrintProperties(tileDecoder.FrameInfo), PrettyPrintProperties(tileDecoder2.FrameInfo)); + Assert.Equal(PrettyPrintProperties(tileDecoder.TileInfo), PrettyPrintProperties(tileDecoder2.TileInfo)); + } + + private static string PrettyPrintProperties(object obj) + { + StringBuilder builder = new(); + builder.Append(obj.GetType().Name); + builder.AppendLine("{"); + MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null); + foreach (MemberInfo member in properties) + { + if (member is PropertyInfo property) + { + builder.Append(property.Name); + builder.Append(" = "); + object value = property.GetValue(obj) ?? "NULL"; + builder.AppendLine(value.ToString()); + } + } + + builder.AppendLine("}"); + return builder.ToString(); + } } From 95d9f0916a4d8e430885a3b4cfd7841df41cf8d8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 12 May 2024 16:21:49 +0200 Subject: [PATCH 057/190] Initial symbol parsing --- .../Av1/Symbol/Av1DefaultDistributions.cs | 27 ++ .../Heif/Av1/Symbol/Av1SymbolDecoder.cs | 12 + .../Heif/Av1/Symbol/Av1SymbolEncoder.cs | 12 + .../Heif/Av1/Symbol/Av1SymbolReader.cs | 243 ++++++++++++++++++ .../Heif/Av1/Symbol/Av1SymbolWriter.cs | 190 ++++++++++++++ .../Formats/Heif/Av1/SymbolTest.cs | 234 +++++++++++++++++ 6 files changed, 718 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs new file mode 100644 index 0000000000..84832bb7b8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal static class Av1DefaultDistributions +{ + public static uint[] YMode => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] UvModeCflNotAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] UvModeCflAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] AngleDelta => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] IntraBlockCopy => [30531, 0, 0]; + + public static uint[] PartitionWidth8 => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] PartitionWidth16 => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] PartitionWidth32 => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] PartitionWidth64 => [Av1SymbolReader.CdfProbabilityTop, 0]; + + public static uint[] SegmentId => [Av1SymbolReader.CdfProbabilityTop, 0]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs new file mode 100644 index 0000000000..53ab655220 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1SymbolDecoder +{ + private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + public bool ReadUseIntraBlockCopySymbol(ref Av1SymbolReader reader) + => reader.ReadSymbol(this.tileIntraBlockCopy, 2) > 0; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs new file mode 100644 index 0000000000..6ff2807ece --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1SymbolEncoder +{ + private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + public void WriteUseIntraBlockCopySymbol(Av1SymbolWriter writer, bool value) + => writer.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs new file mode 100644 index 0000000000..7012a973c4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -0,0 +1,243 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal ref struct Av1SymbolReader +{ + internal const int CdfProbabilityTop = 1 << CdfProbabilityBitCount; + internal const int ProbabilityMinimum = 4; + internal const int CdfShift = 15 - CdfProbabilityBitCount; + internal const int ProbabilityShift = 6; + internal static readonly int[] NumberOfSymbols2Speed = [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; + + private const int CdfProbabilityBitCount = 15; + private const int DecoderWindowsSize = 32; + private const int LotsOfBits = 0x4000; + + private readonly Span buffer; + private int position; + + /* + * The difference between the high end of the current range, (low + rng), and + * the coded value, minus 1. + * This stores up to OD_EC_WINDOW_SIZE bits of that difference, but the + * decoder only uses the top 16 bits of the window to decode the next symbol. + * As we shift up during renormalization, if we don't have enough bits left in + * the window to fill the top 16, we'll read in more bits of the coded + * value. + */ + private uint difference; + + // The number of values in the current range. + private uint range; + + // The number of bits in the current value. + private int count; + + public Av1SymbolReader(Span span) + { + this.buffer = span; + this.position = 0; + this.difference = (1U << (DecoderWindowsSize - 1)) - 1; + this.range = 0x8000; + this.count = -15; + this.Refill(); + } + + public int ReadSymbol(uint[] probabilities, int numberOfSymbols) + { + int value = this.DecodeIntegerQ15(probabilities, numberOfSymbols); + UpdateCdf(probabilities, value, numberOfSymbols); + return value; + } + + public int ReadLiteral(int bitCount) + { + const uint prob = (0x7FFFFFU - (128 << 15) + 128) >> 8; + int literal = 0; + for (int bit = bitCount - 1; bit >= 0; bit--) + { + if (this.DecodeBoolQ15(prob)) + { + literal |= 1 << bit; + } + } + + return literal; + } + + /// + /// Decode a single binary value. + /// + /// The probability that the bit is one, scaled by 32768. + private bool DecodeBoolQ15(uint frequency) + { + uint dif; + uint vw; + uint range; + uint newRange; + uint v; + bool ret; + + // assert(0 < f); + // assert(f < 32768U); + dif = this.difference; + range = this.range; + + // assert(dif >> (DecoderWindowsSize - 16) < r); + // assert(32768U <= r); + v = ((range >> 8) * (frequency >> ProbabilityShift)) >> (7 - ProbabilityShift); + v += ProbabilityMinimum; + vw = v << (DecoderWindowsSize - 16); + ret = true; + newRange = v; + if (dif >= vw) + { + newRange = range - v; + dif -= vw; + ret = false; + } + + this.Normalize(dif, newRange); + return ret; + } + + /// + /// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. + /// + /// + /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range + /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). + /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. + /// + /// + /// The number of symbols in the alphabet. + /// This should be at most 16. + /// + /// The decoded symbol. + private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) + { + uint c; + uint u; + uint v; + int ret; + + uint dif = this.difference; + uint r = this.range; + int n = numberOfSymbols - 1; + + DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r)); + DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last value in probability array needs to be zero."); + DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); + DebugGuard.MustBeGreaterThanOrEqualTo(7 - ProbabilityShift - CdfShift, 0, nameof(CdfShift)); + c = dif >> (DecoderWindowsSize - 16); + v = r; + ret = -1; + do + { + u = v; + v = ((r >> 8) * (probabilities[++ret] >> ProbabilityShift)) >> (7 - ProbabilityShift - CdfShift); + v += (uint)(ProbabilityMinimum * (n - ret)); + } + while (c < v); + + DebugGuard.MustBeLessThan(v, u, nameof(v)); + DebugGuard.MustBeLessThan(u, r, nameof(u)); + r = u - v; + dif -= v << (DecoderWindowsSize - 16); + this.Normalize(dif, r); + return ret; + } + + /// + /// Takes updated dif and range values, renormalizes them so that + /// has value between 32768 and 65536 (reading more bytes from the stream into dif if + /// necessary), and stores them back in the decoder context. + /// + private void Normalize(uint dif, uint rng) + { + int d; + + // assert(rng <= 65535U); + /*The number of leading zeros in the 16-bit binary representation of rng.*/ + d = 15 - Av1Math.MostSignificantBit(rng); + /*d bits in dec->dif are consumed.*/ + this.count -= d; + /*This is equivalent to shifting in 1's instead of 0's.*/ + this.difference = ((dif + 1) << d) - 1; + this.range = rng << d; + if (this.count < 0) + { + this.Refill(); + } + } + + private void Refill() + { + int s; + uint dif = this.difference; + int cnt = this.count; + int position = this.position; + int end = this.buffer.Length; + s = DecoderWindowsSize - 9 - (cnt + 15); + for (; s >= 0 && position < end; s -= 8, position++) + { + /*Each time a byte is inserted into the window (dif), bptr advances and cnt + is incremented by 8, so the total number of consumed bits (the return + value of od_ec_dec_tell) does not change.*/ + DebugGuard.MustBeLessThan(s, DecoderWindowsSize - 8, nameof(s)); + dif ^= (uint)this.buffer[0] << s; + cnt += 8; + } + + if (position >= end) + { + /* + * We've reached the end of the buffer. It is perfectly valid for us to need + * to fill the window with additional bits past the end of the buffer (and + * this happens in normal operation). These bits should all just be taken + * as zero. But we cannot increment bptr past 'end' (this is undefined + * behavior), so we start to increment dec->tell_offs. We also don't want + * to keep testing bptr against 'end', so we set cnt to OD_EC_LOTS_OF_BITS + * and adjust dec->tell_offs so that the total number of unconsumed bits in + * the window (dec->cnt - dec->tell_offs) does not change. This effectively + * puts lots of zero bits into the window, and means we won't try to refill + * it from the buffer for a very long time (at which point we'll put lots + * of zero bits into the window again). + */ + cnt = LotsOfBits; + } + + this.difference = dif; + this.count = cnt; + this.position = position; + } + + internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymbols) + { + DebugGuard.MustBeLessThan(numberOfSymbols, 17, nameof(numberOfSymbols)); + int rate15 = probabilities[numberOfSymbols] > 15 ? 1 : 0; + int rate31 = probabilities[numberOfSymbols] > 31 ? 1 : 0; + int rate = 3 + rate15 + rate31 + NumberOfSymbols2Speed[numberOfSymbols]; // + get_msb(nsymbs); + int tmp = CdfProbabilityTop; + + // Single loop (faster) + for (int i = 0; i < numberOfSymbols - 1; i++) + { + tmp = (i == value) ? 0 : tmp; + if (tmp < probabilities[i]) + { + probabilities[i] -= (ushort)((probabilities[i] - tmp) >> rate); + } + else + { + probabilities[i] += (ushort)((tmp - probabilities[i]) >> rate); + } + } + + probabilities[numberOfSymbols] = Math.Min(probabilities[numberOfSymbols]++, 32); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs new file mode 100644 index 0000000000..424bda8761 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1SymbolWriter +{ + private uint low; + private uint rng = 0x8000U; + + // Count is initialized to -9 so that it crosses zero after we've accumulated one byte + one carry bit. + private int cnt = -9; + private readonly Stream stream; + + public Av1SymbolWriter(Stream stream) => this.stream = stream; + + public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols) + { + DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); + DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol)); + DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); + + this.EncodeIntegerQ15(symbol, probabilities, numberOfSymbols); + Av1SymbolReader.UpdateCdf(probabilities, symbol, numberOfSymbols); + } + + public void WriteLiteral(uint value, int bitCount) + { + const uint p = 0x4000U; // (0x7FFFFFU - (128 << 15) + 128) >> 8; + for (int bit = bitCount - 1; bit >= 0; bit--) + { + bool bitValue = ((value >> bit) & 0x1) > 0; + this.EncodeBoolQ15(bitValue, p); + } + } + + public void Exit() + { + uint m; + uint e; + uint l; + int c; + int s; + + // We output the minimum number of bits that ensures that the symbols encoded + // thus far will be decoded correctly regardless of the bits that follow. + l = this.low; + c = this.cnt; + s = 10; + m = 0x3FFFU; + e = ((l + m) & ~m) | (m + 1); + s += c; + if (s > 0) + { + uint n = (1U << (c + 16)) - 1; + do + { + this.stream.WriteByte((byte)(e >> (c + 16))); + e &= n; + s -= 8; + c -= 8; + n >>= 8; + } + while (s > 0); + } + } + + /// + /// Encode a single binary value. + /// + /// The value to encode. + /// The probability that the value is true, scaled by 32768. + private void EncodeBoolQ15(bool val, uint frequency) + { + uint l; + uint r; + uint v; + DebugGuard.MustBeGreaterThan(frequency, 0U, nameof(frequency)); + DebugGuard.MustBeLessThanOrEqualTo(frequency, 32768U, nameof(frequency)); + l = this.low; + r = this.rng; + DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); + v = ((r >> 8) * (frequency >> Av1SymbolReader.ProbabilityShift)) >> (7 - Av1SymbolReader.ProbabilityShift); + v += Av1SymbolReader.ProbabilityMinimum; + if (val) + { + l += r - v; + r = v; + } + else + { + r -= v; + } + + this.Normalize(l, r); + } + + /// + /// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. + /// + /// The value to encode. + /// + /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range + /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). + /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. + /// + /// + /// The number of symbols in the alphabet. + /// This should be at most 16. + /// + private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymbols) + => this.EncodeIntegerQ15(symbol > 0 ? probabilities[symbol - 1] : Av1SymbolReader.CdfProbabilityTop, probabilities[symbol], symbol, numberOfSymbols); + + private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) + { + uint l = this.low; + uint r = this.rng; + int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; + DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r)); + DebugGuard.MustBeLessThanOrEqualTo(highFrequency, lowFrequency, nameof(highFrequency)); + DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency)); + DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, string.Empty); + int n = numberOfSymbols - 1; + if (lowFrequency < Av1SymbolReader.CdfProbabilityTop) + { + uint u; + uint v; + u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + + (Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1)))); + v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + + (Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); + l += r - u; + r = u - v; + } + else + { + r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + + (Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); + } + + this.Normalize(l, r); + } + + /// + /// Takes updated low and range values, renormalizes them so that + /// lies between 32768 and 65536 (flushing bytes from low to the pre-carry buffer if necessary), + /// and stores them back in the encoder context. + /// + /// The new value of . + /// The new value of . + private void Normalize(uint low, uint rng) + { + int d; + int c; + int s; + c = this.cnt; + DebugGuard.MustBeLessThanOrEqualTo(rng, 65535U, nameof(rng)); + d = 15 - Av1Math.MostSignificantBit(rng); + s = c + d; + /*TODO: Right now we flush every time we have at least one byte available. + Instead we should use an OdEcWindow and flush right before we're about to + shift bits off the end of the window. + For a 32-bit window this is about the same amount of work, but for a 64-bit + window it should be a fair win.*/ + if (s >= 0) + { + uint m; + + c += 16; + m = (1U << c) - 1; + if (s >= 8) + { + this.stream.WriteByte((byte)(low >> c)); + low &= m; + c -= 8; + m >>= 8; + } + + this.stream.WriteByte((byte)(low >> c)); + s = c + d - 24; + low &= m; + } + + this.low = low << d; + this.rng = rng << d; + this.cnt = s; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs new file mode 100644 index 0000000000..5ad969c3f6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -0,0 +1,234 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; +using Newtonsoft.Json.Linq; +using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class SymbolTest +{ + [Fact] + public void ReadRandomLiteral() + { + // Assign + const int bitCount = 4; + Random rand = new(bitCount); + byte[] values = Enumerable.Range(0, 100).Select(x => (byte)rand.Next(1 << bitCount)).ToArray(); + Av1SymbolReader reader = new(values); + List actuals = []; + + // Act + for (int i = 0; i < values.Length; i++) + { + actuals.Add(reader.ReadLiteral(bitCount)); + } + + // Assert + Assert.True(values.Length > bitCount); + } + + [Fact] + public void WriteRandomLiteral() + { + // Assign + const int bitCount = 4; + Random rand = new(bitCount); + uint[] values = Enumerable.Range(0, 100).Select(x => (uint)rand.Next(1 << bitCount)).ToArray(); + MemoryStream output = new(); + Av1SymbolWriter writer = new(output); + + // Act + for (int i = 0; i < values.Length; i++) + { + writer.WriteLiteral(values[i], bitCount); + } + + // Assert + Assert.True(output.Position > 0); + } + + [Theory] + [InlineData(0, 0, 128)] + [InlineData(1, 255, 128)] + public void RawBytesFromWriteLiteral1Bit(uint value, byte exp0, byte exp1) + { + byte[] expected = [exp0, exp1]; + AssertRawBytesWritten(1, value, expected); + } + + [Theory] + [InlineData(0, 0, 0, 128)] + [InlineData(1, 85, 118, 192)] + [InlineData(2, 170, 165, 128)] + [InlineData(3, 255, 255, 128)] + public void RawBytesFromWriteLiteral2Bits(uint value, byte exp0, byte exp1, byte exp2) + { + byte[] expected = [exp0, exp1, exp2]; + AssertRawBytesWritten(2, value, expected); + } + + [Theory] + [InlineData(0, 0, 0, 0, 128)] + [InlineData(1, 36, 198, 146, 128)] + [InlineData(2, 73, 81, 182, 192)] + [InlineData(3, 109, 192, 146, 64)] + [InlineData(4, 146, 66, 73, 128)] + [InlineData(5, 182, 214, 219, 128)] + [InlineData(6, 219, 107, 109, 128)] + [InlineData(7, 255, 255, 255, 128)] + public void RawBytesFromWriteLiteral3Bits(uint value, byte exp0, byte exp1, byte exp2, byte exp3) + { + byte[] expected = [exp0, exp1, exp2, exp3]; + AssertRawBytesWritten(3, value, expected); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 128)] + [InlineData(1, 17, 68, 34, 34, 128)] + [InlineData(2, 34, 86, 68, 68, 128)] + [InlineData(3, 51, 104, 102, 102, 128)] + [InlineData(4, 68, 118, 34, 34, 64)] + [InlineData(5, 85, 118, 170, 170, 64)] + [InlineData(6, 102, 119, 51, 51, 64)] + [InlineData(7, 119, 119, 187, 187, 192)] + [InlineData(8, 136, 129, 17, 17, 128)] + [InlineData(9, 153, 147, 51, 51, 128)] + [InlineData(10, 170, 165, 85, 85, 128)] + [InlineData(11, 187, 183, 119, 119, 128)] + [InlineData(12, 204, 201, 153, 153, 128)] + [InlineData(13, 221, 219, 187, 187, 128)] + [InlineData(14, 238, 237, 221, 221, 128)] + [InlineData(15, 255, 255, 255, 255, 128)] + public void RawBytesFromWriteLiteral4Bits(uint value, byte exp0, byte exp1, byte exp2, byte exp3, byte exp4) + { + byte[] expected = [exp0, exp1, exp2, exp3, exp4]; + AssertRawBytesWritten(4, value, expected); + } + + private static void AssertRawBytesWritten(int bitCount, uint value, byte[] expected) + { + // Assign + uint[] values = new uint[8]; + Array.Fill(values, value); + MemoryStream output = new(); + Av1SymbolWriter writer = new(output); + + // Act + for (int i = 0; i < 8; i++) + { + writer.WriteLiteral(value, bitCount); + } + + writer.Exit(); + + // Assert + Assert.Equal(expected, output.ToArray()); + } + + [Theory] + [InlineData(0, 0, 128)] + [InlineData(1, 255, 128)] + public void RawBytesReadLiteral1Bit(int value, byte exp0, byte exp1) + { + byte[] buffer = [exp0, exp1]; + AssertRawBytesRead(1, buffer, value); + } + + [Theory] + [InlineData(0, 0, 0, 128)] + [InlineData(1, 85, 118, 192)] + [InlineData(2, 170, 165, 128)] + [InlineData(3, 255, 255, 128)] + public void RawBytesReadLiteral2Bits(int value, byte exp0, byte exp1, byte exp2) + { + byte[] buffer = [exp0, exp1, exp2]; + AssertRawBytesRead(2, buffer, value); + } + + [Theory] + [InlineData(0, 0, 0, 0, 128)] + [InlineData(1, 36, 198, 146, 128)] + [InlineData(2, 73, 81, 182, 192)] + [InlineData(3, 109, 192, 146, 64)] + [InlineData(4, 146, 66, 73, 128)] + [InlineData(5, 182, 214, 219, 128)] + [InlineData(6, 219, 107, 109, 128)] + [InlineData(7, 255, 255, 255, 128)] + public void RawBytesReadLiteral3Bits(int value, byte exp0, byte exp1, byte exp2, byte exp3) + { + byte[] buffer = [exp0, exp1, exp2, exp3]; + AssertRawBytesRead(3, buffer, value); + } + + [Theory] + [InlineData(0, 0, 0, 0, 0, 128)] + [InlineData(1, 17, 68, 34, 34, 128)] + [InlineData(2, 34, 86, 68, 68, 128)] + [InlineData(3, 51, 104, 102, 102, 128)] + [InlineData(4, 68, 118, 34, 34, 64)] + [InlineData(5, 85, 118, 170, 170, 64)] + [InlineData(6, 102, 119, 51, 51, 64)] + [InlineData(7, 119, 119, 187, 187, 192)] + [InlineData(8, 136, 129, 17, 17, 128)] + [InlineData(9, 153, 147, 51, 51, 128)] + [InlineData(10, 170, 165, 85, 85, 128)] + [InlineData(11, 187, 183, 119, 119, 128)] + [InlineData(12, 204, 201, 153, 153, 128)] + [InlineData(13, 221, 219, 187, 187, 128)] + [InlineData(14, 238, 237, 221, 221, 128)] + [InlineData(15, 255, 255, 255, 255, 128)] + public void RawBytesReadLiteral4Bits(int value, byte exp0, byte exp1, byte exp2, byte exp3, byte exp4) + { + byte[] buffer = [exp0, exp1, exp2, exp3, exp4]; + AssertRawBytesRead(4, buffer, value); + } + + private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected) + { + // Assign + int[] values = new int[8]; + int[] expectedValues = new int[8]; + Array.Fill(expectedValues, expected); + Av1SymbolReader reader = new(buffer); + + // Act + for (int i = 0; i < 8; i++) + { + values[i] = reader.ReadLiteral(bitCount); + } + + // Assert + Assert.Equal(expectedValues, values); + } + + [Fact] + public void RoundTripUseIntraBlockCopy() + { + // Assign + bool[] values = [true, true, false, true, false, false, false]; + MemoryStream output = new(100); + Av1SymbolWriter writer = new(output); + Av1SymbolEncoder encoder = new(); + Av1SymbolDecoder decoder = new(); + bool[] actuals = new bool[values.Length]; + + // Act + foreach (bool value in values) + { + encoder.WriteUseIntraBlockCopySymbol(writer, value); + } + + Av1SymbolReader reader = new(output.ToArray()); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader); + } + + // Assert + Assert.Equal(values, actuals); + } +} From 06b0c0425633c7da9a9d39040ec7560542d011d7 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 17 May 2024 12:17:47 +0200 Subject: [PATCH 058/190] Fix literal reading and writing --- .../Heif/Av1/Symbol/Av1SymbolReader.cs | 6 +- .../Heif/Av1/Symbol/Av1SymbolWriter.cs | 71 +++++++++++++------ .../Formats/Heif/Av1/SymbolTest.cs | 47 ++++-------- 3 files changed, 65 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs index 7012a973c4..542270f3f0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolReader @@ -145,7 +143,7 @@ private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) while (c < v); DebugGuard.MustBeLessThan(v, u, nameof(v)); - DebugGuard.MustBeLessThan(u, r, nameof(u)); + DebugGuard.MustBeLessThanOrEqualTo(u, r, nameof(u)); r = u - v; dif -= v << (DecoderWindowsSize - 16); this.Normalize(dif, r); @@ -189,7 +187,7 @@ private void Refill() is incremented by 8, so the total number of consumed bits (the return value of od_ec_dec_tell) does not change.*/ DebugGuard.MustBeLessThan(s, DecoderWindowsSize - 8, nameof(s)); - dif ^= (uint)this.buffer[0] << s; + dif ^= (uint)this.buffer[position] << s; cnt += 8; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs index 424bda8761..296b516fb1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -1,20 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1SymbolWriter +internal class Av1SymbolWriter : IDisposable { private uint low; private uint rng = 0x8000U; // Count is initialized to -9 so that it crosses zero after we've accumulated one byte + one carry bit. private int cnt = -9; - private readonly Stream stream; + private readonly Configuration configuration; + private readonly AutoExpandingMemory memory; + private int position; - public Av1SymbolWriter(Stream stream) => this.stream = stream; + public Av1SymbolWriter(Configuration configuration, int initialSize) + { + this.configuration = configuration; + this.memory = new AutoExpandingMemory(configuration, (initialSize + 1) >> 1); + } + + public void Dispose() => this.memory.Dispose(); public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols) { @@ -36,28 +45,25 @@ public void WriteLiteral(uint value, int bitCount) } } - public void Exit() + public IMemoryOwner Exit() { - uint m; - uint e; - uint l; - int c; - int s; - // We output the minimum number of bits that ensures that the symbols encoded // thus far will be decoded correctly regardless of the bits that follow. - l = this.low; - c = this.cnt; - s = 10; - m = 0x3FFFU; - e = ((l + m) & ~m) | (m + 1); + uint l = this.low; + int c = this.cnt; + int pos = this.position; + int s = 10; + uint m = 0x3FFFU; + uint e = ((l + m) & ~m) | (m + 1); s += c; + Span buffer = this.memory.GetSpan(this.position + ((s + 7) >> 3)); if (s > 0) { uint n = (1U << (c + 16)) - 1; do { - this.stream.WriteByte((byte)(e >> (c + 16))); + buffer[pos] = (ushort)(e >> (c + 16)); + pos++; e &= n; s -= 8; c -= 8; @@ -65,6 +71,22 @@ public void Exit() } while (s > 0); } + + c = Math.Max((s + 7) >> 3, 0); + IMemoryOwner output = this.configuration.MemoryAllocator.Allocate(pos + c); + + // Perform carry propagation. + Span outputSlice = output.GetSpan()[(output.Length() - pos)..]; + c = 0; + while (pos > 0) + { + pos--; + c = buffer[pos] + c; + outputSlice[pos] = (byte)c; + c >>= 8; + } + + return output; } /// @@ -115,13 +137,13 @@ private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymb private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) { + const int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; uint l = this.low; uint r = this.rng; - int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r)); DebugGuard.MustBeLessThanOrEqualTo(highFrequency, lowFrequency, nameof(highFrequency)); DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency)); - DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, string.Empty); + DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift)); int n = numberOfSymbols - 1; if (lowFrequency < Av1SymbolReader.CdfProbabilityTop) { @@ -130,14 +152,14 @@ private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + (Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1)))); v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); + (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); l += r - u; r = u - v; } else { r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - (symbol + 0)))); + (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); } this.Normalize(l, r); @@ -167,18 +189,21 @@ window it should be a fair win.*/ if (s >= 0) { uint m; + Span buffer = this.memory.GetSpan(this.position + 2); c += 16; m = (1U << c) - 1; if (s >= 8) { - this.stream.WriteByte((byte)(low >> c)); + buffer[this.position] = (ushort)(low >> c); + this.position++; low &= m; c -= 8; m >>= 8; } - this.stream.WriteByte((byte)(low >> c)); + buffer[this.position] = (ushort)(low >> c); + this.position++; s = c + d - 24; low &= m; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 5ad969c3f6..28e53f870f 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -1,8 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using Newtonsoft.Json.Linq; +using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; using SixLabors.ImageSharp.Memory; @@ -31,26 +30,6 @@ public void ReadRandomLiteral() Assert.True(values.Length > bitCount); } - [Fact] - public void WriteRandomLiteral() - { - // Assign - const int bitCount = 4; - Random rand = new(bitCount); - uint[] values = Enumerable.Range(0, 100).Select(x => (uint)rand.Next(1 << bitCount)).ToArray(); - MemoryStream output = new(); - Av1SymbolWriter writer = new(output); - - // Act - for (int i = 0; i < values.Length; i++) - { - writer.WriteLiteral(values[i], bitCount); - } - - // Assert - Assert.True(output.Position > 0); - } - [Theory] [InlineData(0, 0, 128)] [InlineData(1, 255, 128)] @@ -92,7 +71,7 @@ public void RawBytesFromWriteLiteral3Bits(uint value, byte exp0, byte exp1, byte [InlineData(2, 34, 86, 68, 68, 128)] [InlineData(3, 51, 104, 102, 102, 128)] [InlineData(4, 68, 118, 34, 34, 64)] - [InlineData(5, 85, 118, 170, 170, 64)] + [InlineData(5, 85, 118, 170, 170, 192)] [InlineData(6, 102, 119, 51, 51, 64)] [InlineData(7, 119, 119, 187, 187, 192)] [InlineData(8, 136, 129, 17, 17, 128)] @@ -112,21 +91,22 @@ public void RawBytesFromWriteLiteral4Bits(uint value, byte exp0, byte exp1, byte private static void AssertRawBytesWritten(int bitCount, uint value, byte[] expected) { // Assign - uint[] values = new uint[8]; + const int writeCount = 8; + uint[] values = new uint[writeCount]; Array.Fill(values, value); - MemoryStream output = new(); - Av1SymbolWriter writer = new(output); + Configuration configuration = Configuration.Default; + using Av1SymbolWriter writer = new(configuration, (writeCount * bitCount) >> 3); // Act - for (int i = 0; i < 8; i++) + for (int i = 0; i < writeCount; i++) { writer.WriteLiteral(value, bitCount); } - writer.Exit(); + using IMemoryOwner actual = writer.Exit(); // Assert - Assert.Equal(expected, output.ToArray()); + Assert.Equal(expected, actual.GetSpan().ToArray()); } [Theory] @@ -170,7 +150,7 @@ public void RawBytesReadLiteral3Bits(int value, byte exp0, byte exp1, byte exp2, [InlineData(2, 34, 86, 68, 68, 128)] [InlineData(3, 51, 104, 102, 102, 128)] [InlineData(4, 68, 118, 34, 34, 64)] - [InlineData(5, 85, 118, 170, 170, 64)] + [InlineData(5, 85, 118, 170, 170, 192)] [InlineData(6, 102, 119, 51, 51, 64)] [InlineData(7, 119, 119, 187, 187, 192)] [InlineData(8, 136, 129, 17, 17, 128)] @@ -205,13 +185,14 @@ private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected Assert.Equal(expectedValues, values); } - [Fact] + //[Fact] public void RoundTripUseIntraBlockCopy() { // Assign bool[] values = [true, true, false, true, false, false, false]; MemoryStream output = new(100); - Av1SymbolWriter writer = new(output); + Configuration configuration = Configuration.Default; + using Av1SymbolWriter writer = new(configuration, 100 / 8); Av1SymbolEncoder encoder = new(); Av1SymbolDecoder decoder = new(); bool[] actuals = new bool[values.Length]; @@ -222,6 +203,8 @@ public void RoundTripUseIntraBlockCopy() encoder.WriteUseIntraBlockCopySymbol(writer, value); } + writer.Exit(); + Av1SymbolReader reader = new(output.ToArray()); for (int i = 0; i < values.Length; i++) { From e34b83148cdc832e621f6e53064299dca161c2e9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 17 May 2024 12:35:19 +0200 Subject: [PATCH 059/190] First symbol coding roundtrip --- .../Formats/Heif/Av1/Symbol/Av1SymbolReader.cs | 10 ++++++---- tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs index 542270f3f0..91e00676ab 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -226,16 +226,18 @@ internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymb for (int i = 0; i < numberOfSymbols - 1; i++) { tmp = (i == value) ? 0 : tmp; - if (tmp < probabilities[i]) + uint p = probabilities[i]; + if (tmp < p) { - probabilities[i] -= (ushort)((probabilities[i] - tmp) >> rate); + probabilities[i] -= (ushort)((p - tmp) >> rate); } else { - probabilities[i] += (ushort)((tmp - probabilities[i]) >> rate); + probabilities[i] += (ushort)((tmp - p) >> rate); } } - probabilities[numberOfSymbols] = Math.Min(probabilities[numberOfSymbols]++, 32); + uint rate32 = probabilities[numberOfSymbols] < 32 ? 1U : 0U; + probabilities[numberOfSymbols] += rate32; } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 28e53f870f..99f38c118f 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -185,7 +185,7 @@ private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected Assert.Equal(expectedValues, values); } - //[Fact] + [Fact] public void RoundTripUseIntraBlockCopy() { // Assign @@ -203,9 +203,9 @@ public void RoundTripUseIntraBlockCopy() encoder.WriteUseIntraBlockCopySymbol(writer, value); } - writer.Exit(); + using IMemoryOwner encoded = writer.Exit(); - Av1SymbolReader reader = new(output.ToArray()); + Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader); From fec0413a1abf51619f64bbb0111d02bac074a0bd Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 28 May 2024 20:58:07 +0200 Subject: [PATCH 060/190] Initial symbol decoding --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 11 + .../Formats/Heif/Av1/Av1BlockModeInfo.cs | 2 +- .../Formats/Heif/Av1/Av1BlockSize.cs | 96 ++- .../Heif/Av1/Av1BlockSizeExtensions.cs | 46 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 792 +++++++++++++++++- .../Formats/Heif/Av1/Av1PartitionType.cs | 22 +- .../Heif/Av1/Av1PartitionTypeExtensions.cs | 104 +++ .../Formats/Heif/Av1/IAv1TileDecoder.cs | 5 +- .../Av1/OpenBitstreamUnit/ObuConstants.cs | 29 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 18 +- .../ObuSegmentationFeature.cs | 10 + .../ObuSegmentationParameters.cs | 8 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 4 +- .../Heif/Av1/Prediction/Av1PredictionMode.cs | 1 + .../Av1/Symbol/Av1DefaultDistributions.cs | 122 ++- .../Heif/Av1/Symbol/Av1Distribution.cs | 114 +++ .../Heif/Av1/Symbol/Av1ParseAboveContext.cs | 11 + .../Heif/Av1/Symbol/Av1ParseLeftContext.cs | 11 + .../Heif/Av1/Symbol/Av1SymbolDecoder.cs | 124 ++- .../Heif/Av1/Symbol/Av1SymbolEncoder.cs | 23 +- .../Heif/Av1/Symbol/Av1SymbolReader.cs | 64 +- .../Heif/Av1/Symbol/Av1SymbolWriter.cs | 34 +- .../Transform/Av1TransformSizeExtensions.cs | 8 + .../Formats/Heif/Av1/Av1TileDecoderStub.cs | 2 +- .../Formats/Heif/Av1/SymbolTest.cs | 38 +- 25 files changed, 1550 insertions(+), 149 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index d378f8e034..f47ca720ea 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -171,4 +172,14 @@ public void AdvanceToNextWord() uint temp = this.data[this.wordPosition]; this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24); } + + public Span GetSymbolReader(int tileDataSize) + { + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Symbol reading needs to start on byte boundary."); + + // TODO: Pass exact byte iso Word start. + Span span = this.data.Slice(this.bitOffset >> WordSize, tileDataSize); + this.bitOffset += tileDataSize << 8; + return MemoryMarshal.Cast(span); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs index c288919cec..4f41d47082 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs @@ -11,7 +11,7 @@ internal class Av1BlockModeInfo public Av1PredictionMode PredictionMode { get; } - public Av1PartitionType Partition { get; } + public Av1PartitionType Partition { get; set; } public int SegmentId { get; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index 6eca5bc638..5a524c261d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -5,30 +5,74 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal enum Av1BlockSize { - Block4x4, - Block4x8, - Block8x4, - Block8x8, - Block8x16, - Block16x8, - Block16x16, - Block16x32, - Block32x16, - Block32x32, - Block32x64, - Block64x32, - Block64x64, - Block64x128, - Block128x64, - Block128x128, - Block4x16, - Block16x4, - Block8x32, - Block32x8, - Block16x64, - Block64x16, - BlockSizeSAll, - BlockSizeS = Block4x16, - BlockInvalid = 255, - BlockLargest = BlockSizeS - 1, + // See sction 6.10.4 of the Av1 Specification. + + /// A block of samples, 4 samples wide and 4 samples high. + Block4x4 = 0, + + /// A block of samples, 4 samples wide and 8 samples high. + Block4x8 = 1, + + /// A block of samples, 8 samples wide and 4 samples high. + Block8x4 = 2, + + /// A block of samples, 8 samples wide and 8 samples high. + Block8x8 = 3, + + /// A block of samples, 8 samples wide and 16 samples high. + Block8x16 = 4, + + /// A block of samples, 16 samples wide and 8 samples high. + Block16x8 = 5, + + /// A block of samples, 16 samples wide and 16 samples high. + Block16x16 = 6, + + /// A block of samples, 16 samples wide and 32 samples high. + Block16x32 = 7, + + /// A block of samples, 32 samples wide and 16 samples high. + Block32x16 = 8, + + /// A block of samples, 32 samples wide and 32 samples high. + Block32x32 = 9, + + /// A block of samples, 32 samples wide and 64 samples high. + Block32x64 = 10, + + /// A block of samples, 64 samples wide and 32 samples high. + Block64x32 = 11, + + /// A block of samples, 64 samples wide and 64 samples high. + Block64x64 = 12, + + /// A block of samples, 64 samples wide and 128 samples high. + Block64x128 = 13, + + /// A block of samples, 128 samples wide and 64 samples high. + Block128x64 = 14, + + /// A block of samples, 128 samples wide and 128 samples high. + Block128x128 = 15, + + /// A block of samples, 4 samples wide and 16 samples high. + Block4x16 = 16, + + /// A block of samples, 16 samples wide and 4 samples high. + Block16x4 = 17, + + /// A block of samples, 8 samples wide and 32 samples high. + Block8x32 = 18, + + /// A block of samples, 32 samples wide and 8 samples high. + Block32x8 = 19, + + /// A block of samples, 16 samples wide and 64 samples high. + Block16x64 = 20, + + /// A block of samples, 64 samples wide and 16 samples high. + Block64x16 = 21, + Invalid = 22, + SizeS = Block4x16, + Largest = SizeS - 1, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 16190dd6b2..f6e7d6f3f8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -1,14 +1,56 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1BlockSizeExtensions { - private static readonly int[] SizeWide = { 1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16 }; - private static readonly int[] SizeHigh = { 1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4 }; + private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]; + private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]; + + private static readonly Av1TransformSize[] MaxTransformSize = [ + Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8, + Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32, + Av1TransformSize.Size32x16, Av1TransformSize.Size32x32, Av1TransformSize.Size32x64, Av1TransformSize.Size64x32, + Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, Av1TransformSize.Size64x64, + Av1TransformSize.Size4x16, Av1TransformSize.Size16x4, Av1TransformSize.Size8x32, Av1TransformSize.Size32x8, + Av1TransformSize.Size16x64, Av1TransformSize.Size64x16 + ]; public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; + + /// + /// Returns the width of the block in samples. + /// + public static int GetWidth(this Av1BlockSize blockSize) + => Get4x4WideCount(blockSize) << 2; + + /// + /// Returns of the height of the block in 4 samples. + /// + public static int GetHeight(this Av1BlockSize blockSize) + => Get4x4HighCount(blockSize) << 2; + + /// + /// Returns base 2 logarithm of the width of the block in units of 4 samples. + /// + public static int Get4x4WidthLog2(this Av1BlockSize blockSize) + => Get4x4WideCount(blockSize) << 2; + + /// + /// Returns base 2 logarithm of the height of the block in units of 4 samples. + /// + public static int Get4x4HeightLog2(this Av1BlockSize blockSize) + => Get4x4HighCount(blockSize) << 2; + + /// + /// Returns th largest transform size that can be used for blocks of given size. + /// The can be either a square or rectangular block. + /// + public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize) + => MaxTransformSize[(int)blockSize]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index edd4940bf3..0a8fac5d0e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -2,16 +2,69 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal class Av1Decoder : IAv1TileDecoder { + private static readonly int[] SgrprojXqdMid = [-32, 31]; + private static readonly int[] WienerTapsMid = [3, -7, 15]; + private const int PartitionProbabilitySet = 4; + + private int[] deltaLoopFilter = []; + private bool[][][] blockDecoded = []; + private int[][] referenceSgrXqd = []; + private int[][][] referenceLrWiener = []; + private bool availableUp; + private bool availableLeft; + private bool availableUpForChroma; + private bool availableLeftForChroma; + private Av1ParseAboveContext aboveContext = new(); + private Av1ParseLeftContext leftContext = new(); + private bool skip; + private bool readDeltas; + private int currentQuantizerIndex; + private Av1PredictionMode[][] YModes = []; + private Av1PredictionMode YMode = Av1PredictionMode.DC; + private Av1PredictionMode UvMode = Av1PredictionMode.DC; + private Av1PredictionMode[][] UvModes = []; + private object[] ReferenceFrame = []; + private object[][][] ReferenceFrames = []; + private bool HasChroma; + private bool SubsamplingX; + private bool SubsamplingY; + private int PaletteSizeY; + private int PaletteSizeUv; + private object[][] aboveLevelContext = []; + private object[][] aboveDcContext = []; + private object[][] leftLevelContext = []; + private object[][] leftDcContext = []; + private Av1TransformSize TransformSize = Av1TransformSize.Size4x4; + private bool skipMode; + private bool IsChromaForLumaAllowed; + private object FilterUltraMode = -1; + private int AngleDeltaY; + private int AngleDeltaUv; + private bool Lossless; + private int[][] SegmentIds = []; + private int MaxLumaWidth; + private int MaxLumaHeight; + private int segmentId; + private int[][] cdefIndex = []; + private int deltaLoopFilterResolution; + private int deltaQuantizerResolution; + public Av1Decoder() { this.FrameInfo = new ObuFrameHeader(); this.SequenceHeader = new ObuSequenceHeader(); this.TileInfo = new ObuTileInfo(); + this.SeenFrameHeader = false; + this.currentQuantizerIndex = -1; } public bool SequenceHeaderDone { get; set; } @@ -32,9 +85,96 @@ public void Decode(Span buffer) ObuReader.Read(ref reader, buffer.Length, this, false); } - public void DecodeTile(int tileNum) + public void DecodeTile(Span tileData, int tileNum) { - // TODO: Implement + Av1SymbolDecoder reader = new(tileData); + int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; + int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; + this.aboveContext.Clear(); + this.ClearLoopFilterDelta(); + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + this.referenceSgrXqd = new int[planesCount][]; + this.referenceLrWiener = new int[planesCount][][]; + for (int plane = 0; plane < planesCount; plane++) + { + this.referenceSgrXqd[plane] = new int[2]; + Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length); + this.referenceLrWiener[plane] = new int[2][]; + for (int pass = 0; pass < 2; pass++) + { + this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount]; + Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length); + } + } + + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + int superBlock4x4Size = superBlockSize.Get4x4WideCount(); + for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) + { + int superBlockRow = (row << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2; + this.leftContext.Clear(); + for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) + { + int superBlockColumn = (column << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2; + bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + + bool ReadDeltas = this.FrameInfo.DeltaQParameters.IsPresent; + + // Nothing to do for CDEF + // this.ClearCdef(row, column); + this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); + this.ReadLoopRestoration(row, column, superBlockSize); + this.DecodePartition(ref reader, row, column, superBlockSize); + } + } + } + + private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4]; + + private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) + { + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + for (int plane = 0; plane < planesCount; plane++) + { + int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0; + int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0; + int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX; + int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY; + for (int y = -1; y <= (superBlock4x4Size >> subY); y++) + { + for (int x = -1; x <= (superBlock4x4Size >> subX); x++) + { + if (y < 0 && x < superBlock4x4Width) + { + this.blockDecoded[plane][y][x] = true; + } + else if (x < 0 && y < superBlock4x4Height) + { + this.blockDecoded[plane][y][x] = true; + } + else + { + this.blockDecoded[plane][y][x] = false; + } + } + } + + this.blockDecoded[plane][superBlock4x4Size >> subY][-1] = false; + } + } + + private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize) + { + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + for (int plane = 0; plane < planesCount; plane++) + { + if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None) + { + // TODO: Implement. + throw new NotImplementedException("No loop restoration filter support."); + } + } } public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) @@ -42,9 +182,655 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } - private static void DecodeBlock(Av1BlockModeInfo blockMode, int rowIndex, int columnIndex) + private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || + columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) + { + return; + } + + this.availableUp = this.IsInside(rowIndex - 1, columnIndex); + this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + int block4x4Size = blockSize.Get4x4WideCount(); + int halfBlock4x4Size = block4x4Size >> 1; + int quarterBlock4x4Size = halfBlock4x4Size >> 2; + bool hasRows = (rowIndex + halfBlock4x4Size) < this.TileInfo.TileRowCount; + bool hasColumns = (columnIndex + halfBlock4x4Size) < this.TileInfo.TileColumnCount; + Av1PartitionType partitionType = Av1PartitionType.Split; + if (blockSize < Av1BlockSize.Block8x8) + { + partitionType = Av1PartitionType.None; + } + else if (hasRows && hasColumns) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + partitionType = reader.ReadPartitionSymbol(ctx); + } + else if (hasColumns) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); + partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + } + else if (hasRows) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); + partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; + } + + Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); + Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize); + switch (partitionType) + { + case Av1PartitionType.Split: + this.DecodePartition(ref reader, rowIndex, columnIndex, subSize); + this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); + this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); + this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); + break; + case Av1PartitionType.None: + this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize); + break; + default: + throw new NotImplementedException($"Partition type: {partitionType} is not supported."); + } + } + + private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int block4x4Width = blockSize.Get4x4WideCount(); + int block4x4Height = blockSize.Get4x4HighCount(); + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + bool hasChroma = planesCount > 1; + if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0) + { + hasChroma = false; + } + + if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0) + { + hasChroma = false; + } + + this.availableUp = this.IsInside(rowIndex - 1, columnIndex); + this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + this.availableUpForChroma = this.availableUp; + this.availableLeftForChroma = this.availableLeft; + if (hasChroma) + { + if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) + { + this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); + } + + if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) + { + this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); + } + } + + this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize); + this.ReadPaletteTokens(ref reader); + this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + if (this.skip) + { + this.ResetBlockContext(rowIndex, columnIndex, block4x4Width, block4x4Height); + } + + bool isCompound = false; + for (int y = 0; y < block4x4Height; y++) + { + for (int x = 0; x < block4x4Width; x++) + { + this.YModes[rowIndex + y][columnIndex + x] = this.YMode; + if (this.ReferenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma) + { + this.UvModes[rowIndex + y][columnIndex + x] = this.UvMode; + } + + for (int refList = 0; refList < 2; refList++) + { + this.ReferenceFrames[rowIndex + y][columnIndex + x][refList] = this.ReferenceFrame[refList]; + } + } + } + + this.ComputePrediction(); + this.Residual(rowIndex, columnIndex, blockSize); + } + + private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; + int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6); + int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6); + Av1BlockSize sizeChunk = (widthChunks > 1 || heightChunks > 1) ? Av1BlockSize.Block64x64 : blockSize; + + for (int chunkY = 0; chunkY < heightChunks; chunkY++) + { + for (int chunkX = 0; chunkX < widthChunks; chunkX++) + { + int rowChunk = rowIndex + (chunkY << 4); + int columnChunk = columnIndex + (chunkX << 4); + int subBlockRow = rowChunk & superBlockMask; + int subBlockColumn = columnChunk & superBlockMask; + for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++) + { + Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.TransformSize); + int stepX = transformSize.GetWidth() >> 2; + int stepY = transformSize.GetHeight() >> 2; + Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); + int num4x4Width = planeSize.Get4x4WideCount(); + int num4x4Height = planeSize.Get4x4HighCount(); + int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; + int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; + int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); + for (int y = 0; y < num4x4Height; y += stepY) + { + for (int x = 0; x < num4x4Width; x += stepX) + { + this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + ((chunkX << 4) >> subX), y + ((chunkY << 4) >> subY)); + } + } + } + } + } + } + + private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); + + private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException(); + + private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) + { + int startX = baseX + (4 * x); + int startY = baseY + (4 * y); + int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; + int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; + int columnIndex = (startX << subX) >> ObuConstants.ModeInfoSizeLog2; + int rowIndex = (startY << subY) >> ObuConstants.ModeInfoSizeLog2; + int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; + int subBlockColumn = columnIndex & superBlockMask; + int subBlockRow = rowIndex & superBlockMask; + int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2; + int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2; + int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX; + int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY; + if (startX >= maxX || startY >= maxY) + { + return; + } + + if ((plane == 0 && this.PaletteSizeY > 0) || + (plane != 0 && this.PaletteSizeUv > 0)) + { + this.PredictPalette(plane, startX, startY, x, y, transformSize); + } + else + { + bool isChromaFromLuma = plane > 0 && this.UvMode == Av1PredictionMode.UvChromaFromLuma; + Av1PredictionMode mode; + if (plane == 0) + { + mode = this.YMode; + } + else + { + mode = isChromaFromLuma ? Av1PredictionMode.DC : this.UvMode; + } + + int log2Width = transformSize.GetWidthLog2(); + int log2Height = transformSize.GetHeightLog2(); + bool leftAvailable = (x > 0) || plane == 0 ? this.availableLeft : this.availableLeftForChroma; + bool upAvailable = (y > 0) || plane == 0 ? this.availableUp : this.availableUpForChroma; + bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; + bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; + this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); + if (isChromaFromLuma) + { + this.PredictChromaFromLuma(plane, startX, startY, transformSize); + } + } + + if (plane == 0) + { + this.MaxLumaWidth = startX + (stepX * 4); + this.MaxLumaHeight = startY + (stepY * 4); + } + + if (!this.skip) + { + int eob = this.Coefficients(plane, startX, startY, transformSize); + if (eob > 0) + { + this.Reconstruct(plane, startX, startY, transformSize); + } + } + + for (int i = 0; i < stepY; i++) + { + for (int j = 0; j < stepX; j++) + { + // Ignore loop filter. + this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true; + } + } + } + + private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException(); + + private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private void ComputePrediction() + { + // Not applicable for INTRA frames. + } + + private void ResetBlockContext(int rowIndex, int columnIndex, int block4x4Width, int block4x4Height) + { + for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++) + { + int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; + int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; + for (int i = columnIndex >> subX; i < ((columnIndex + block4x4Width) >> subX); i++) + { + this.aboveLevelContext[plane][i] = 0; + this.aboveDcContext[plane][i] = 0; + } + + for (int i = rowIndex >> subY; i < ((rowIndex + block4x4Height) >> subY); i++) + { + this.leftLevelContext[plane][i] = 0; + this.leftDcContext[plane][i] = 0; + } + } + } + + private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int block4x4Width = blockSize.Get4x4WideCount(); + int block4x4Height = blockSize.Get4x4HighCount(); + + // First condition in spec is for INTER frames, implemented only the INTRA condition. + this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++) + { + for (int column = columnIndex; column < columnIndex + block4x4Width; column++) + { + this.InterTransformSizes[row][column] = this.TransformSize; + } + }*/ + } + + private void ReadPaletteTokens(ref Av1SymbolDecoder reader) + { + if (this.PaletteSizeY != 0) + { + // Todo: Implement. + throw new NotImplementedException(); + } + + if (this.PaletteSizeUv != 0) + { + // Todo: Implement. + throw new NotImplementedException(); + } + } + + private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize); + + private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + this.skip = false; + if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + { + this.ReadIntraSegmentId(ref reader); + } + + this.skipMode = false; + this.ReadSkip(ref reader); + if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + { + this.IntraSegmentId(ref reader, rowIndex, columnIndex); + } + + this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize); + this.ReadDeltaQuantizerIndex(ref reader, blockSize); + this.ReadDeltaLoopFilter(ref reader, blockSize); + this.readDeltas = false; + this.ReferenceFrame[0] = -1; // IntraFrame; + this.ReferenceFrame[1] = -1; // None; + bool useIntraBlockCopy = false; + if (this.FrameInfo.AllowIntraBlockCopy) + { + useIntraBlockCopy = reader.ReadUseIntraBlockCopy(); + } + + if (useIntraBlockCopy) + { + // TODO: Implement + } + else + { + // this.IsInter = false; + this.YMode = reader.ReadIntraFrameYMode(blockSize); + this.IntraAngleInfoY(ref reader, blockSize); + if (this.HasChroma) + { + this.UvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed); + if (this.UvMode == Av1PredictionMode.UvChromaFromLuma) + { + this.ReadChromaFromLumaAlphas(ref reader); + } + + this.IntraAngleInfoUv(ref reader, blockSize); + } + + this.PaletteSizeY = 0; + this.PaletteSizeUv = 0; + if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 && + ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 && + ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 && + this.FrameInfo.AllowScreenContentTools) + { + this.PaletteModeInfo(ref reader); + } + + this.FilterIntraModeInfo(ref reader, blockSize); + } + } + + private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException(); + + private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + bool useFilterIntra = false; + if (this.SequenceHeader.EnableFilterIntra && + this.YMode == Av1PredictionMode.DC && this.PaletteSizeY == 0 && + Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32) + { + useFilterIntra = reader.ReadUseFilterUltra(); + if (useFilterIntra) + { + this.FilterUltraMode = reader.ReadFilterUltraMode(); + } + } + } + + private void PaletteModeInfo(ref Av1SymbolDecoder reader) => + + // TODO: Implement. + throw new NotImplementedException(); + + private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) => + + // TODO: Implement. + throw new NotImplementedException(); + + private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + this.AngleDeltaY = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.YMode)) + { + int angleDeltaY = reader.ReadAngleDelta(this.YMode); + this.AngleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta; + } + } + + private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + this.AngleDeltaUv = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.UvMode)) + { + int angleDeltaUv = reader.ReadAngleDelta(this.UvMode); + this.AngleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta; + } + } + + private static bool IsDirectionalMode(Av1PredictionMode mode) + => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; + + private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + { + if (this.FrameInfo.SegmentationParameters.Enabled) + { + this.ReadSegmentId(ref reader, rowIndex, columnIndex); + } + else + { + this.segmentId = 0; + } + + this.Lossless = this.FrameInfo.LosslessArray[this.segmentId]; + } + + private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + { + int pred; + int prevUL = -1; + int prevU = -1; + int prevL = -1; + if (this.availableUp && this.availableLeft) + { + prevUL = this.SegmentIds[rowIndex - 1][columnIndex - 1]; + } + + if (this.availableUp) + { + prevU = this.SegmentIds[rowIndex - 1][columnIndex]; + } + + if (this.availableLeft) + { + prevU = this.SegmentIds[rowIndex][columnIndex - 1]; + } + + if (prevU == -1) + { + pred = (prevL == -1) ? 0 : prevL; + } + else if (prevL == -1) + { + pred = prevU; + } + else + { + pred = (prevU == prevUL) ? prevU : prevL; + } + + if (this.skip) + { + this.segmentId = 0; + } + else + { + int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; + this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1); + } + } + + private static int NegativeDeinterleave(int diff, int reference, int max) + { + if (reference == 0) + { + return diff; + } + + if (reference >= (max - 1)) + { + return max - diff - 1; + } + + if (2 * reference < max) + { + if (diff <= 2 * reference) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return diff; + } + else + { + if (diff <= 2 * (max - reference - 1)) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return max - (diff + 1); + } + } + + private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) + { + return; + } + + int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount(); + int cdefMask4 = ~(cdefSize4 - 1); + int r = rowIndex & cdefMask4; + int c = columnIndex & cdefMask4; + if (this.cdefIndex[r][c] == -1) + { + this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); + int w4 = blockSize.Get4x4WideCount(); + int h4 = blockSize.Get4x4HighCount(); + for (int i = r; i < r + h4; i += cdefSize4) + { + for (int j = c; j < c + w4; j += cdefSize4) + { + this.cdefIndex[i][j] = this.cdefIndex[r][c]; + } + } + } + } + + private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + if (blockSize == superBlockSize && this.skip) + { + return; + } + + if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent) + { + int frameLoopFilterCount = 1; + if (this.FrameInfo.DeltaLoopFilterParameters.Multi) + { + frameLoopFilterCount = (this.SequenceHeader.ColorConfig.ChannelCount > 1) ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2; + } + + for (int i = 0; i < frameLoopFilterCount; i++) + { + int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); + if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall) + { + int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1; + int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits); + deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1; + } + + if (deltaLoopFilterAbsolute != 0) + { + bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; + int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; + this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); + } + } + } + } + + private void ReadSkip(ref Av1SymbolDecoder reader) + { + if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && this.FrameInfo.SegmentationParameters.IsFeatureActive(ObuSegmentationFeature.LevelSkip)) + { + this.skip = true; + } + else + { + this.skip = reader.ReadSkip(-1); + } + } + + private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + if (blockSize == superBlockSize && this.skip) + { + return; + } + + if (this.readDeltas) + { + int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); + if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) + { + int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1; + int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits); + deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1; + } + + if (deltaQuantizerAbsolute != 0) + { + bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; + int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; + this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); + } + } + } + + private bool IsInside(int rowIndex, int columnIndex) => + columnIndex >= this.TileInfo.TileColumnCount && + columnIndex < this.TileInfo.TileColumnCount && + rowIndex >= this.TileInfo.TileRowCount && + rowIndex < this.TileInfo.TileRowCount; + + private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY) { int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); + bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX; + bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY; + return xPos && yPos; + } + + private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + // Maximum partition point is 8x8. Offset the log value occordingly. + int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); + int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex]; + int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex]; + int above = (aboveCtx >> blockSizeLog) & 0x1; + int left = (leftCtx >> blockSizeLog) & 0x1; + return ((left * 2) + above) + (blockSizeLog * PartitionProbabilitySet); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs index e5aa1f5b18..0a6092ce89 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs @@ -5,55 +5,57 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal enum Av1PartitionType { + // See section 6.10.4 of Avi Spcification + /// /// Not partitioned any further. /// - None, + None = 0, /// /// Horizontally split in 2 partitions. /// - Horizontal, + Horizontal = 1, /// /// Vertically split in 2 partitions. /// - Vertical, + Vertical = 2, /// /// 4 equally sized partitions. /// - Split, + Split = 3, /// /// Horizontal split and the top partition is split again. /// - HorizontalA, + HorizontalA = 4, /// /// Horizontal split and the bottom partition is split again. /// - HorizontalB, + HorizontalB = 5, /// /// Vertical split and the left partition is split again. /// - VerticalA, + VerticalA = 6, /// /// Vertical split and the right partitino is split again. /// - VerticalB, + VerticalB = 7, /// /// 4:1 horizontal partition. /// - Horizontal4, + Horizontal4 = 8, /// /// 4:1 vertical partition. /// - Vertical4, + Vertical4 = 9, /// /// Invalid value. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs new file mode 100644 index 0000000000..99ba78c3ba --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionTypeExtensions.cs @@ -0,0 +1,104 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1PartitionTypeExtensions +{ + private static readonly Av1BlockSize[][] PartitionSubSize = [ + [ + Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block128x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x128, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x4, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block32x8, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block64x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + ], [ + Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block4x16, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block8x32, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Block16x64, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + Av1BlockSize.Invalid, Av1BlockSize.Invalid, Av1BlockSize.Invalid, + ] + ]; + + public static Av1BlockSize GetBlockSubSize(this Av1PartitionType partition, Av1BlockSize blockSize) + => PartitionSubSize[(int)partition][(int)blockSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs index 345cd29fe0..c7aa943f8d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -43,8 +43,11 @@ internal interface IAv1TileDecoder /// /// Decode a single tile. /// + /// + /// The bytes of encoded data in the bitstream dedicated to this tile. + /// /// The index of the tile that is to be decoded. - void DecodeTile(int tileNum); + void DecodeTile(Span tileData, int tileNum); /// /// Finshed decoding all tiles of a frame. diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index c89e747f3a..2742264498 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal static class ObuConstants @@ -84,4 +86,31 @@ internal static class ObuConstants /// Maximum size of a loop restoration tile. /// public const int RestorationMaxTileSize = 256; + + /// + /// Number of Wiener coefficients to read. + /// + public const int WienerCoefficientCount = 3; + + public const int FrameLoopFilterCount = 4; + + /// + /// Value indicating alternative encoding of quantizer index delta values. + /// + public const int DeltaQuantizerSmall = 3; + + /// + /// Value indicating alternative encoding of loop filter delta values. + /// + public const int DeltaLoopFilterSmall = 3; + + /// + /// Maximum value used for loop filtering. + /// + public const int MaxLoopFilter = 63; + + /// + /// Maximum magnitude of AngleDeltaY and AngleDeltaUV. + /// + public const int MaxAngleDelta = 3; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 2c018101de..756f71f1f9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -899,7 +899,7 @@ private static void SetupPastIndependence(ObuFrameHeader frameInfo) } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -980,18 +980,16 @@ private static void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) { - int tileRow = tileNum / tileInfo.TileColumnCount; - int tileColumn = tileNum % tileInfo.TileColumnCount; bool isLastTile = tileNum == tileGroupEnd; - int tileSize = header.PayloadSize; + int tileDataSize = header.PayloadSize; if (!isLastTile) { - tileSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; - header.PayloadSize -= tileSize + tileInfo.TileSizeBytes; + tileDataSize = (int)reader.ReadLittleEndian(tileInfo.TileSizeBytes) + 1; + header.PayloadSize -= tileDataSize + tileInfo.TileSizeBytes; } - // TODO: Pass more info to the decoder. - decoder.DecodeTile(tileNum); + Span tileData = reader.GetSymbolReader(tileDataSize); + decoder.DecodeTile(tileData, tileNum); } if (tileGroupEnd != tileCount - 1) @@ -1101,8 +1099,8 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - frameInfo.SegmentationParameters.SegmentationEnabled = reader.ReadBoolean(); - Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentation not supported yet."); + frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); + Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet."); // TODO: Parse more stuff. } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs new file mode 100644 index 0000000000..1cfade4063 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal enum ObuSegmentationFeature +{ + None = 0, + LevelSkip, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index ed92abf4c4..f68c23adc9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -9,7 +9,13 @@ internal class ObuSegmentationParameters public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; - public bool SegmentationEnabled { get; internal set; } + public bool Enabled { get; internal set; } public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + + public bool SegmentIdPrecedesSkip { get; internal set; } + + public int LastActiveSegmentId { get; internal set; } + + internal bool IsFeatureActive(ObuSegmentationFeature feature) => false; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 9437cc6ea4..80b0675850 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -428,7 +428,7 @@ private static void SetupPastIndependence(ObuFrameHeader frameInfo) } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.SegmentationEnabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -560,7 +560,7 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - Guard.IsFalse(frameInfo.SegmentationParameters.SegmentationEnabled, nameof(frameInfo.SegmentationParameters.SegmentationEnabled), "Segmentatino not supported yet."); + Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentatino not supported yet."); writer.WriteBoolean(false); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs index a46733b007..dc35c96f31 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionMode.cs @@ -19,6 +19,7 @@ internal enum Av1PredictionMode SmoothVertical, SmoothHorizontal, Paeth, + UvChromaFromLuma, IntraModeStart = DC, IntraModeEnd = Paeth + 1, IntraModes = Paeth, diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs index 84832bb7b8..3b9f555ef5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -5,23 +5,125 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal static class Av1DefaultDistributions { - public static uint[] YMode => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] FrameYMode => + [ + new(22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349), + new(18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852), + new(19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659), + new(20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049) + ]; - public static uint[] UvModeCflNotAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[][] FilterYMode => + [ + [ + new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466), + new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409), + new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567), + new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462), + new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475) + ], [ + new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436), + new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794), + new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492), + new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210), + new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994) + ], [ + new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077), + new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907), + new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086), + new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252), + new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463) + ], [ + new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409), + new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342), + new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018), + new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528), + new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485) + ], [ + new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477), + new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277), + new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261), + new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482), + new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593) + ] + ]; - public static uint[] UvModeCflAllowed => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[][] UvMode => + [ + [ + new(22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961), + new(9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796), + new(9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029), + new(13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493), + new(9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363), + new(11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550), + new(14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667), + new(12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461), + new(12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416), + new(19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175), + new(18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148), + new(17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819), + new(12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606) + ], [ + new(10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212), + new(4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472), + new(5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679), + new(6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182), + new(4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376), + new(5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176), + new(4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940), + new(6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793), + new(5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989), + new(10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861), + new(10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838), + new(10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208), + new(3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048) + ] + ]; - public static uint[] AngleDelta => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] AngleDelta => + [ + new(2180, 5032, 7567, 22776, 26989, 30217), + new(2301, 5608, 8801, 23487, 26974, 30330), + new(3780, 11018, 13699, 19354, 23083, 31286), + new(4581, 11226, 15147, 17138, 21834, 28397), + new(1737, 10927, 14509, 19588, 22745, 28823), + new(2664, 10176, 12485, 17650, 21600, 30495), + new(2240, 11096, 15453, 20341, 22561, 28917), + new(3605, 10428, 12459, 17676, 21244, 30655) + ]; - public static uint[] IntraBlockCopy => [30531, 0, 0]; + public static Av1Distribution IntraBlockCopy => new(30531); - public static uint[] PartitionWidth8 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] PartitionTypes => + [ + new(19132, 25510, 30392), + new(13928, 19855, 28540), + new(12522, 23679, 28629), + new(9896, 18783, 25853), + new(15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902), + new(7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834), + new(5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117), + new(2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171), + new(18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544), + new(7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052), + new(6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047), + new(1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899), + new(20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724), + new(6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104), + new(5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238), + new(870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332), + new(27899, 28219, 28529, 32484, 32539, 32619, 32639), + new(6607, 6990, 8268, 32060, 32219, 32338, 32371), + new(5429, 6676, 7122, 32027, 32227, 32531, 32582), + new(711, 966, 1172, 32448, 32538, 32617, 32664) + ]; - public static uint[] PartitionWidth16 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] Skip => [new(31671), new(16515), new(4576)]; - public static uint[] PartitionWidth32 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution DeltaLoopFilterAbsolute => new(28160, 32120, 32677); - public static uint[] PartitionWidth64 => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); - public static uint[] SegmentId => [Av1SymbolReader.CdfProbabilityTop, 0]; + public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs new file mode 100644 index 0000000000..c3d33a4409 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs @@ -0,0 +1,114 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +/// +/// Class representing the probability distribution used for symbol coding. +/// +internal class Av1Distribution +{ + internal const int ProbabilityTop = 1 << ProbabilityBitCount; + internal const int ProbabilityMinimum = 4; + internal const int CdfShift = 15 - ProbabilityBitCount; + internal const int ProbabilityShift = 6; + + private const int ProbabilityBitCount = 15; + + private readonly uint[] probabilities; + private readonly int speed; + private int updateCount; + + public Av1Distribution(uint p0) + : this([p0, 0], 1) + { + } + + public Av1Distribution(uint p0, uint p1) + : this([p0, p1, 0], 1) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2) + : this([p0, p1, p2, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3) + : this([p0, p1, p2, p3, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4) + : this([p0, p1, p2, p3, p4, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5) + : this([p0, p1, p2, p3, p4, p5, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6) + : this([p0, p1, p2, p3, p4, p5, p6, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, 0], 2) + { + } + + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, 0], 2) + { + } + + private Av1Distribution(uint[] props, int speed) + { + // this.probabilities = props; + this.probabilities = new uint[props.Length]; + for (int i = 0; i < props.Length - 1; i++) + { + this.probabilities[i] = ProbabilityTop - props[i]; + } + + this.NumberOfSymbols = props.Length; + this.speed = speed; + } + + public int NumberOfSymbols { get; } + + public uint this[int index] => this.probabilities[index]; + + public void Update(int value) + { + int rate15 = this.updateCount > 15 ? 1 : 0; + int rate31 = this.updateCount > 31 ? 1 : 0; + int rate = 3 + rate15 + rate31 + this.speed; // + get_msb(nsymbs); + int tmp = ProbabilityTop; + + // Single loop (faster) + for (int i = 0; i < this.NumberOfSymbols - 1; i++) + { + tmp = (i == value) ? 0 : tmp; + uint p = this.probabilities[i]; + if (tmp < p) + { + this.probabilities[i] -= (ushort)((p - tmp) >> rate); + } + else + { + this.probabilities[i] += (ushort)((tmp - p) >> rate); + } + } + + int rate32 = this.updateCount < 32 ? 1 : 0; + this.updateCount += rate32; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs new file mode 100644 index 0000000000..f783c9cdda --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseAboveContext +{ + public int PartitionWidth { get; internal set; } + + internal void Clear() => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs new file mode 100644 index 0000000000..7f521c273e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseLeftContext +{ + public int PartitionHeight { get; internal set; } + + internal void Clear() => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs index 53ab655220..d0193031e2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -1,12 +1,128 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1SymbolDecoder +internal ref struct Av1SymbolDecoder { - private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; + private readonly Av1Distribution[] frameYMode = Av1DefaultDistributions.FrameYMode; + private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; + private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; + private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; + private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; + private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + private Av1SymbolReader reader; + + public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); + + public int ReadLiteral(int bitCount) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadLiteral(bitCount); + } + + public bool ReadUseIntraBlockCopy() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.tileIntraBlockCopy) > 0; + } + + public Av1PartitionType ReadPartitionSymbol(int context) + { + ref Av1SymbolReader r = ref this.reader; + return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]); + } + + public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) + { + Av1Distribution input = this.tilePartitionTypes[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Horizontal); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.HorizontalB); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Horizontal4); + } + + Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(distribution) > 0; + } + + public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context) + { + Av1Distribution input = this.tilePartitionTypes[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Vertical); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalB); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Vertical4); + } + + Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(distribution) > 0; + } + + public Av1PredictionMode ReadIntraFrameYMode(Av1BlockSize blockSize) + { + ref Av1SymbolReader r = ref this.reader; + return (Av1PredictionMode)r.ReadSymbol(this.frameYMode[(int)blockSize]); + } + + public Av1PredictionMode ReadUvMode(Av1BlockSize blockSize, bool chromaFromLumaAllowed) + { + int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; + ref Av1SymbolReader r = ref this.reader; + return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)blockSize]); + } + + public bool ReadSkip(int ctx) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.skip[ctx]) > 0; + } + + public int ReadDeltaLoopFilterAbsolute() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.deltaLoopFilterAbsolute); + } + + public int ReadDeltaQuantizerAbsolute() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.deltaQuantizerAbsolute); + } + + public int ReadSegmentId(int ctx) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.segmentId[ctx]); + } + + public int ReadAngleDelta(Av1PredictionMode mode) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.angleDelta[((int)mode) - 1]); + } + + public bool ReadUseFilterUltra() => throw new NotImplementedException(); + + public object ReadFilterUltraMode() => throw new NotImplementedException(); - public bool ReadUseIntraBlockCopySymbol(ref Av1SymbolReader reader) - => reader.ReadSymbol(this.tileIntraBlockCopy, 2) > 0; + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) + => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs index 6ff2807ece..7ed6ff7453 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs @@ -1,12 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Buffers; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1SymbolEncoder +internal class Av1SymbolEncoder : IDisposable { - private readonly uint[] tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + + private Av1SymbolWriter? writer; + + public Av1SymbolEncoder(Configuration configuration, int initialSize) + => this.writer = new(configuration, initialSize); + + public void WriteUseIntraBlockCopySymbol(bool value) + => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); + + public IMemoryOwner Exit() => this.writer!.Exit(); - public void WriteUseIntraBlockCopySymbol(Av1SymbolWriter writer, bool value) - => writer.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); + public void Dispose() + { + this.writer?.Dispose(); + this.writer = null; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs index 91e00676ab..19aed63865 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs @@ -5,13 +5,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolReader { - internal const int CdfProbabilityTop = 1 << CdfProbabilityBitCount; - internal const int ProbabilityMinimum = 4; - internal const int CdfShift = 15 - CdfProbabilityBitCount; - internal const int ProbabilityShift = 6; - internal static readonly int[] NumberOfSymbols2Speed = [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; - - private const int CdfProbabilityBitCount = 15; private const int DecoderWindowsSize = 32; private const int LotsOfBits = 0x4000; @@ -45,10 +38,12 @@ public Av1SymbolReader(Span span) this.Refill(); } - public int ReadSymbol(uint[] probabilities, int numberOfSymbols) + public int ReadSymbol(Av1Distribution distribution) { - int value = this.DecodeIntegerQ15(probabilities, numberOfSymbols); - UpdateCdf(probabilities, value, numberOfSymbols); + int value = this.DecodeIntegerQ15(distribution); + + // UpdateCdf(probabilities, value, numberOfSymbols); + distribution.Update(value); return value; } @@ -87,8 +82,8 @@ private bool DecodeBoolQ15(uint frequency) // assert(dif >> (DecoderWindowsSize - 16) < r); // assert(32768U <= r); - v = ((range >> 8) * (frequency >> ProbabilityShift)) >> (7 - ProbabilityShift); - v += ProbabilityMinimum; + v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v += Av1Distribution.ProbabilityMinimum; vw = v << (DecoderWindowsSize - 16); ret = true; newRange = v; @@ -106,17 +101,13 @@ private bool DecodeBoolQ15(uint frequency) /// /// Decodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. /// - /// + /// /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. /// - /// - /// The number of symbols in the alphabet. - /// This should be at most 16. - /// /// The decoded symbol. - private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) + private int DecodeIntegerQ15(Av1Distribution distribution) { uint c; uint u; @@ -125,20 +116,20 @@ private int DecodeIntegerQ15(uint[] probabilities, int numberOfSymbols) uint dif = this.difference; uint r = this.range; - int n = numberOfSymbols - 1; + int n = distribution.NumberOfSymbols - 1; DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r)); - DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last value in probability array needs to be zero."); + DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero."); DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); - DebugGuard.MustBeGreaterThanOrEqualTo(7 - ProbabilityShift - CdfShift, 0, nameof(CdfShift)); + DebugGuard.MustBeGreaterThanOrEqualTo(7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift, 0, nameof(Av1Distribution.CdfShift)); c = dif >> (DecoderWindowsSize - 16); v = r; ret = -1; do { u = v; - v = ((r >> 8) * (probabilities[++ret] >> ProbabilityShift)) >> (7 - ProbabilityShift - CdfShift); - v += (uint)(ProbabilityMinimum * (n - ret)); + v = ((r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift); + v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret)); } while (c < v); @@ -213,31 +204,4 @@ private void Refill() this.count = cnt; this.position = position; } - - internal static void UpdateCdf(uint[] probabilities, int value, int numberOfSymbols) - { - DebugGuard.MustBeLessThan(numberOfSymbols, 17, nameof(numberOfSymbols)); - int rate15 = probabilities[numberOfSymbols] > 15 ? 1 : 0; - int rate31 = probabilities[numberOfSymbols] > 31 ? 1 : 0; - int rate = 3 + rate15 + rate31 + NumberOfSymbols2Speed[numberOfSymbols]; // + get_msb(nsymbs); - int tmp = CdfProbabilityTop; - - // Single loop (faster) - for (int i = 0; i < numberOfSymbols - 1; i++) - { - tmp = (i == value) ? 0 : tmp; - uint p = probabilities[i]; - if (tmp < p) - { - probabilities[i] -= (ushort)((p - tmp) >> rate); - } - else - { - probabilities[i] += (ushort)((tmp - p) >> rate); - } - } - - uint rate32 = probabilities[numberOfSymbols] < 32 ? 1U : 0U; - probabilities[numberOfSymbols] += rate32; - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs index 296b516fb1..cf36b04a11 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -25,14 +25,14 @@ public Av1SymbolWriter(Configuration configuration, int initialSize) public void Dispose() => this.memory.Dispose(); - public void WriteSymbol(int symbol, uint[] probabilities, int numberOfSymbols) + public void WriteSymbol(int symbol, Av1Distribution distribution, int numberOfSymbols) { DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol)); - DebugGuard.IsTrue(probabilities[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); + DebugGuard.IsTrue(distribution[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); - this.EncodeIntegerQ15(symbol, probabilities, numberOfSymbols); - Av1SymbolReader.UpdateCdf(probabilities, symbol, numberOfSymbols); + this.EncodeIntegerQ15(symbol, distribution, numberOfSymbols); + distribution.Update(symbol); } public void WriteLiteral(uint value, int bitCount) @@ -104,8 +104,8 @@ private void EncodeBoolQ15(bool val, uint frequency) l = this.low; r = this.rng; DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); - v = ((r >> 8) * (frequency >> Av1SymbolReader.ProbabilityShift)) >> (7 - Av1SymbolReader.ProbabilityShift); - v += Av1SymbolReader.ProbabilityMinimum; + v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v += Av1Distribution.ProbabilityMinimum; if (val) { l += r - v; @@ -123,7 +123,7 @@ private void EncodeBoolQ15(bool val, uint frequency) /// Encodes a symbol given an inverse cumulative distribution function(CDF) table in Q15. /// /// The value to encode. - /// + /// /// CDF_PROB_TOP minus the CDF, such that symbol s falls in the range /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. @@ -132,12 +132,12 @@ private void EncodeBoolQ15(bool val, uint frequency) /// The number of symbols in the alphabet. /// This should be at most 16. /// - private void EncodeIntegerQ15(int symbol, uint[] probabilities, int numberOfSymbols) - => this.EncodeIntegerQ15(symbol > 0 ? probabilities[symbol - 1] : Av1SymbolReader.CdfProbabilityTop, probabilities[symbol], symbol, numberOfSymbols); + private void EncodeIntegerQ15(int symbol, Av1Distribution distribution, int numberOfSymbols) + => this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, numberOfSymbols); private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) { - const int totalShift = 7 - Av1SymbolReader.ProbabilityShift - Av1SymbolReader.CdfShift; + const int totalShift = 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift; uint l = this.low; uint r = this.rng; DebugGuard.MustBeLessThanOrEqualTo(32768U, r, nameof(r)); @@ -145,21 +145,21 @@ private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, DebugGuard.MustBeLessThanOrEqualTo(lowFrequency, 32768U, nameof(lowFrequency)); DebugGuard.MustBeGreaterThanOrEqualTo(totalShift, 0, nameof(totalShift)); int n = numberOfSymbols - 1; - if (lowFrequency < Av1SymbolReader.CdfProbabilityTop) + if (lowFrequency < Av1Distribution.ProbabilityTop) { uint u; uint v; - u = (uint)((((r >> 8) * (lowFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - (symbol - 1)))); - v = (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); + u = (uint)((((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - (symbol - 1)))); + v = (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - symbol))); l += r - u; r = u - v; } else { - r -= (uint)((((r >> 8) * (highFrequency >> Av1SymbolReader.ProbabilityShift)) >> totalShift) + - (Av1SymbolReader.ProbabilityMinimum * (n - symbol))); + r -= (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + + (Av1Distribution.ProbabilityMinimum * (n - symbol))); } this.Normalize(l, r); diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 3db631e0b3..8366a8e887 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -13,4 +13,12 @@ public static int GetScale(this Av1TransformSize size) int pels = Size2d[(int)size]; return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0; } + + public static int GetWidth(this Av1TransformSize size) => (int)size; + + public static int GetHeight(this Av1TransformSize size) => (int)size; + + public static int GetWidthLog2(this Av1TransformSize size) => (int)size; + + public static int GetHeightLog2(this Av1TransformSize size) => (int)size; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index 36ae4cfe84..9f0dbc2844 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -20,7 +20,7 @@ internal class Av1TileDecoderStub : IAv1TileDecoder public ObuTileInfo TileInfo { get; } = new ObuTileInfo(); - public void DecodeTile(int tileNum) + public void DecodeTile(Span tileData, int tileNum) { } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 99f38c118f..36b66a8b77 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; using SixLabors.ImageSharp.Memory; @@ -185,30 +186,53 @@ private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected Assert.Equal(expectedValues, values); } + [Theory] + [InlineData(0, 255, 255, 255, 158)] + + // [InlineData(1, 255, 255, 255, 158)] + // [InlineData(4, 255, 207, 254, 18)] + public void ReadPartitionTypeSymbols(int ctx, byte b0, byte b1, byte b2, byte b3) + { + // Assign + byte[] values = [b0, b1, b2, b3, 0]; + Av1SymbolDecoder decoder = new(values); + Av1PartitionType[] expected = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None ]; + Av1PartitionType[] actuals = new Av1PartitionType[expected.Length]; + + // Act + for (int i = 0; i < expected.Length; i++) + { + actuals[i] = decoder.ReadPartitionSymbol(ctx); + } + + // Assert + Assert.Equal(expected, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { // Assign bool[] values = [true, true, false, true, false, false, false]; - MemoryStream output = new(100); Configuration configuration = Configuration.Default; - using Av1SymbolWriter writer = new(configuration, 100 / 8); - Av1SymbolEncoder encoder = new(); - Av1SymbolDecoder decoder = new(); + Av1SymbolEncoder encoder = new(configuration, 100 / 8); bool[] actuals = new bool[values.Length]; // Act foreach (bool value in values) { - encoder.WriteUseIntraBlockCopySymbol(writer, value); + encoder.WriteUseIntraBlockCopySymbol(value); } - using IMemoryOwner encoded = writer.Exit(); + using IMemoryOwner encoded = encoder.Exit(); + Av1SymbolDecoder decoder = new(encoded.GetSpan()); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadUseIntraBlockCopySymbol(ref reader); + actuals[i] = decoder.ReadUseIntraBlockCopy(); } // Assert From b26c0fa18391dfbe0db2a58a0ce4acdb547b2773 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 1 Jun 2024 11:08:44 +0200 Subject: [PATCH 061/190] PartitionType symbol round trips --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 2 +- .../Heif/Av1/Symbol/Av1SymbolDecoder.cs | 2 +- .../Heif/Av1/Symbol/Av1SymbolEncoder.cs | 8 +++-- .../Heif/Av1/Symbol/Av1SymbolWriter.cs | 16 ++++----- .../Formats/Heif/Av1/SymbolTest.cs | 34 +++++++++++-------- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 0a8fac5d0e..35ce9c37ff 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -205,7 +205,7 @@ private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int colu else if (hasRows && hasColumns) { int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); - partitionType = reader.ReadPartitionSymbol(ctx); + partitionType = reader.ReadPartitionType(ctx); } else if (hasColumns) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs index d0193031e2..faec9c8214 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -32,7 +32,7 @@ public bool ReadUseIntraBlockCopy() return r.ReadSymbol(this.tileIntraBlockCopy) > 0; } - public Av1PartitionType ReadPartitionSymbol(int context) + public Av1PartitionType ReadPartitionType(int context) { ref Av1SymbolReader r = ref this.reader; return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]); diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs index 7ed6ff7453..391cd13605 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs @@ -8,14 +8,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1SymbolEncoder : IDisposable { private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; + private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; private Av1SymbolWriter? writer; public Av1SymbolEncoder(Configuration configuration, int initialSize) => this.writer = new(configuration, initialSize); - public void WriteUseIntraBlockCopySymbol(bool value) - => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy, 2); + public void WriteUseIntraBlockCopy(bool value) + => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + + public void WritePartitionType(Av1PartitionType value, int context) + => this.writer!.WriteSymbol((int)value, this.tilePartitionTypes[context]); public IMemoryOwner Exit() => this.writer!.Exit(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs index cf36b04a11..2752facb4d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs @@ -25,13 +25,13 @@ public Av1SymbolWriter(Configuration configuration, int initialSize) public void Dispose() => this.memory.Dispose(); - public void WriteSymbol(int symbol, Av1Distribution distribution, int numberOfSymbols) + public void WriteSymbol(int symbol, Av1Distribution distribution) { DebugGuard.MustBeGreaterThanOrEqualTo(symbol, 0, nameof(symbol)); - DebugGuard.MustBeLessThan(symbol, numberOfSymbols, nameof(symbol)); - DebugGuard.IsTrue(distribution[numberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); + DebugGuard.MustBeLessThan(symbol, distribution.NumberOfSymbols, nameof(symbol)); + DebugGuard.IsTrue(distribution[distribution.NumberOfSymbols - 1] == 0, "Last entry in Probabilities table needs to be zero."); - this.EncodeIntegerQ15(symbol, distribution, numberOfSymbols); + this.EncodeIntegerQ15(symbol, distribution); distribution.Update(symbol); } @@ -128,12 +128,8 @@ private void EncodeBoolQ15(bool val, uint frequency) /// [s > 0 ? (CDF_PROB_TOP - icdf[s - 1]) : 0, CDF_PROB_TOP - icdf[s]). /// The values must be monotonically non - increasing, and icdf[nsyms - 1] must be 0. /// - /// - /// The number of symbols in the alphabet. - /// This should be at most 16. - /// - private void EncodeIntegerQ15(int symbol, Av1Distribution distribution, int numberOfSymbols) - => this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, numberOfSymbols); + private void EncodeIntegerQ15(int symbol, Av1Distribution distribution) + => this.EncodeIntegerQ15(symbol > 0 ? distribution[symbol - 1] : Av1Distribution.ProbabilityTop, distribution[symbol], symbol, distribution.NumberOfSymbols); private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, int numberOfSymbols) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 36b66a8b77..943eced4ce 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -186,29 +186,35 @@ private static void AssertRawBytesRead(int bitCount, byte[] buffer, int expected Assert.Equal(expectedValues, values); } - [Theory] - [InlineData(0, 255, 255, 255, 158)] - - // [InlineData(1, 255, 255, 255, 158)] - // [InlineData(4, 255, 207, 254, 18)] - public void ReadPartitionTypeSymbols(int ctx, byte b0, byte b1, byte b2, byte b3) + [Fact] + public void RoundTripPartitionType() { // Assign - byte[] values = [b0, b1, b2, b3, 0]; - Av1SymbolDecoder decoder = new(values); - Av1PartitionType[] expected = [ + int ctx = 7; + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None ]; - Av1PartitionType[] actuals = new Av1PartitionType[expected.Length]; + Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; // Act - for (int i = 0; i < expected.Length; i++) + foreach (Av1PartitionType value in values) + { + encoder.WritePartitionType(value, 7); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan()); + Av1SymbolReader reader = new(encoded.GetSpan()); + for (int i = 0; i < values.Length; i++) { - actuals[i] = decoder.ReadPartitionSymbol(ctx); + actuals[i] = decoder.ReadPartitionType(ctx); } // Assert - Assert.Equal(expected, actuals); + Assert.Equal(values, actuals); } [Fact] @@ -223,7 +229,7 @@ public void RoundTripUseIntraBlockCopy() // Act foreach (bool value in values) { - encoder.WriteUseIntraBlockCopySymbol(value); + encoder.WriteUseIntraBlockCopy(value); } using IMemoryOwner encoded = encoder.Exit(); From 1184968ca6a5e9388ffe37a0b12cc7527beab27d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 1 Jun 2024 12:34:17 +0200 Subject: [PATCH 062/190] Introduce Tile Decoder --- .../Heif/Av1/Av1BlockSizeExtensions.cs | 44 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 799 +--------------- .../ObuSegmentationParameters.cs | 3 +- .../Formats/Heif/Av1/Symbol/Av1TileDecoder.cs | 862 ++++++++++++++++++ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 1 + 5 files changed, 912 insertions(+), 797 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index f6e7d6f3f8..9c16b63def 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -10,6 +10,35 @@ internal static class Av1BlockSizeExtensions private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]; private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]; + private static readonly Av1BlockSize[][][] SubSampled = + [ + + // ss_x == 0 ss_x == 0 ss_x == 1 ss_x == 1 + // ss_y == 0 ss_y == 1 ss_y == 0 ss_y == 1 + [[Av1BlockSize.Block4x4, Av1BlockSize.Block4x4], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]], + [[Av1BlockSize.Block4x8, Av1BlockSize.Block4x4], [Av1BlockSize.Invalid, Av1BlockSize.Block4x4]], + [[Av1BlockSize.Block8x4, Av1BlockSize.Invalid], [Av1BlockSize.Block4x4, Av1BlockSize.Block4x4]], + [[Av1BlockSize.Block8x8, Av1BlockSize.Block8x4], [Av1BlockSize.Block4x8, Av1BlockSize.Block4x4]], + [[Av1BlockSize.Block8x16, Av1BlockSize.Block8x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]], + [[Av1BlockSize.Block16x8, Av1BlockSize.Invalid], [Av1BlockSize.Block8x8, Av1BlockSize.Block8x4]], + [[Av1BlockSize.Block16x16, Av1BlockSize.Block16x8], [Av1BlockSize.Block8x16, Av1BlockSize.Block8x8]], + [[Av1BlockSize.Block16x32, Av1BlockSize.Block16x16], [Av1BlockSize.Invalid, Av1BlockSize.Block8x16]], + [[Av1BlockSize.Block32x16, Av1BlockSize.Invalid], [Av1BlockSize.Block16x16, Av1BlockSize.Block16x8]], + [[Av1BlockSize.Block32x32, Av1BlockSize.Block32x16], [Av1BlockSize.Block16x32, Av1BlockSize.Block16x16]], + [[Av1BlockSize.Block32x64, Av1BlockSize.Block32x32], [Av1BlockSize.Invalid, Av1BlockSize.Block16x32]], + [[Av1BlockSize.Block64x32, Av1BlockSize.Invalid], [Av1BlockSize.Block32x32, Av1BlockSize.Block32x16]], + [[Av1BlockSize.Block64x64, Av1BlockSize.Block64x32], [Av1BlockSize.Block32x64, Av1BlockSize.Block32x32]], + [[Av1BlockSize.Block64x128, Av1BlockSize.Block64x64], [Av1BlockSize.Invalid, Av1BlockSize.Block32x64]], + [[Av1BlockSize.Block128x64, Av1BlockSize.Invalid], [Av1BlockSize.Block64x64, Av1BlockSize.Block64x32]], + [[Av1BlockSize.Block128x128, Av1BlockSize.Block128x64], [Av1BlockSize.Block64x128, Av1BlockSize.Block64x64]], + [[Av1BlockSize.Block4x16, Av1BlockSize.Block4x8], [Av1BlockSize.Invalid, Av1BlockSize.Block4x8]], + [[Av1BlockSize.Block16x4, Av1BlockSize.Invalid], [Av1BlockSize.Block8x4, Av1BlockSize.Block8x4]], + [[Av1BlockSize.Block8x32, Av1BlockSize.Block8x16], [Av1BlockSize.Invalid, Av1BlockSize.Block4x16]], + [[Av1BlockSize.Block32x8, Av1BlockSize.Invalid], [Av1BlockSize.Block16x8, Av1BlockSize.Block16x4]], + [[Av1BlockSize.Block16x64, Av1BlockSize.Block16x32], [Av1BlockSize.Invalid, Av1BlockSize.Block8x32]], + [[Av1BlockSize.Block64x16, Av1BlockSize.Invalid], [Av1BlockSize.Block32x16, Av1BlockSize.Block32x8]] + ]; + private static readonly Av1TransformSize[] MaxTransformSize = [ Av1TransformSize.Size4x4, Av1TransformSize.Size4x8, Av1TransformSize.Size8x4, Av1TransformSize.Size8x8, Av1TransformSize.Size8x16, Av1TransformSize.Size16x8, Av1TransformSize.Size16x16, Av1TransformSize.Size16x32, @@ -48,7 +77,20 @@ public static int Get4x4HeightLog2(this Av1BlockSize blockSize) => Get4x4HighCount(blockSize) << 2; /// - /// Returns th largest transform size that can be used for blocks of given size. + /// Returns the block size of a sub sampled block. + /// + public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY) + { + if (blockSize == Av1BlockSize.Invalid) + { + return Av1BlockSize.Invalid; + } + + return SubSampled[(int)blockSize][subX ? 1 : 0][subY ? 1 : 0]; + } + + /// + /// Returns the largest transform size that can be used for blocks of given size. /// The can be either a square or rectangular block. /// public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 35ce9c37ff..1fd5d3c2f2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -2,61 +2,13 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal class Av1Decoder : IAv1TileDecoder { - private static readonly int[] SgrprojXqdMid = [-32, 31]; - private static readonly int[] WienerTapsMid = [3, -7, 15]; - private const int PartitionProbabilitySet = 4; - - private int[] deltaLoopFilter = []; - private bool[][][] blockDecoded = []; - private int[][] referenceSgrXqd = []; - private int[][][] referenceLrWiener = []; - private bool availableUp; - private bool availableLeft; - private bool availableUpForChroma; - private bool availableLeftForChroma; - private Av1ParseAboveContext aboveContext = new(); - private Av1ParseLeftContext leftContext = new(); - private bool skip; - private bool readDeltas; - private int currentQuantizerIndex; - private Av1PredictionMode[][] YModes = []; - private Av1PredictionMode YMode = Av1PredictionMode.DC; - private Av1PredictionMode UvMode = Av1PredictionMode.DC; - private Av1PredictionMode[][] UvModes = []; - private object[] ReferenceFrame = []; - private object[][][] ReferenceFrames = []; - private bool HasChroma; - private bool SubsamplingX; - private bool SubsamplingY; - private int PaletteSizeY; - private int PaletteSizeUv; - private object[][] aboveLevelContext = []; - private object[][] aboveDcContext = []; - private object[][] leftLevelContext = []; - private object[][] leftDcContext = []; - private Av1TransformSize TransformSize = Av1TransformSize.Size4x4; - private bool skipMode; - private bool IsChromaForLumaAllowed; - private object FilterUltraMode = -1; - private int AngleDeltaY; - private int AngleDeltaUv; - private bool Lossless; - private int[][] SegmentIds = []; - private int MaxLumaWidth; - private int MaxLumaHeight; - private int segmentId; - private int[][] cdefIndex = []; - private int deltaLoopFilterResolution; - private int deltaQuantizerResolution; + private readonly Av1TileDecoder tileDecoder; public Av1Decoder() { @@ -64,7 +16,7 @@ public Av1Decoder() this.SequenceHeader = new ObuSequenceHeader(); this.TileInfo = new ObuTileInfo(); this.SeenFrameHeader = false; - this.currentQuantizerIndex = -1; + this.tileDecoder = new Av1TileDecoder(this.SequenceHeader, this.FrameInfo, this.TileInfo); } public bool SequenceHeaderDone { get; set; } @@ -86,751 +38,8 @@ public void Decode(Span buffer) } public void DecodeTile(Span tileData, int tileNum) - { - Av1SymbolDecoder reader = new(tileData); - int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; - int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; - this.aboveContext.Clear(); - this.ClearLoopFilterDelta(); - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - this.referenceSgrXqd = new int[planesCount][]; - this.referenceLrWiener = new int[planesCount][][]; - for (int plane = 0; plane < planesCount; plane++) - { - this.referenceSgrXqd[plane] = new int[2]; - Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length); - this.referenceLrWiener[plane] = new int[2][]; - for (int pass = 0; pass < 2; pass++) - { - this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount]; - Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length); - } - } - - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - int superBlock4x4Size = superBlockSize.Get4x4WideCount(); - for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) - { - int superBlockRow = (row << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2; - this.leftContext.Clear(); - for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) - { - int superBlockColumn = (column << ObuConstants.ModeInfoSizeLog2) >> this.SequenceHeader.SuperBlockSizeLog2; - bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - - bool ReadDeltas = this.FrameInfo.DeltaQParameters.IsPresent; - - // Nothing to do for CDEF - // this.ClearCdef(row, column); - this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - this.ReadLoopRestoration(row, column, superBlockSize); - this.DecodePartition(ref reader, row, column, superBlockSize); - } - } - } - - private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4]; - - private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) - { - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - for (int plane = 0; plane < planesCount; plane++) - { - int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0; - int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0; - int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX; - int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY; - for (int y = -1; y <= (superBlock4x4Size >> subY); y++) - { - for (int x = -1; x <= (superBlock4x4Size >> subX); x++) - { - if (y < 0 && x < superBlock4x4Width) - { - this.blockDecoded[plane][y][x] = true; - } - else if (x < 0 && y < superBlock4x4Height) - { - this.blockDecoded[plane][y][x] = true; - } - else - { - this.blockDecoded[plane][y][x] = false; - } - } - } - - this.blockDecoded[plane][superBlock4x4Size >> subY][-1] = false; - } - } - - private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize) - { - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - for (int plane = 0; plane < planesCount; plane++) - { - if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None) - { - // TODO: Implement. - throw new NotImplementedException("No loop restoration filter support."); - } - } - } + => this.tileDecoder.DecodeTile(tileData, tileNum); public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) - { - // TODO: Implement - } - - private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || - columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) - { - return; - } - - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); - int block4x4Size = blockSize.Get4x4WideCount(); - int halfBlock4x4Size = block4x4Size >> 1; - int quarterBlock4x4Size = halfBlock4x4Size >> 2; - bool hasRows = (rowIndex + halfBlock4x4Size) < this.TileInfo.TileRowCount; - bool hasColumns = (columnIndex + halfBlock4x4Size) < this.TileInfo.TileColumnCount; - Av1PartitionType partitionType = Av1PartitionType.Split; - if (blockSize < Av1BlockSize.Block8x8) - { - partitionType = Av1PartitionType.None; - } - else if (hasRows && hasColumns) - { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); - partitionType = reader.ReadPartitionType(ctx); - } - else if (hasColumns) - { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); - bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); - partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; - } - else if (hasRows) - { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); - bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); - partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; - } - - Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); - Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize); - switch (partitionType) - { - case Av1PartitionType.Split: - this.DecodePartition(ref reader, rowIndex, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); - break; - case Av1PartitionType.None: - this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize); - break; - default: - throw new NotImplementedException($"Partition type: {partitionType} is not supported."); - } - } - - private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - int block4x4Width = blockSize.Get4x4WideCount(); - int block4x4Height = blockSize.Get4x4HighCount(); - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - bool hasChroma = planesCount > 1; - if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0) - { - hasChroma = false; - } - - if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0) - { - hasChroma = false; - } - - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); - this.availableUpForChroma = this.availableUp; - this.availableLeftForChroma = this.availableLeft; - if (hasChroma) - { - if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) - { - this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); - } - - if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) - { - this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); - } - } - - this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize); - this.ReadPaletteTokens(ref reader); - this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - if (this.skip) - { - this.ResetBlockContext(rowIndex, columnIndex, block4x4Width, block4x4Height); - } - - bool isCompound = false; - for (int y = 0; y < block4x4Height; y++) - { - for (int x = 0; x < block4x4Width; x++) - { - this.YModes[rowIndex + y][columnIndex + x] = this.YMode; - if (this.ReferenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma) - { - this.UvModes[rowIndex + y][columnIndex + x] = this.UvMode; - } - - for (int refList = 0; refList < 2; refList++) - { - this.ReferenceFrames[rowIndex + y][columnIndex + x][refList] = this.ReferenceFrame[refList]; - } - } - } - - this.ComputePrediction(); - this.Residual(rowIndex, columnIndex, blockSize); - } - - private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; - int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6); - int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6); - Av1BlockSize sizeChunk = (widthChunks > 1 || heightChunks > 1) ? Av1BlockSize.Block64x64 : blockSize; - - for (int chunkY = 0; chunkY < heightChunks; chunkY++) - { - for (int chunkX = 0; chunkX < widthChunks; chunkX++) - { - int rowChunk = rowIndex + (chunkY << 4); - int columnChunk = columnIndex + (chunkX << 4); - int subBlockRow = rowChunk & superBlockMask; - int subBlockColumn = columnChunk & superBlockMask; - for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++) - { - Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.TransformSize); - int stepX = transformSize.GetWidth() >> 2; - int stepY = transformSize.GetHeight() >> 2; - Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); - int num4x4Width = planeSize.Get4x4WideCount(); - int num4x4Height = planeSize.Get4x4HighCount(); - int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; - int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; - int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); - for (int y = 0; y < num4x4Height; y += stepY) - { - for (int x = 0; x < num4x4Width; x += stepX) - { - this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + ((chunkX << 4) >> subX), y + ((chunkY << 4) >> subY)); - } - } - } - } - } - } - - private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); - - private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException(); - - private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) - { - int startX = baseX + (4 * x); - int startY = baseY + (4 * y); - int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; - int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; - int columnIndex = (startX << subX) >> ObuConstants.ModeInfoSizeLog2; - int rowIndex = (startY << subY) >> ObuConstants.ModeInfoSizeLog2; - int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; - int subBlockColumn = columnIndex & superBlockMask; - int subBlockRow = rowIndex & superBlockMask; - int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2; - int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2; - int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX; - int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY; - if (startX >= maxX || startY >= maxY) - { - return; - } - - if ((plane == 0 && this.PaletteSizeY > 0) || - (plane != 0 && this.PaletteSizeUv > 0)) - { - this.PredictPalette(plane, startX, startY, x, y, transformSize); - } - else - { - bool isChromaFromLuma = plane > 0 && this.UvMode == Av1PredictionMode.UvChromaFromLuma; - Av1PredictionMode mode; - if (plane == 0) - { - mode = this.YMode; - } - else - { - mode = isChromaFromLuma ? Av1PredictionMode.DC : this.UvMode; - } - - int log2Width = transformSize.GetWidthLog2(); - int log2Height = transformSize.GetHeightLog2(); - bool leftAvailable = (x > 0) || plane == 0 ? this.availableLeft : this.availableLeftForChroma; - bool upAvailable = (y > 0) || plane == 0 ? this.availableUp : this.availableUpForChroma; - bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; - bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; - this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); - if (isChromaFromLuma) - { - this.PredictChromaFromLuma(plane, startX, startY, transformSize); - } - } - - if (plane == 0) - { - this.MaxLumaWidth = startX + (stepX * 4); - this.MaxLumaHeight = startY + (stepY * 4); - } - - if (!this.skip) - { - int eob = this.Coefficients(plane, startX, startY, transformSize); - if (eob > 0) - { - this.Reconstruct(plane, startX, startY, transformSize); - } - } - - for (int i = 0; i < stepY; i++) - { - for (int j = 0; j < stepX; j++) - { - // Ignore loop filter. - this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true; - } - } - } - - private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException(); - - private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private void ComputePrediction() - { - // Not applicable for INTRA frames. - } - - private void ResetBlockContext(int rowIndex, int columnIndex, int block4x4Width, int block4x4Height) - { - for (int plane = 0; plane < 1 + (this.HasChroma ? 2 : 0); plane++) - { - int subX = (plane > 0 && this.SubsamplingX) ? 1 : 0; - int subY = (plane > 0 && this.SubsamplingY) ? 1 : 0; - for (int i = columnIndex >> subX; i < ((columnIndex + block4x4Width) >> subX); i++) - { - this.aboveLevelContext[plane][i] = 0; - this.aboveDcContext[plane][i] = 0; - } - - for (int i = rowIndex >> subY; i < ((rowIndex + block4x4Height) >> subY); i++) - { - this.leftLevelContext[plane][i] = 0; - this.leftDcContext[plane][i] = 0; - } - } - } - - private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - int block4x4Width = blockSize.Get4x4WideCount(); - int block4x4Height = blockSize.Get4x4HighCount(); - - // First condition in spec is for INTER frames, implemented only the INTRA condition. - this.ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++) - { - for (int column = columnIndex; column < columnIndex + block4x4Width; column++) - { - this.InterTransformSizes[row][column] = this.TransformSize; - } - }*/ - } - - private void ReadPaletteTokens(ref Av1SymbolDecoder reader) - { - if (this.PaletteSizeY != 0) - { - // Todo: Implement. - throw new NotImplementedException(); - } - - if (this.PaletteSizeUv != 0) - { - // Todo: Implement. - throw new NotImplementedException(); - } - } - - private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize); - - private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - this.skip = false; - if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) - { - this.ReadIntraSegmentId(ref reader); - } - - this.skipMode = false; - this.ReadSkip(ref reader); - if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) - { - this.IntraSegmentId(ref reader, rowIndex, columnIndex); - } - - this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize); - this.ReadDeltaQuantizerIndex(ref reader, blockSize); - this.ReadDeltaLoopFilter(ref reader, blockSize); - this.readDeltas = false; - this.ReferenceFrame[0] = -1; // IntraFrame; - this.ReferenceFrame[1] = -1; // None; - bool useIntraBlockCopy = false; - if (this.FrameInfo.AllowIntraBlockCopy) - { - useIntraBlockCopy = reader.ReadUseIntraBlockCopy(); - } - - if (useIntraBlockCopy) - { - // TODO: Implement - } - else - { - // this.IsInter = false; - this.YMode = reader.ReadIntraFrameYMode(blockSize); - this.IntraAngleInfoY(ref reader, blockSize); - if (this.HasChroma) - { - this.UvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed); - if (this.UvMode == Av1PredictionMode.UvChromaFromLuma) - { - this.ReadChromaFromLumaAlphas(ref reader); - } - - this.IntraAngleInfoUv(ref reader, blockSize); - } - - this.PaletteSizeY = 0; - this.PaletteSizeUv = 0; - if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 && - this.FrameInfo.AllowScreenContentTools) - { - this.PaletteModeInfo(ref reader); - } - - this.FilterIntraModeInfo(ref reader, blockSize); - } - } - - private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException(); - - private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - bool useFilterIntra = false; - if (this.SequenceHeader.EnableFilterIntra && - this.YMode == Av1PredictionMode.DC && this.PaletteSizeY == 0 && - Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32) - { - useFilterIntra = reader.ReadUseFilterUltra(); - if (useFilterIntra) - { - this.FilterUltraMode = reader.ReadFilterUltraMode(); - } - } - } - - private void PaletteModeInfo(ref Av1SymbolDecoder reader) => - - // TODO: Implement. - throw new NotImplementedException(); - - private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) => - - // TODO: Implement. - throw new NotImplementedException(); - - private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - this.AngleDeltaY = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.YMode)) - { - int angleDeltaY = reader.ReadAngleDelta(this.YMode); - this.AngleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta; - } - } - - private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - this.AngleDeltaUv = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.UvMode)) - { - int angleDeltaUv = reader.ReadAngleDelta(this.UvMode); - this.AngleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta; - } - } - - private static bool IsDirectionalMode(Av1PredictionMode mode) - => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; - - private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) - { - if (this.FrameInfo.SegmentationParameters.Enabled) - { - this.ReadSegmentId(ref reader, rowIndex, columnIndex); - } - else - { - this.segmentId = 0; - } - - this.Lossless = this.FrameInfo.LosslessArray[this.segmentId]; - } - - private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) - { - int pred; - int prevUL = -1; - int prevU = -1; - int prevL = -1; - if (this.availableUp && this.availableLeft) - { - prevUL = this.SegmentIds[rowIndex - 1][columnIndex - 1]; - } - - if (this.availableUp) - { - prevU = this.SegmentIds[rowIndex - 1][columnIndex]; - } - - if (this.availableLeft) - { - prevU = this.SegmentIds[rowIndex][columnIndex - 1]; - } - - if (prevU == -1) - { - pred = (prevL == -1) ? 0 : prevL; - } - else if (prevL == -1) - { - pred = prevU; - } - else - { - pred = (prevU == prevUL) ? prevU : prevL; - } - - if (this.skip) - { - this.segmentId = 0; - } - else - { - int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; - this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1); - } - } - - private static int NegativeDeinterleave(int diff, int reference, int max) - { - if (reference == 0) - { - return diff; - } - - if (reference >= (max - 1)) - { - return max - diff - 1; - } - - if (2 * reference < max) - { - if (diff <= 2 * reference) - { - if ((diff & 1) > 0) - { - return reference + ((diff + 1) >> 1); - } - else - { - return reference - (diff >> 1); - } - } - - return diff; - } - else - { - if (diff <= 2 * (max - reference - 1)) - { - if ((diff & 1) > 0) - { - return reference + ((diff + 1) >> 1); - } - else - { - return reference - (diff >> 1); - } - } - - return max - (diff + 1); - } - } - - private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) - { - return; - } - - int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount(); - int cdefMask4 = ~(cdefSize4 - 1); - int r = rowIndex & cdefMask4; - int c = columnIndex & cdefMask4; - if (this.cdefIndex[r][c] == -1) - { - this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); - int w4 = blockSize.Get4x4WideCount(); - int h4 = blockSize.Get4x4HighCount(); - for (int i = r; i < r + h4; i += cdefSize4) - { - for (int j = c; j < c + w4; j += cdefSize4) - { - this.cdefIndex[i][j] = this.cdefIndex[r][c]; - } - } - } - } - - private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) - { - return; - } - - if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent) - { - int frameLoopFilterCount = 1; - if (this.FrameInfo.DeltaLoopFilterParameters.Multi) - { - frameLoopFilterCount = (this.SequenceHeader.ColorConfig.ChannelCount > 1) ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2; - } - - for (int i = 0; i < frameLoopFilterCount; i++) - { - int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); - if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall) - { - int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1; - int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits); - deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1; - } - - if (deltaLoopFilterAbsolute != 0) - { - bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; - int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); - } - } - } - } - - private void ReadSkip(ref Av1SymbolDecoder reader) - { - if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && this.FrameInfo.SegmentationParameters.IsFeatureActive(ObuSegmentationFeature.LevelSkip)) - { - this.skip = true; - } - else - { - this.skip = reader.ReadSkip(-1); - } - } - - private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) - { - return; - } - - if (this.readDeltas) - { - int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); - if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) - { - int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1; - int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits); - deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1; - } - - if (deltaQuantizerAbsolute != 0) - { - bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; - int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; - this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); - } - } - } - - private bool IsInside(int rowIndex, int columnIndex) => - columnIndex >= this.TileInfo.TileColumnCount && - columnIndex < this.TileInfo.TileColumnCount && - rowIndex >= this.TileInfo.TileRowCount && - rowIndex < this.TileInfo.TileRowCount; - - private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY) - { - int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); - int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); - bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX; - bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY; - return xPos && yPos; - } - - private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - // Maximum partition point is 8x8. Offset the log value occordingly. - int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); - int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex]; - int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex]; - int above = (aboveCtx >> blockSizeLog) & 0x1; - int left = (leftCtx >> blockSizeLog) & 0x1; - return ((left * 2) + above) + (blockSizeLog * PartitionProbabilitySet); - } + => this.tileDecoder.FinishDecodeTiles(doCdef, doLoopRestoration); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index f68c23adc9..be428efc32 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -17,5 +17,6 @@ internal class ObuSegmentationParameters public int LastActiveSegmentId { get; internal set; } - internal bool IsFeatureActive(ObuSegmentationFeature feature) => false; + internal bool IsFeatureActive(int segmentId, ObuSegmentationFeature feature) + => this.FeatureEnabled[segmentId, (int)feature]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs new file mode 100644 index 0000000000..6c8a6bbff2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs @@ -0,0 +1,862 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1TileDecoder : IAv1TileDecoder +{ + private static readonly int[] SgrprojXqdMid = [-32, 31]; + private static readonly int[] WienerTapsMid = [3, -7, 15]; + private const int PartitionProbabilitySet = 4; + + private int[] deltaLoopFilter = []; + private bool[][][] blockDecoded = []; + private int[][] referenceSgrXqd = []; + private int[][][] referenceLrWiener = []; + private bool availableUp; + private bool availableLeft; + private bool availableUpForChroma; + private bool availableLeftForChroma; + private Av1ParseAboveContext aboveContext = new(); + private Av1ParseLeftContext leftContext = new(); + private bool skip; + private bool readDeltas; + private int currentQuantizerIndex; + private Av1PredictionMode[][] yModes = []; + private Av1PredictionMode yMode = Av1PredictionMode.DC; + private Av1PredictionMode uvMode = Av1PredictionMode.DC; + private Av1PredictionMode[][] uvModes = []; + private object[] referenceFrame = []; + private object[][][] referenceFrames = []; + private int paletteSizeY; + private int paletteSizeUv; + private object[][] aboveLevelContext = []; + private object[][] aboveDcContext = []; + private object[][] leftLevelContext = []; + private object[][] leftDcContext = []; + private Av1TransformSize transformSize = Av1TransformSize.Size4x4; + private object filterUltraMode = -1; + private int angleDeltaY; + private int angleDeltaUv; + private bool lossless; + private int[][] segmentIds = []; + private int maxLumaWidth; + private int maxLumaHeight; + private int segmentId; + private int[][] cdefIndex = []; + private int deltaLoopFilterResolution = -1; + private int deltaQuantizerResolution = -1; + + public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) + { + this.FrameInfo = frameInfo; + this.SequenceHeader = sequenceHeader; + this.TileInfo = tileInfo; + } + + public bool SequenceHeaderDone { get; set; } + + public bool ShowExistingFrame { get; set; } + + public bool SeenFrameHeader { get; set; } + + public ObuFrameHeader FrameInfo { get; } + + public ObuSequenceHeader SequenceHeader { get; } + + public ObuTileInfo TileInfo { get; } + + public void DecodeTile(Span tileData, int tileNum) + { + Av1SymbolDecoder reader = new(tileData); + int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; + int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; + this.aboveContext.Clear(); + this.ClearLoopFilterDelta(); + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + this.referenceSgrXqd = new int[planesCount][]; + this.referenceLrWiener = new int[planesCount][][]; + for (int plane = 0; plane < planesCount; plane++) + { + this.referenceSgrXqd[plane] = new int[2]; + Array.Copy(SgrprojXqdMid, this.referenceSgrXqd[plane], SgrprojXqdMid.Length); + this.referenceLrWiener[plane] = new int[2][]; + for (int pass = 0; pass < 2; pass++) + { + this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount]; + Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length); + } + } + + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + int superBlock4x4Size = superBlockSize.Get4x4WideCount(); + for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) + { + int superBlockRow = row << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + this.leftContext.Clear(); + for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) + { + int superBlockColumn = column << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + + bool readDeltas = this.FrameInfo.DeltaQParameters.IsPresent; + + // Nothing to do for CDEF + // this.ClearCdef(row, column); + this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); + this.ReadLoopRestoration(row, column, superBlockSize); + this.DecodePartition(ref reader, row, column, superBlockSize); + } + } + } + + private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4]; + + private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) + { + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + for (int plane = 0; plane < planesCount; plane++) + { + int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; + int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; + int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX; + int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY; + for (int y = -1; y <= superBlock4x4Size >> subY; y++) + { + for (int x = -1; x <= superBlock4x4Size >> subX; x++) + { + if (y < 0 && x < superBlock4x4Width) + { + this.blockDecoded[plane][y][x] = true; + } + else if (x < 0 && y < superBlock4x4Height) + { + this.blockDecoded[plane][y][x] = true; + } + else + { + this.blockDecoded[plane][y][x] = false; + } + } + } + + int lastIndex = this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1].Length - 1; + this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1][lastIndex] = false; + } + } + + private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize) + { + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + for (int plane = 0; plane < planesCount; plane++) + { + if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None) + { + // TODO: Implement. + throw new NotImplementedException("No loop restoration filter support."); + } + } + } + + public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) + { + // TODO: Implement + } + + private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || + columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) + { + return; + } + + this.availableUp = this.IsInside(rowIndex - 1, columnIndex); + this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + int block4x4Size = blockSize.Get4x4WideCount(); + int halfBlock4x4Size = block4x4Size >> 1; + int quarterBlock4x4Size = halfBlock4x4Size >> 2; + bool hasRows = rowIndex + halfBlock4x4Size < this.TileInfo.TileRowCount; + bool hasColumns = columnIndex + halfBlock4x4Size < this.TileInfo.TileColumnCount; + Av1PartitionType partitionType = Av1PartitionType.Split; + if (blockSize < Av1BlockSize.Block8x8) + { + partitionType = Av1PartitionType.None; + } + else if (hasRows && hasColumns) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + partitionType = reader.ReadPartitionType(ctx); + } + else if (hasColumns) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); + partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + } + else if (hasRows) + { + int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); + partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; + } + + Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); + Av1BlockSize splitSize = Av1PartitionType.Split.GetBlockSubSize(blockSize); + switch (partitionType) + { + case Av1PartitionType.Split: + this.DecodePartition(ref reader, rowIndex, columnIndex, subSize); + this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); + this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); + this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); + break; + case Av1PartitionType.None: + this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize); + break; + default: + throw new NotImplementedException($"Partition type: {partitionType} is not supported."); + } + } + + private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int block4x4Width = blockSize.Get4x4WideCount(); + int block4x4Height = blockSize.Get4x4HighCount(); + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + bool hasChroma = planesCount > 1; + if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0) + { + hasChroma = false; + } + + if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0) + { + hasChroma = false; + } + + this.availableUp = this.IsInside(rowIndex - 1, columnIndex); + this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + this.availableUpForChroma = this.availableUp; + this.availableLeftForChroma = this.availableLeft; + if (hasChroma) + { + if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) + { + this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); + } + + if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) + { + this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); + } + } + + this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize); + this.ReadPaletteTokens(ref reader); + ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + if (this.skip) + { + this.ResetBlockContext(rowIndex, columnIndex, blockSize); + } + + // bool isCompound = false; + for (int y = 0; y < block4x4Height; y++) + { + for (int x = 0; x < block4x4Width; x++) + { + this.yModes[rowIndex + y][columnIndex + x] = this.yMode; + if (this.referenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma) + { + this.uvModes[rowIndex + y][columnIndex + x] = this.uvMode; + } + + for (int refList = 0; refList < 2; refList++) + { + this.referenceFrames[rowIndex + y][columnIndex + x][refList] = this.referenceFrame[refList]; + } + } + } + + ComputePrediction(); + this.Residual(rowIndex, columnIndex, blockSize); + } + + private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; + int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6); + int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6); + Av1BlockSize sizeChunk = widthChunks > 1 || heightChunks > 1 ? Av1BlockSize.Block64x64 : blockSize; + + for (int chunkY = 0; chunkY < heightChunks; chunkY++) + { + for (int chunkX = 0; chunkX < widthChunks; chunkX++) + { + int rowChunk = rowIndex + (chunkY << 4); + int columnChunk = columnIndex + (chunkX << 4); + int subBlockRow = rowChunk & superBlockMask; + int subBlockColumn = columnChunk & superBlockMask; + for (int plane = 0; plane < 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); plane++) + { + Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.transformSize); + int stepX = transformSize.GetWidth() >> 2; + int stepY = transformSize.GetHeight() >> 2; + Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); + int num4x4Width = planeSize.Get4x4WideCount(); + int num4x4Height = planeSize.Get4x4HighCount(); + int subX = plane > 0 && subsamplingX ? 1 : 0; + int subY = plane > 0 && subsamplingY ? 1 : 0; + int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); + for (int y = 0; y < num4x4Height; y += stepY) + { + for (int x = 0; x < num4x4Width; x += stepX) + { + this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + (chunkX << 4 >> subX), y + (chunkY << 4 >> subY)); + } + } + } + } + } + } + + private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int bw = blockSize.Get4x4WideCount(); + int bh = blockSize.Get4x4HighCount(); + bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + bool hasChroma = ((rowIndex & 0x01) != 0 || (bh & 0x01) == 0 || !subY) && + ((columnIndex & 0x01) != 0 || (bw & 0x01) == 0 || !subX); + return hasChroma; + } + + private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); + + private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException(); + + private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) + { + int startX = (baseX + 4) * x; + int startY = (baseY + 4) * y; + bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + int subX = plane > 0 && subsamplingX ? 1 : 0; + int subY = plane > 0 && subsamplingY ? 1 : 0; + int columnIndex = startX << subX >> ObuConstants.ModeInfoSizeLog2; + int rowIndex = startY << subY >> ObuConstants.ModeInfoSizeLog2; + int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; + int subBlockColumn = columnIndex & superBlockMask; + int subBlockRow = rowIndex & superBlockMask; + int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2; + int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2; + int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX; + int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY; + if (startX >= maxX || startY >= maxY) + { + return; + } + + if ((plane == 0 && this.paletteSizeY > 0) || + (plane != 0 && this.paletteSizeUv > 0)) + { + this.PredictPalette(plane, startX, startY, x, y, transformSize); + } + else + { + bool isChromaFromLuma = plane > 0 && this.uvMode == Av1PredictionMode.UvChromaFromLuma; + Av1PredictionMode mode; + if (plane == 0) + { + mode = this.yMode; + } + else + { + mode = isChromaFromLuma ? Av1PredictionMode.DC : this.uvMode; + } + + int log2Width = transformSize.GetWidthLog2(); + int log2Height = transformSize.GetHeightLog2(); + bool leftAvailable = x > 0 || plane == 0 ? this.availableLeft : this.availableLeftForChroma; + bool upAvailable = y > 0 || plane == 0 ? this.availableUp : this.availableUpForChroma; + bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; + bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; + this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); + if (isChromaFromLuma) + { + this.PredictChromaFromLuma(plane, startX, startY, transformSize); + } + } + + if (plane == 0) + { + this.maxLumaWidth = startX + (stepX * 4); + this.maxLumaHeight = startY + (stepY * 4); + } + + if (!this.skip) + { + int eob = this.Coefficients(plane, startX, startY, transformSize); + if (eob > 0) + { + this.Reconstruct(plane, startX, startY, transformSize); + } + } + + for (int i = 0; i < stepY; i++) + { + for (int j = 0; j < stepX; j++) + { + // Ignore loop filter. + this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true; + } + } + } + + private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException(); + + private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); + + private static void ComputePrediction() + { + // Not applicable for INTRA frames. + } + + private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int block4x4Width = blockSize.Get4x4WideCount(); + int block4x4Height = blockSize.Get4x4HighCount(); + bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1; + for (int plane = 0; plane < endPlane; plane++) + { + int subX = plane > 0 && subsamplingX ? 1 : 0; + int subY = plane > 0 && subsamplingY ? 1 : 0; + for (int i = columnIndex >> subX; i < (columnIndex + block4x4Width) >> subX; i++) + { + this.aboveLevelContext[plane][i] = 0; + this.aboveDcContext[plane][i] = 0; + } + + for (int i = rowIndex >> subY; i < (rowIndex + block4x4Height) >> subY; i++) + { + this.leftLevelContext[plane][i] = 0; + this.leftDcContext[plane][i] = 0; + } + } + } + + private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + int block4x4Width = blockSize.Get4x4WideCount(); + int block4x4Height = blockSize.Get4x4HighCount(); + + // First condition in spec is for INTER frames, implemented only the INTRA condition. + ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++) + { + for (int column = columnIndex; column < columnIndex + block4x4Width; column++) + { + this.InterTransformSizes[row][column] = this.TransformSize; + } + }*/ + } + + private void ReadPaletteTokens(ref Av1SymbolDecoder reader) + { + reader.ReadLiteral(-1); + if (this.paletteSizeY != 0) + { + // Todo: Implement. + throw new NotImplementedException(); + } + + if (this.paletteSizeUv != 0) + { + // Todo: Implement. + throw new NotImplementedException(); + } + } + + private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize); + + private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + this.skip = false; + if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + { + this.ReadIntraSegmentId(ref reader); + } + + // this.skipMode = false; + this.ReadSkip(ref reader); + if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + { + this.IntraSegmentId(ref reader, rowIndex, columnIndex); + } + + this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize); + this.ReadDeltaQuantizerIndex(ref reader, blockSize); + this.ReadDeltaLoopFilter(ref reader, blockSize); + this.readDeltas = false; + this.referenceFrame[0] = -1; // IntraFrame; + this.referenceFrame[1] = -1; // None; + bool useIntraBlockCopy = false; + if (this.FrameInfo.AllowIntraBlockCopy) + { + useIntraBlockCopy = reader.ReadUseIntraBlockCopy(); + } + + if (useIntraBlockCopy) + { + // TODO: Implement + } + else + { + // this.IsInter = false; + this.yMode = reader.ReadIntraFrameYMode(blockSize); + this.IntraAngleInfoY(ref reader, blockSize); + if (this.HasChroma(rowIndex, columnIndex, blockSize)) + { + this.uvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed(blockSize)); + if (this.uvMode == Av1PredictionMode.UvChromaFromLuma) + { + this.ReadChromaFromLumaAlphas(ref reader); + } + + this.IntraAngleInfoUv(ref reader, blockSize); + } + + this.paletteSizeY = 0; + this.paletteSizeUv = 0; + if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 && + ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 && + ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 && + this.FrameInfo.AllowScreenContentTools) + { + this.PaletteModeInfo(ref reader); + } + + this.FilterIntraModeInfo(ref reader, blockSize); + } + } + + private bool IsChromaForLumaAllowed(Av1BlockSize blockSize) + { + if (this.lossless) + { + // In lossless, CfL is available when the partition size is equal to the + // transform size. + bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + return planeBlockSize == Av1BlockSize.Block4x4; + } + + // Spec: CfL is available to luma partitions lesser than or equal to 32x32 + return blockSize.Get4x4WideCount() <= 32 && blockSize.Get4x4HighCount() <= 32; + } + + private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException(); + + private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + bool useFilterIntra = false; + if (this.SequenceHeader.EnableFilterIntra && + this.yMode == Av1PredictionMode.DC && this.paletteSizeY == 0 && + Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32) + { + useFilterIntra = reader.ReadUseFilterUltra(); + if (useFilterIntra) + { + this.filterUltraMode = reader.ReadFilterUltraMode(); + } + } + } + + private void PaletteModeInfo(ref Av1SymbolDecoder reader) => + + // TODO: Implement. + throw new NotImplementedException(); + + private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) => + + // TODO: Implement. + throw new NotImplementedException(); + + private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + this.angleDeltaY = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.yMode)) + { + int angleDeltaY = reader.ReadAngleDelta(this.yMode); + this.angleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta; + } + } + + private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + this.angleDeltaUv = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.uvMode)) + { + int angleDeltaUv = reader.ReadAngleDelta(this.uvMode); + this.angleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta; + } + } + + private static bool IsDirectionalMode(Av1PredictionMode mode) + => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; + + private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + { + if (this.FrameInfo.SegmentationParameters.Enabled) + { + this.ReadSegmentId(ref reader, rowIndex, columnIndex); + } + else + { + this.segmentId = 0; + } + + this.lossless = this.FrameInfo.LosslessArray[this.segmentId]; + } + + private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + { + int pred; + int prevUL = -1; + int prevU = -1; + int prevL = -1; + if (this.availableUp && this.availableLeft) + { + prevUL = this.segmentIds[rowIndex - 1][columnIndex - 1]; + } + + if (this.availableUp) + { + prevU = this.segmentIds[rowIndex - 1][columnIndex]; + } + + if (this.availableLeft) + { + prevU = this.segmentIds[rowIndex][columnIndex - 1]; + } + + if (prevU == -1) + { + pred = prevL == -1 ? 0 : prevL; + } + else if (prevL == -1) + { + pred = prevU; + } + else + { + pred = prevU == prevUL ? prevU : prevL; + } + + if (this.skip) + { + this.segmentId = 0; + } + else + { + int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; + this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1); + } + } + + private static int NegativeDeinterleave(int diff, int reference, int max) + { + if (reference == 0) + { + return diff; + } + + if (reference >= max - 1) + { + return max - diff - 1; + } + + if (2 * reference < max) + { + if (diff <= 2 * reference) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return diff; + } + else + { + if (diff <= 2 * (max - reference - 1)) + { + if ((diff & 1) > 0) + { + return reference + ((diff + 1) >> 1); + } + else + { + return reference - (diff >> 1); + } + } + + return max - (diff + 1); + } + } + + private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) + { + return; + } + + int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount(); + int cdefMask4 = ~(cdefSize4 - 1); + int r = rowIndex & cdefMask4; + int c = columnIndex & cdefMask4; + if (this.cdefIndex[r][c] == -1) + { + this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); + int w4 = blockSize.Get4x4WideCount(); + int h4 = blockSize.Get4x4HighCount(); + for (int i = r; i < r + h4; i += cdefSize4) + { + for (int j = c; j < c + w4; j += cdefSize4) + { + this.cdefIndex[i][j] = this.cdefIndex[r][c]; + } + } + } + } + + private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + if (blockSize == superBlockSize && this.skip) + { + return; + } + + if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent) + { + int frameLoopFilterCount = 1; + if (this.FrameInfo.DeltaLoopFilterParameters.Multi) + { + frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2; + } + + for (int i = 0; i < frameLoopFilterCount; i++) + { + int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); + if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall) + { + int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1; + int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits); + deltaLoopFilterAbsolute = deltaLoopFilterAbsoluteBitCount + (1 << deltaLoopFilterRemainingBits) + 1; + } + + if (deltaLoopFilterAbsolute != 0) + { + bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; + int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; + this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); + } + } + } + } + + private void ReadSkip(ref Av1SymbolDecoder reader) + { + if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && + this.FrameInfo.SegmentationParameters.IsFeatureActive(-1, ObuSegmentationFeature.LevelSkip)) + { + this.skip = true; + } + else + { + this.skip = reader.ReadSkip(-1); + } + } + + private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + { + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + if (blockSize == superBlockSize && this.skip) + { + return; + } + + if (this.readDeltas) + { + int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); + if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) + { + int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1; + int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits); + deltaQuantizerAbsolute = deltaQuantizerRemainingBits + (1 << deltaQuantizerRemainingBits) + 1; + } + + if (deltaQuantizerAbsolute != 0) + { + bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; + int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; + this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); + } + } + } + + private bool IsInside(int rowIndex, int columnIndex) => + columnIndex >= this.TileInfo.TileColumnCount && + columnIndex < this.TileInfo.TileColumnCount && + rowIndex >= this.TileInfo.TileRowCount && + rowIndex < this.TileInfo.TileRowCount; + + /* + private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY) + { + int block4x4Width = blockMode.BlockSize.Get4x4WideCount(); + int block4x4Height = blockMode.BlockSize.Get4x4HighCount(); + bool xPos = (columnIndex & 0x1) > 0 || (block4x4Width & 0x1) > 0 || !subSamplingX; + bool yPos = (rowIndex & 0x1) > 0 || (block4x4Height & 0x1) > 0 || !subSamplingY; + return xPos && yPos; + }*/ + + private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) + { + // Maximum partition point is 8x8. Offset the log value occordingly. + int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); + int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex]; + int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex]; + int above = (aboveCtx >> blockSizeLog) & 0x1; + int left = (leftCtx >> blockSizeLog) & 0x1; + return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 69f1bf63e0..89b42535f4 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -5,6 +5,7 @@ using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; From 881199e1e82ff44e0d397d006452c6f8f011e4cf Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 2 Jun 2024 16:46:17 +0200 Subject: [PATCH 063/190] Introduce Av1PartitionInfo and fill it during decoding --- .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuConstants.cs | 5 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 22 +- .../ObuSegmentationFeature.cs | 10 - .../ObuSegmentationParameters.cs | 2 +- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 4 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 8 +- .../Heif/Av1/Symbol/Av1BlockModeInfo.cs | 49 ++ .../Av1/Symbol/Av1DefaultDistributions.cs | 45 ++ .../Heif/Av1/Symbol/Av1FilterIntraMode.cs | 13 + .../Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs | 11 + .../Heif/Av1/Symbol/Av1ParseAboveContext.cs | 10 +- .../Heif/Av1/Symbol/Av1ParseLeftContext.cs | 4 +- .../Heif/Av1/Symbol/Av1PartitionInfo.cs | 45 ++ .../Formats/Heif/Av1/Symbol/Av1PlaneType.cs | 10 + .../Heif/Av1/Symbol/Av1SuperblockInfo.cs | 11 + .../Heif/Av1/Symbol/Av1SymbolDecoder.cs | 40 +- .../Formats/Heif/Av1/Symbol/Av1TileDecoder.cs | 439 ++++++++++-------- 18 files changed, 488 insertions(+), 242 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index c70c189a55..7ad04f6bba 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -9,7 +9,7 @@ internal class ObuColorConfig public int ChannelCount { get; set; } - public bool Monochrome { get; set; } + public bool IsMonochrome { get; set; } public ObuColorPrimaries ColorPrimaries { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs index 2742264498..d3d2194a2e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs @@ -113,4 +113,9 @@ internal static class ObuConstants /// Maximum magnitude of AngleDeltaY and AngleDeltaUV. /// public const int MaxAngleDelta = 3; + + /// + /// Number of segments allowed in segmentation map. + /// + public const int MaxSegments = 8; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 756f71f1f9..fcae54d647 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -263,8 +263,8 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.EnableDualFilter = false; sequenceHeader.OrderHintInfo.EnableJointCompound = false; sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; - sequenceHeader.SequenceForceScreenContentTools = 2; - sequenceHeader.SequenceForceIntegerMotionVector = 2; + sequenceHeader.ForceScreenContentTools = 2; + sequenceHeader.ForceIntegerMotionVector = 2; sequenceHeader.OrderHintInfo.OrderHintBits = 0; // Video related flags removed @@ -280,13 +280,13 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu { ObuColorConfig colorConfig = new(); ReadBitDepth(ref reader, colorConfig, sequenceHeader); - colorConfig.Monochrome = false; + colorConfig.IsMonochrome = false; if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) { - colorConfig.Monochrome = reader.ReadBoolean(); + colorConfig.IsMonochrome = reader.ReadBoolean(); } - colorConfig.ChannelCount = colorConfig.Monochrome ? 1 : 3; + colorConfig.ChannelCount = colorConfig.IsMonochrome ? 1 : 3; colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; @@ -303,7 +303,7 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu colorConfig.SubSamplingY = false; colorConfig.ChromaSamplePosition = ObuChromoSamplePosition.Unknown; colorConfig.HasSeparateUvDelta = false; - if (colorConfig.Monochrome) + if (colorConfig.IsMonochrome) { colorConfig.ColorRange = reader.ReadBoolean(); colorConfig.SubSamplingX = true; @@ -669,7 +669,7 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I } frameInfo.DisableCdfUpdate = reader.ReadBoolean(); - frameInfo.AllowScreenContentTools = sequenceHeader.SequenceForceScreenContentTools == 1; + frameInfo.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 1; if (frameInfo.AllowScreenContentTools) { frameInfo.AllowScreenContentTools = reader.ReadBoolean(); @@ -677,13 +677,13 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I if (frameInfo.AllowScreenContentTools) { - if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + if (sequenceHeader.ForceIntegerMotionVector == 1) { frameInfo.ForceIntegerMotionVector = reader.ReadBoolean(); } else { - frameInfo.ForceIntegerMotionVector = sequenceHeader.SequenceForceIntegerMotionVector != 0; + frameInfo.ForceIntegerMotionVector = sequenceHeader.ForceIntegerMotionVector != 0; } } else @@ -899,7 +899,7 @@ private static void SetupPastIndependence(ObuFrameHeader frameInfo) } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) - => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; + => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature); private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) { @@ -919,7 +919,7 @@ private static void ReadFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecod { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = reader.BitPosition; ReadUncompressedFrameHeader(ref reader, decoder, header, planeCount); if (trailingBit) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs deleted file mode 100644 index 1cfade4063..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationFeature.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - -internal enum ObuSegmentationFeature -{ - None = 0, - LevelSkip, -} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index be428efc32..c74a698b2f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -17,6 +17,6 @@ internal class ObuSegmentationParameters public int LastActiveSegmentId { get; internal set; } - internal bool IsFeatureActive(int segmentId, ObuSegmentationFeature feature) + internal bool IsFeatureActive(int segmentId, ObuSegmentationLevelFeature feature) => this.FeatureEnabled[segmentId, (int)feature]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 8bb6a11215..0b970091af 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -55,9 +55,9 @@ internal class ObuSequenceHeader public bool EnableDualFilter { get; set; } - public int SequenceForceIntegerMotionVector { get; set; } + public int ForceIntegerMotionVector { get; set; } - public int SequenceForceScreenContentTools { get; set; } + public int ForceScreenContentTools { get; set; } public bool EnableSuperResolution { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 80b0675850..45a05653d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -109,11 +109,11 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH WriteBitDepth(ref writer, colorConfig, sequenceHeader); if (sequenceHeader.SequenceProfile != ObuSequenceProfile.High) { - writer.WriteBoolean(colorConfig.Monochrome); + writer.WriteBoolean(colorConfig.IsMonochrome); } writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent - if (colorConfig.Monochrome) + if (colorConfig.IsMonochrome) { writer.WriteBoolean(colorConfig.ColorRange); return; @@ -321,7 +321,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, if (frameInfo.AllowScreenContentTools) { - if (sequenceHeader.SequenceForceIntegerMotionVector == 1) + if (sequenceHeader.ForceIntegerMotionVector == 1) { writer.WriteBoolean(frameInfo.ForceIntegerMotionVector); } @@ -448,7 +448,7 @@ private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecod { ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.Monochrome ? 1 : 3; + int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = writer.BitPosition; WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); if (writeTrailingBits) diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs new file mode 100644 index 0000000000..27ba271ab3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs @@ -0,0 +1,49 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1BlockModeInfo +{ + private int[] paletteSize; + + public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize) + { + this.BlockSize = blockSize; + this.AngleDelta = new int[numPlanes]; + this.paletteSize = new int[numPlanes - 1]; + this.FilterIntraModeInfo = new(); + } + + public Av1BlockSize BlockSize { get; } + + public Av1PredictionMode YMode { get; set; } + + public bool Skip { get; set; } + + public Av1PartitionType PartitionType { get; } + + public bool SkipMode { get; set; } + + public int SegmentId { get; set; } + + public Av1PredictionMode UvMode { get; set; } + + public bool UseUltraBlockCopy { get; set; } + + public int ChromaFromLumaAlphaIndex { get; set; } + + public int ChromaFromLumaAlphaSign { get; set; } + + public int[] AngleDelta { get; set; } + + public Size IndexInSuperblock { get; set; } + + public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } + + public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; + + public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs index 3b9f555ef5..cb65bfafe4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs @@ -126,4 +126,49 @@ internal static class Av1DefaultDistributions public static Av1Distribution DeltaQuantizerAbsolute => new(28160, 32120, 32677); public static Av1Distribution[] SegmentId => [new(128 * 128), new(128 * 128), new(128 * 128)]; + + public static Av1Distribution[][] KeyFrameYMode => + [ + [ + new(15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466), + new(12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409), + new(10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567), + new(14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462), + new(12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475), + ], [ + new(10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436), + new(5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794), + new(7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492), + new(8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210), + new(7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994) + ], [ + new(12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077), + new(9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907), + new(6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086), + new(10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252), + new(9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463) + ], [ + new(12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409), + new(8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342), + new(8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018), + new(7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528), + new(8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485) + ], [ + new(12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477), + new(9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277), + new(8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261), + new(10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482), + new(7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593) + ] + ]; + + public static Av1Distribution FilterIntraMode => new(8949, 12776, 17211, 29558); + + public static Av1Distribution[] FilterIntra => + [ + new(4621), new(6743), new(5893), new(7866), new(12551), new(9394), + new(12408), new(14301), new(12756), new(22343), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(12770), new(10368), + new(20229), new(18101), new(16384), new(16384) + ]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs new file mode 100644 index 0000000000..b93b8522df --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal enum Av1FilterIntraMode +{ + DC, + Vertical, + Horizontal, + Directional157, + Paeth +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs new file mode 100644 index 0000000000..6adb43dabd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1IntraFilterModeInfo +{ + public bool UseFilterIntra { get; set; } + + public Av1FilterIntraMode Mode { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs index f783c9cdda..e213005dff 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs @@ -5,7 +5,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseAboveContext { - public int PartitionWidth { get; internal set; } + public int PartitionWidth { get; set; } - internal void Clear() => throw new NotImplementedException(); + public int[][] AboveContext { get; set; } = []; + + internal void Clear(int startColumnIndex, int endColumnIndex) + { + this.PartitionWidth = -1; + this.AboveContext = []; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs index 7f521c273e..01219dcfcb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs @@ -5,7 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseLeftContext { - public int PartitionHeight { get; internal set; } + public int PartitionHeight { get; set; } + + public int[][] LeftContext { get; set; } = []; internal void Clear() => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs new file mode 100644 index 0000000000..d6dc9ffa65 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1PartitionInfo +{ + public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) + { + this.ModeInfo = modeInfo; + this.SuperblockInfo = superblockInfo; + this.IsChroma = isChroma; + this.PartitionType = partitionType; + this.CdefStrength = []; + this.ReferenceFrame = [-1, -1]; + } + + public Av1BlockModeInfo ModeInfo { get; } + + public Av1SuperblockInfo SuperblockInfo { get; } + + public bool IsChroma { get; } + + public Av1PartitionType PartitionType { get; } + + public bool AvailableUp { get; set; } + + public bool AvailableLeft { get; set; } + + public bool AvailableUpForChroma { get; set; } + + public bool AvailableLeftForChroma { get; set; } + + public int ColumnIndex { get; set; } + + public int RowIndex { get; set; } + + public Av1BlockModeInfo? AboveModeInfo { get; set; } + + public Av1BlockModeInfo? LeftModeInfo { get; set; } + + public int[][] CdefStrength { get; set; } + + public int[] ReferenceFrame { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs new file mode 100644 index 0000000000..d41e4e6e10 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal enum Av1PlaneType : int +{ + Y, + Uv +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs new file mode 100644 index 0000000000..fccc6f6079 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1SuperblockInfo +{ + public int[] SuperblockDeltaQ { get; internal set; } = []; + + public Av1BlockModeInfo GetModeInfo(int rowIndex, int columnIndex) => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs index faec9c8214..3d76994391 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs @@ -7,15 +7,19 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolDecoder { + private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - private readonly Av1Distribution[] frameYMode = Av1DefaultDistributions.FrameYMode; + private readonly Av1Distribution[][] keyFrameYMode = Av1DefaultDistributions.KeyFrameYMode; private readonly Av1Distribution[][] uvMode = Av1DefaultDistributions.UvMode; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; private readonly Av1Distribution deltaLoopFilterAbsolute = Av1DefaultDistributions.DeltaLoopFilterAbsolute; private readonly Av1Distribution deltaQuantizerAbsolute = Av1DefaultDistributions.DeltaQuantizerAbsolute; private readonly Av1Distribution[] segmentId = Av1DefaultDistributions.SegmentId; private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; + private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; + private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); @@ -76,17 +80,31 @@ public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context) return r.ReadSymbol(distribution) > 0; } - public Av1PredictionMode ReadIntraFrameYMode(Av1BlockSize blockSize) + public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) { ref Av1SymbolReader r = ref this.reader; - return (Av1PredictionMode)r.ReadSymbol(this.frameYMode[(int)blockSize]); + Av1PredictionMode aboveMode = Av1PredictionMode.DC; + if (aboveModeInfo != null) + { + aboveMode = aboveModeInfo.YMode; + } + + Av1PredictionMode leftMode = Av1PredictionMode.DC; + if (leftModeInfo != null) + { + leftMode = leftModeInfo.YMode; + } + + int aboveContext = IntraModeContext[(int)aboveMode]; + int leftContext = IntraModeContext[(int)leftMode]; + return (Av1PredictionMode)r.ReadSymbol(this.keyFrameYMode[aboveContext][leftContext]); } - public Av1PredictionMode ReadUvMode(Av1BlockSize blockSize, bool chromaFromLumaAllowed) + public Av1PredictionMode ReadIntraModeUv(Av1PredictionMode mode, bool chromaFromLumaAllowed) { int chromaForLumaIndex = chromaFromLumaAllowed ? 1 : 0; ref Av1SymbolReader r = ref this.reader; - return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)blockSize]); + return (Av1PredictionMode)r.ReadSymbol(this.uvMode[chromaForLumaIndex][(int)mode]); } public bool ReadSkip(int ctx) @@ -119,9 +137,17 @@ public int ReadAngleDelta(Av1PredictionMode mode) return r.ReadSymbol(this.angleDelta[((int)mode) - 1]); } - public bool ReadUseFilterUltra() => throw new NotImplementedException(); + public bool ReadUseFilterUltra(Av1BlockSize blockSize) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.filterIntra[(int)blockSize]) > 0; + } - public object ReadFilterUltraMode() => throw new NotImplementedException(); + public Av1FilterIntraMode ReadFilterUltraMode() + { + ref Av1SymbolReader r = ref this.reader; + return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); + } private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs index 6c8a6bbff2..00812e521b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs @@ -18,37 +18,16 @@ internal class Av1TileDecoder : IAv1TileDecoder private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; - private bool availableUp; - private bool availableLeft; - private bool availableUpForChroma; - private bool availableLeftForChroma; private Av1ParseAboveContext aboveContext = new(); private Av1ParseLeftContext leftContext = new(); - private bool skip; - private bool readDeltas; private int currentQuantizerIndex; - private Av1PredictionMode[][] yModes = []; - private Av1PredictionMode yMode = Av1PredictionMode.DC; - private Av1PredictionMode uvMode = Av1PredictionMode.DC; - private Av1PredictionMode[][] uvModes = []; - private object[] referenceFrame = []; - private object[][][] referenceFrames = []; - private int paletteSizeY; - private int paletteSizeUv; - private object[][] aboveLevelContext = []; - private object[][] aboveDcContext = []; - private object[][] leftLevelContext = []; - private object[][] leftDcContext = []; - private Av1TransformSize transformSize = Av1TransformSize.Size4x4; - private object filterUltraMode = -1; - private int angleDeltaY; - private int angleDeltaUv; - private bool lossless; + private int[][] aboveLevelContext = []; + private int[][] aboveDcContext = []; + private int[][] leftLevelContext = []; + private int[][] leftDcContext = []; private int[][] segmentIds = []; private int maxLumaWidth; private int maxLumaHeight; - private int segmentId; - private int[][] cdefIndex = []; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; @@ -74,9 +53,10 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo public void DecodeTile(Span tileData, int tileNum) { Av1SymbolDecoder reader = new(tileData); - int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; - this.aboveContext.Clear(); + int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; + + this.aboveContext.Clear(this.TileInfo.TileColumnStartModeInfo[tileColumnIndex], this.TileInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; this.referenceSgrXqd = new int[planesCount][]; @@ -111,12 +91,13 @@ public void DecodeTile(Span tileData, int tileNum) // this.ClearCdef(row, column); this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); this.ReadLoopRestoration(row, column, superBlockSize); - this.DecodePartition(ref reader, row, column, superBlockSize); + this.ParsePartition(ref reader, row, column, superBlockSize); } } } - private void ClearLoopFilterDelta() => this.deltaLoopFilter = new int[4]; + private void ClearLoopFilterDelta() + => this.deltaLoopFilter = new int[4]; private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { @@ -169,16 +150,17 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } - private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) { + Av1SuperblockInfo superblockInfo = new(); if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) { return; } - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); + bool availableUp = this.IsInside(rowIndex - 1, columnIndex); + bool availableLeft = this.IsInside(rowIndex, columnIndex - 1); int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 2; @@ -212,82 +194,86 @@ private void DecodePartition(ref Av1SymbolDecoder reader, int rowIndex, int colu switch (partitionType) { case Av1PartitionType.Split: - this.DecodePartition(ref reader, rowIndex, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); - this.DecodePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); + this.ParsePartition(ref reader, rowIndex, columnIndex, subSize); + this.ParsePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); break; case Av1PartitionType.None: - this.DecodeBlock(ref reader, rowIndex, columnIndex, subSize); + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.None); break; default: throw new NotImplementedException($"Partition type: {partitionType} is not supported."); } } - private void DecodeBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ParseBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) { int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - bool hasChroma = planesCount > 1; - if (block4x4Height == 1 && this.SequenceHeader.ColorConfig.SubSamplingY && (rowIndex & 0x1) == 0) - { - hasChroma = false; - } - - if (block4x4Width == 1 && this.SequenceHeader.ColorConfig.SubSamplingX && (columnIndex & 0x1) == 0) - { - hasChroma = false; - } - - this.availableUp = this.IsInside(rowIndex - 1, columnIndex); - this.availableLeft = this.IsInside(rowIndex, columnIndex - 1); - this.availableUpForChroma = this.availableUp; - this.availableLeftForChroma = this.availableLeft; + Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize); + bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); + Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); + partitionInfo.ColumnIndex = columnIndex; + partitionInfo.RowIndex = rowIndex; + partitionInfo.AvailableUp = this.IsInside(rowIndex - 1, columnIndex); + partitionInfo.AvailableLeft = this.IsInside(rowIndex, columnIndex - 1); + partitionInfo.AvailableUpForChroma = partitionInfo.AvailableUp; + partitionInfo.AvailableLeftForChroma = partitionInfo.AvailableLeft; if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) { - this.availableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); + partitionInfo.AvailableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); } if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) { - this.availableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); + partitionInfo.AvailableLeftForChroma = this.IsInside(rowIndex, columnIndex - 2); } } - this.ReadModeInfo(ref reader, rowIndex, columnIndex, blockSize); - this.ReadPaletteTokens(ref reader); - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - if (this.skip) + if (partitionInfo.AvailableUp) { - this.ResetBlockContext(rowIndex, columnIndex, blockSize); + partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(rowIndex - 1, columnIndex); } - // bool isCompound = false; - for (int y = 0; y < block4x4Height; y++) + if (partitionInfo.AvailableLeft) { - for (int x = 0; x < block4x4Width; x++) - { - this.yModes[rowIndex + y][columnIndex + x] = this.yMode; - if (this.referenceFrame[0] == (object)ObuFrameType.IntraOnlyFrame && hasChroma) - { - this.uvModes[rowIndex + y][columnIndex + x] = this.uvMode; - } + partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(rowIndex, columnIndex - 1); + } - for (int refList = 0; refList < 2; refList++) - { - this.referenceFrames[rowIndex + y][columnIndex + x][refList] = this.referenceFrame[refList]; - } - } + this.ReadModeInfo(ref reader, partitionInfo); + ReadPaletteTokens(ref reader, partitionInfo); + ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + if (partitionInfo.ModeInfo.Skip) + { + this.ResetSkipContext(partitionInfo); } - ComputePrediction(); this.Residual(rowIndex, columnIndex, blockSize); } + private void ResetSkipContext(Av1PartitionInfo partitionInfo) + { + int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + for (int i = 0; i < planesCount; i++) + { + bool subX = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingY; + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int txsWide = planeBlockSize.GetWidth() >> 2; + int txsHigh = planeBlockSize.GetHeight() >> 2; + int aboveOffset = (partitionInfo.ColumnIndex - this.TileInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); + int leftOffset = (partitionInfo.RowIndex - this.TileInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); + int[] aboveContext = this.aboveContext.AboveContext[i + aboveOffset]; + int[] leftContext = this.leftContext.LeftContext[i + leftOffset]; + Array.Fill(aboveContext, 0); + Array.Fill(leftContext, 0); + } + } + private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) { bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -307,7 +293,7 @@ private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) int subBlockColumn = columnChunk & superBlockMask; for (int plane = 0; plane < 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); plane++) { - Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, this.transformSize); + Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, -1); int stepX = transformSize.GetWidth() >> 2; int stepY = transformSize.GetHeight() >> 2; Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); @@ -348,6 +334,7 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -368,28 +355,28 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr return; } - if ((plane == 0 && this.paletteSizeY > 0) || - (plane != 0 && this.paletteSizeUv > 0)) + if ((plane == 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) > 0) || + (plane != 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) > 0)) { this.PredictPalette(plane, startX, startY, x, y, transformSize); } else { - bool isChromaFromLuma = plane > 0 && this.uvMode == Av1PredictionMode.UvChromaFromLuma; + bool isChromaFromLuma = plane > 0 && partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma; Av1PredictionMode mode; if (plane == 0) { - mode = this.yMode; + mode = partitionInfo.ModeInfo.YMode; } else { - mode = isChromaFromLuma ? Av1PredictionMode.DC : this.uvMode; + mode = isChromaFromLuma ? Av1PredictionMode.DC : partitionInfo.ModeInfo.UvMode; } int log2Width = transformSize.GetWidthLog2(); int log2Height = transformSize.GetHeightLog2(); - bool leftAvailable = x > 0 || plane == 0 ? this.availableLeft : this.availableLeftForChroma; - bool upAvailable = y > 0 || plane == 0 ? this.availableUp : this.availableUpForChroma; + bool leftAvailable = x > 0 || plane == 0 ? partitionInfo.AvailableLeft : partitionInfo.AvailableLeftForChroma; + bool upAvailable = y > 0 || plane == 0 ? partitionInfo.AvailableUp : partitionInfo.AvailableUpForChroma; bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); @@ -405,7 +392,7 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr this.maxLumaHeight = startY + (stepY * 4); } - if (!this.skip) + if (!partitionInfo.ModeInfo.Skip) { int eob = this.Coefficients(plane, startX, startY, transformSize); if (eob > 0) @@ -434,11 +421,6 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); - private static void ComputePrediction() - { - // Not applicable for INTRA frames. - } - private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) { int block4x4Width = blockSize.Get4x4WideCount(); @@ -446,21 +428,22 @@ private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize block bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1; + this.aboveLevelContext = new int[3][]; + this.aboveDcContext = new int[3][]; + this.leftLevelContext = new int[3][]; + this.leftDcContext = new int[3][]; for (int plane = 0; plane < endPlane; plane++) { int subX = plane > 0 && subsamplingX ? 1 : 0; int subY = plane > 0 && subsamplingY ? 1 : 0; - for (int i = columnIndex >> subX; i < (columnIndex + block4x4Width) >> subX; i++) - { - this.aboveLevelContext[plane][i] = 0; - this.aboveDcContext[plane][i] = 0; - } - - for (int i = rowIndex >> subY; i < (rowIndex + block4x4Height) >> subY; i++) - { - this.leftLevelContext[plane][i] = 0; - this.leftDcContext[plane][i] = 0; - } + this.aboveLevelContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; + this.aboveDcContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; + this.leftLevelContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; + this.leftDcContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; + Array.Fill(this.aboveLevelContext[plane], 0); + Array.Fill(this.aboveDcContext[plane], 0); + Array.Fill(this.leftLevelContext[plane], 0); + Array.Fill(this.leftDcContext[plane], 0); } } @@ -480,209 +463,250 @@ private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowI }*/ } - private void ReadPaletteTokens(ref Av1SymbolDecoder reader) + private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { reader.ReadLiteral(-1); - if (this.paletteSizeY != 0) + if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0) { // Todo: Implement. throw new NotImplementedException(); } - if (this.paletteSizeUv != 0) + if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) { // Todo: Implement. throw new NotImplementedException(); } } - private void ReadModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) - => this.ReadIntraFrameModeInfo(ref reader, rowIndex, columnIndex, blockSize); + private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) + { + DebugGuard.IsTrue(this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); + this.ReadIntraFrameModeInfo(ref reader, partitionInfo); + } - private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - this.skip = false; if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) { - this.ReadIntraSegmentId(ref reader); + this.IntraSegmentId(ref reader, partitionInfo); } // this.skipMode = false; - this.ReadSkip(ref reader); + partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo); if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) { - this.IntraSegmentId(ref reader, rowIndex, columnIndex); + this.IntraSegmentId(ref reader, partitionInfo); } - this.ReadCdef(ref reader, rowIndex, columnIndex, blockSize); - this.ReadDeltaQuantizerIndex(ref reader, blockSize); - this.ReadDeltaLoopFilter(ref reader, blockSize); - this.readDeltas = false; - this.referenceFrame[0] = -1; // IntraFrame; - this.referenceFrame[1] = -1; // None; + this.ReadCdef(ref reader, partitionInfo); + + bool readDeltas = false; + if (readDeltas) + { + this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); + this.ReadDeltaLoopFilter(ref reader, partitionInfo); + } + + partitionInfo.ReferenceFrame[0] = 0; // IntraFrame; + partitionInfo.ReferenceFrame[1] = -1; // None; + partitionInfo.ModeInfo.SetPaletteSizes(0, 0); bool useIntraBlockCopy = false; - if (this.FrameInfo.AllowIntraBlockCopy) + if (this.AllowIntraBlockCopy()) { useIntraBlockCopy = reader.ReadUseIntraBlockCopy(); } if (useIntraBlockCopy) { - // TODO: Implement + partitionInfo.ModeInfo.YMode = Av1PredictionMode.DC; + partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC; } else { // this.IsInter = false; - this.yMode = reader.ReadIntraFrameYMode(blockSize); - this.IntraAngleInfoY(ref reader, blockSize); - if (this.HasChroma(rowIndex, columnIndex, blockSize)) + partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo); + partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize); + if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome) { - this.uvMode = reader.ReadUvMode(blockSize, this.IsChromaForLumaAllowed(blockSize)); - if (this.uvMode == Av1PredictionMode.UvChromaFromLuma) + partitionInfo.ModeInfo.UvMode = reader.ReadIntraModeUv(partitionInfo.ModeInfo.YMode, this.IsChromaForLumaAllowed(partitionInfo)); + if (partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma) { - this.ReadChromaFromLumaAlphas(ref reader); + this.ReadChromaFromLumaAlphas(ref reader, partitionInfo); } - this.IntraAngleInfoUv(ref reader, blockSize); + partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize); + } + else + { + partitionInfo.ModeInfo.UvMode = Av1PredictionMode.DC; } - this.paletteSizeY = 0; - this.paletteSizeUv = 0; - if (this.SequenceHeader.ModeInfoSize >= (int)Av1BlockSize.Block8x8 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4WideCount() <= 64 && - ((Av1BlockSize)this.SequenceHeader.ModeInfoSize).Get4x4HighCount() <= 64 && + if (partitionInfo.ModeInfo.BlockSize >= Av1BlockSize.Block8x8 && + partitionInfo.ModeInfo.BlockSize.GetWidth() <= 64 && + partitionInfo.ModeInfo.BlockSize.GetHeight() <= 64 && this.FrameInfo.AllowScreenContentTools) { - this.PaletteModeInfo(ref reader); + this.PaletteModeInfo(ref reader, partitionInfo); } - this.FilterIntraModeInfo(ref reader, blockSize); + this.FilterIntraModeInfo(ref reader, partitionInfo); } } - private bool IsChromaForLumaAllowed(Av1BlockSize blockSize) + private bool AllowIntraBlockCopy() + => (this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && + (this.SequenceHeader.ForceScreenContentTools > 0) && + this.FrameInfo.AllowIntraBlockCopy; + + private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo) { - if (this.lossless) + if (this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]) { // In lossless, CfL is available when the partition size is equal to the // transform size. bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; - Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); return planeBlockSize == Av1BlockSize.Block4x4; } // Spec: CfL is available to luma partitions lesser than or equal to 32x32 - return blockSize.Get4x4WideCount() <= 32 && blockSize.Get4x4HighCount() <= 32; + return partitionInfo.ModeInfo.BlockSize.GetWidth() <= 32 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 32; } - private void ReadIntraSegmentId(ref Av1SymbolDecoder reader) => throw new NotImplementedException(); - - private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - bool useFilterIntra = false; if (this.SequenceHeader.EnableFilterIntra && - this.yMode == Av1PredictionMode.DC && this.paletteSizeY == 0 && - Math.Max(blockSize.GetWidth(), blockSize.GetHeight()) <= 32) + partitionInfo.ModeInfo.YMode == Av1PredictionMode.DC && + partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) == 0 && + Math.Max(partitionInfo.ModeInfo.BlockSize.GetWidth(), partitionInfo.ModeInfo.BlockSize.GetHeight()) <= 32) { - useFilterIntra = reader.ReadUseFilterUltra(); - if (useFilterIntra) + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = reader.ReadUseFilterUltra(partitionInfo.ModeInfo.BlockSize); + if (partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra) { - this.filterUltraMode = reader.ReadFilterUltraMode(); + partitionInfo.ModeInfo.FilterIntraModeInfo.Mode = reader.ReadFilterUltraMode(); } } + else + { + partitionInfo.ModeInfo.FilterIntraModeInfo.UseFilterIntra = false; + } } - private void PaletteModeInfo(ref Av1SymbolDecoder reader) => + private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); - private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader) => + private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); - private void IntraAngleInfoY(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize) { - this.angleDeltaY = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.yMode)) + int angleDelta = 0; + if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(mode)) { - int angleDeltaY = reader.ReadAngleDelta(this.yMode); - this.angleDeltaY = angleDeltaY - ObuConstants.MaxAngleDelta; + int symbol = reader.ReadAngleDelta(mode); + angleDelta = symbol - ObuConstants.MaxAngleDelta; } - } - private void IntraAngleInfoUv(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) - { - this.angleDeltaUv = 0; - if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(this.uvMode)) - { - int angleDeltaUv = reader.ReadAngleDelta(this.uvMode); - this.angleDeltaUv = angleDeltaUv - ObuConstants.MaxAngleDelta; - } + return angleDelta; } private static bool IsDirectionalMode(Av1PredictionMode mode) => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; - private void IntraSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { if (this.FrameInfo.SegmentationParameters.Enabled) { - this.ReadSegmentId(ref reader, rowIndex, columnIndex); + this.ReadSegmentId(ref reader, partitionInfo); } - else + + int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int x_mis = Math.Min(this.FrameInfo.ModeInfoColumnCount - partitionInfo.ColumnIndex, bw4); + int y_mis = Math.Min(this.FrameInfo.ModeInfoRowCount - partitionInfo.RowIndex, bh4); + + for (int y = 0; y < y_mis; y++) { - this.segmentId = 0; + for (int x = 0; x < x_mis; x++) + { + this.segmentIds[partitionInfo.RowIndex + y][partitionInfo.ColumnIndex + x] = partitionInfo.ModeInfo.SegmentId; + } } - - this.lossless = this.FrameInfo.LosslessArray[this.segmentId]; } - private void ReadSegmentId(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex) + private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - int pred; + int predictor; int prevUL = -1; int prevU = -1; int prevL = -1; - if (this.availableUp && this.availableLeft) + int columnIndex = partitionInfo.ColumnIndex; + int rowIndex = partitionInfo.RowIndex; + if (partitionInfo.AvailableUp && partitionInfo.AvailableLeft) { - prevUL = this.segmentIds[rowIndex - 1][columnIndex - 1]; + prevUL = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex - 1); } - if (this.availableUp) + if (partitionInfo.AvailableUp) { - prevU = this.segmentIds[rowIndex - 1][columnIndex]; + prevU = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex); } - if (this.availableLeft) + if (partitionInfo.AvailableLeft) { - prevU = this.segmentIds[rowIndex][columnIndex - 1]; + prevU = this.GetSegmentId(partitionInfo, rowIndex, columnIndex - 1); } if (prevU == -1) { - pred = prevL == -1 ? 0 : prevL; + predictor = prevL == -1 ? 0 : prevL; } else if (prevL == -1) { - pred = prevU; + predictor = prevU; } else { - pred = prevU == prevUL ? prevU : prevL; + predictor = prevU == prevUL ? prevU : prevL; } - if (this.skip) + if (partitionInfo.ModeInfo.Skip) { - this.segmentId = 0; + partitionInfo.ModeInfo.SegmentId = predictor; } else { + int ctx = prevUL < 0 ? 0 /* Edge cases */ + : prevUL == prevU && prevUL == prevL ? 2 + : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0; int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; - this.segmentId = NegativeDeinterleave(reader.ReadSegmentId(-1), pred, lastActiveSegmentId + 1); + partitionInfo.ModeInfo.SegmentId = NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1); + } + } + + private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int columnIndex) + { + int modeInfoOffset = (rowIndex * this.FrameInfo.ModeInfoColumnCount) + columnIndex; + int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int xMin = Math.Min(this.FrameInfo.ModeInfoColumnCount - columnIndex, bw4); + int yMin = Math.Min(this.FrameInfo.ModeInfoRowCount - rowIndex, bh4); + int segmentId = ObuConstants.MaxSegments - 1; + for (int y = 0; y < yMin; y++) + { + for (int x = 0; x < xMin; x++) + { + segmentId = Math.Min(segmentId, this.segmentIds[y][x]); + } } + + return segmentId; } private static int NegativeDeinterleave(int diff, int reference, int max) @@ -731,41 +755,45 @@ private static int NegativeDeinterleave(int diff, int reference, int max) } } - private void ReadCdef(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - if (this.skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) + if (partitionInfo.ModeInfo.Skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) { return; } int cdefSize4 = Av1BlockSize.Block64x64.Get4x4WideCount(); int cdefMask4 = ~(cdefSize4 - 1); - int r = rowIndex & cdefMask4; - int c = columnIndex & cdefMask4; - if (this.cdefIndex[r][c] == -1) - { - this.cdefIndex[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); - int w4 = blockSize.Get4x4WideCount(); - int h4 = blockSize.Get4x4HighCount(); - for (int i = r; i < r + h4; i += cdefSize4) + int r = partitionInfo.RowIndex & cdefMask4; + int c = partitionInfo.ColumnIndex & cdefMask4; + if (partitionInfo.CdefStrength[r][c] == -1) + { + partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); + if (this.SequenceHeader.SuperBlockSize == Av1BlockSize.Block128x128) { - for (int j = c; j < c + w4; j += cdefSize4) + int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int h4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + for (int i = r; i < r + h4; i += cdefSize4) { - this.cdefIndex[i][j] = this.cdefIndex[r][c]; + for (int j = c; j < c + w4; j += cdefSize4) + { + partitionInfo.CdefStrength[i & cdefMask4][j & cdefMask4] = partitionInfo.CdefStrength[r][c]; + } } } } } - private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) + if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent || + (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (this.readDeltas && this.FrameInfo.DeltaLoopFilterParameters.IsPresent) + if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent) { int frameLoopFilterCount = 1; if (this.FrameInfo.DeltaLoopFilterParameters.Multi) @@ -793,28 +821,32 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1BlockSize block } } - private void ReadSkip(ref Av1SymbolDecoder reader) + private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { + int segmentId = partitionInfo.ModeInfo.SegmentId; if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && - this.FrameInfo.SegmentationParameters.IsFeatureActive(-1, ObuSegmentationFeature.LevelSkip)) + this.FrameInfo.SegmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.Skip)) { - this.skip = true; + return true; } else { - this.skip = reader.ReadSkip(-1); + int aboveSkip = partitionInfo.AboveModeInfo != null && partitionInfo.AboveModeInfo.Skip ? 1 : 0; + int leftSkip = partitionInfo.LeftModeInfo != null && partitionInfo.LeftModeInfo.Skip ? 1 : 0; + return reader.ReadSkip(aboveSkip + leftSkip); } } - private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize blockSize) + private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (blockSize == superBlockSize && this.skip) + if (!this.FrameInfo.DeltaQParameters.IsPresent || + (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (this.readDeltas) + if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperBlockSize || !partitionInfo.ModeInfo.Skip) { int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) @@ -829,6 +861,7 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1BlockSize b bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); + partitionInfo.SuperblockInfo.SuperblockDeltaQ[0] = this.currentQuantizerIndex; } } } From 98bad4d1d9a21ea2aa57f021497f9971981db6f5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jun 2024 21:29:29 +0200 Subject: [PATCH 064/190] Superblock decoding --- src/ImageSharp/Formats/Heif/Av1/Readme.md | 48 ++++++ .../Heif/Av1/Symbol/Av1SuperblockInfo.cs | 22 ++- .../Formats/Heif/Av1/Symbol/Av1TileDecoder.cs | 144 ++++++++++++++++-- .../Heif/Av1/Symbol/Av1TransformInfo.cs | 8 + 4 files changed, 206 insertions(+), 16 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Readme.md create mode 100644 src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md new file mode 100644 index 0000000000..056d351cb7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md @@ -0,0 +1,48 @@ +# Open Bitstream Unit + +An OBU unit is a unit of parameters encoded in a bitstream format. In AVIF, it contains a single frame. +This frame is coded using no other frame as reference, it is a so called INTRA frame. AV1 movie encoding also defines INTER frames, +which are predictions of one or more other frames. INTER frames are not used in AVIF and therefore this coded ignores INTER frames. + +An OBU section for AVIF consists of the following headers: + +## Temporal delimiter + +In AV1 movies this is a time point. Although irrelevant for AVIF, most implementtions write one such delimiter at the start of the section. + +## Sequence header + +Common herader for a list (or sequence) of frames. For AVIF, this is exaclty 1 frame. For AVIF, this header can be reduced in size when its `ReducedStillPictureHerader` parameter is true. +This setting is recommended, as all the extra parameters are not applicable for AVIF. + +## Frame header + +Can be 3 different OBU types, which define a single INTRA frame in AVIF files. + +## Tile group + +Defines the tiling parameters and contains the parameters its tile using a different coding. + +# Tiling + +In AV1 a frame is made up of 1 or more tiles. The parameters for each tile are entropy encoded using the context aware symbol coding. +These parameters are contained in an OBU tile group header. + +## Superblock + +A tile consists of one or more superblocks. Superblocks can be either 64x64 or 128x128 pixels in size. +This choice is made per frame, and is specified in the `ObuFrameHeader`. +A superblock contains one or more partitions, to further devide the area. + +## Partition + +A superblock contains one or more Partitions. The partition Type determines the number of partitions it is further split in. +Paritions can contain other partitions and blocks. + +## Block + +A block is the smallest are of the image which has the same transformation parameters. A block contains ore or more ModeInfos. + +## ModeInfo + +The smallest unit in the frame. It determines the parameters for an area of 4 by 4 pixels. diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs index fccc6f6079..76528351f9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs @@ -5,7 +5,27 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1SuperblockInfo { - public int[] SuperblockDeltaQ { get; internal set; } = []; + public Av1SuperblockInfo(Av1BlockModeInfo superblockModeInfo, Av1TransformInfo superblockTransformInfo) + { + this.SuperblockModeInfo = superblockModeInfo; + this.SuperblockTransformInfo = superblockTransformInfo; + } + + public int SuperblockDeltaQ { get; internal set; } + + public Av1BlockModeInfo SuperblockModeInfo { get; set; } + + public int[] CoefficientsY { get; set; } = []; + + public int[] CoefficientsU { get; set; } = []; + + public int[] CoefficientsV { get; set; } = []; + + public Av1TransformInfo SuperblockTransformInfo { get; set; } + + public int CdefStrength { get; internal set; } + + public int SuperblockDeltaLoopFilter { get; set; } public Av1BlockModeInfo GetModeInfo(int rowIndex, int columnIndex) => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs index 00812e521b..46f682db75 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs @@ -14,7 +14,9 @@ internal class Av1TileDecoder : IAv1TileDecoder private static readonly int[] WienerTapsMid = [3, -7, 15]; private const int PartitionProbabilitySet = 4; - private int[] deltaLoopFilter = []; + // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 DC + 16 AC). + private const int NumberofCoefficients = 1 + 16; + private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; @@ -30,12 +32,50 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; + private int[] coefficientsY = []; + private int[] coefficientsU = []; + private int[] coefficientsV = []; + private int numModeInfosInSuperblock; + private int superblockColumnCount; + private int superblockRowCount; + private Av1SuperblockInfo[] superblockInfos; + private Av1BlockModeInfo[] modeInfos; + private Av1TransformInfo[] transformInfosY; + private Av1TransformInfo[] transformInfosUv; + private int[] deltaQ; + private int[] cdefStrength; + private int[] deltaLoopFilter; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; this.TileInfo = tileInfo; + + // init_main_frame_ctxt + int superblockSizeLog2 = this.SequenceHeader.SuperBlockSizeLog2; + int superblockAlignedWidth = Av1Math.AlignPowerOf2(this.SequenceHeader.MaxFrameWidth, superblockSizeLog2); + int superblockAlignedHeight = Av1Math.AlignPowerOf2(this.SequenceHeader.MaxFrameHeight, superblockSizeLog2); + this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2; + this.superblockRowCount = superblockAlignedHeight >> superblockSizeLog2; + int superblockCount = this.superblockColumnCount * this.superblockRowCount; + this.numModeInfosInSuperblock = (1 << (superblockSizeLog2 - ObuConstants.ModeInfoSizeLog2)) * (1 << (superblockSizeLog2 - ObuConstants.ModeInfoSizeLog2)); + + this.superblockInfos = new Av1SuperblockInfo[superblockCount]; + this.modeInfos = new Av1BlockModeInfo[superblockCount * this.numModeInfosInSuperblock]; + this.transformInfosY = new Av1TransformInfo[superblockCount * this.numModeInfosInSuperblock]; + this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.numModeInfosInSuperblock]; + this.coefficientsY = new int[superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients]; + int subsamplingFactor = (this.SequenceHeader.ColorConfig.SubSamplingX && this.SequenceHeader.ColorConfig.SubSamplingY) ? 2 : + (this.SequenceHeader.ColorConfig.SubSamplingX && !this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : + (!this.SequenceHeader.ColorConfig.SubSamplingX && !this.SequenceHeader.ColorConfig.SubSamplingY) ? 0 : -1; + Guard.IsFalse(subsamplingFactor == -1, nameof(subsamplingFactor), "Invalid combination of subsampling."); + this.coefficientsU = new int[(superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients) >> subsamplingFactor]; + this.coefficientsV = new int[(superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients) >> subsamplingFactor]; + this.deltaQ = new int[superblockCount]; + this.cdefStrength = new int[superblockCount * (this.SequenceHeader.Use128x128SuperBlock ? 4 : 1)]; + Array.Fill(this.cdefStrength, -1); + this.deltaLoopFilter = new int[superblockCount * ObuConstants.FrameLoopFilterCount]; } public bool SequenceHeaderDone { get; set; } @@ -58,7 +98,7 @@ public void DecodeTile(Span tileData, int tileNum) this.aboveContext.Clear(this.TileInfo.TileColumnStartModeInfo[tileColumnIndex], this.TileInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); this.ClearLoopFilterDelta(); - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; this.referenceSgrXqd = new int[planesCount][]; this.referenceLrWiener = new int[planesCount][][]; for (int plane = 0; plane < planesCount; plane++) @@ -87,11 +127,24 @@ public void DecodeTile(Span tileData, int tileNum) bool readDeltas = this.FrameInfo.DeltaQParameters.IsPresent; + this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); + + int superblockIndex = (superBlockRow * this.superblockColumnCount) + superBlockColumn; + int cdefFactor = this.SequenceHeader.Use128x128SuperBlock ? 4 : 1; + Av1SuperblockInfo superblockInfo = new(this.modeInfos[superblockIndex], this.transformInfosY[superblockIndex]) + { + CoefficientsY = this.coefficientsY, + CoefficientsU = this.coefficientsU, + CoefficientsV = this.coefficientsV, + CdefStrength = this.cdefStrength[superblockIndex * cdefFactor], + SuperblockDeltaLoopFilter = this.deltaLoopFilter[ObuConstants.FrameLoopFilterCount * superblockIndex], + SuperblockDeltaQ = this.deltaQ[superblockIndex] + }; + // Nothing to do for CDEF // this.ClearCdef(row, column); - this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - this.ReadLoopRestoration(row, column, superBlockSize); - this.ParsePartition(ref reader, row, column, superBlockSize); + // this.ReadLoopRestoration(row, column, superBlockSize); + this.ParsePartition(ref reader, row, column, superBlockSize, superblockInfo); } } } @@ -150,9 +203,8 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } - private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo) { - Av1SuperblockInfo superblockInfo = new(); if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) { @@ -164,8 +216,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 2; - bool hasRows = rowIndex + halfBlock4x4Size < this.TileInfo.TileRowCount; - bool hasColumns = columnIndex + halfBlock4x4Size < this.TileInfo.TileColumnCount; + bool hasRows = (rowIndex + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; + bool hasColumns = (columnIndex + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; Av1PartitionType partitionType = Av1PartitionType.Split; if (blockSize < Av1BlockSize.Block8x8) { @@ -194,13 +246,75 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum switch (partitionType) { case Av1PartitionType.Split: - this.ParsePartition(ref reader, rowIndex, columnIndex, subSize); - this.ParsePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize); - this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize); - this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize); + this.ParsePartition(ref reader, rowIndex, columnIndex, subSize, superblockInfo); + this.ParsePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize, superblockInfo); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize, superblockInfo); + this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo); break; case Av1PartitionType.None: this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.None); + break; + case Av1PartitionType.Horizontal: + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal); + if (hasRows) + { + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal); + } + + break; + case Av1PartitionType.Vertical: + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.Vertical); + if (hasRows) + { + this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.Vertical); + } + + break; + case Av1PartitionType.HorizontalA: + this.ParseBlock(ref reader, rowIndex, columnIndex, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.HorizontalA); + break; + case Av1PartitionType.HorizontalB: + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + break; + case Av1PartitionType.VerticalA: + this.ParseBlock(ref reader, rowIndex, columnIndex, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.VerticalA); + break; + case Av1PartitionType.VerticalB: + this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.VerticalB); + break; + case Av1PartitionType.Horizontal4: + for (int i = 0; i < 4; i++) + { + int currentBlockRow = rowIndex + (i * quarterBlock4x4Size); + if (i > 0 && currentBlockRow > this.FrameInfo.ModeInfoRowCount) + { + break; + } + + this.ParseBlock(ref reader, currentBlockRow, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal4); + } + + break; + case Av1PartitionType.Vertical4: + for (int i = 0; i < 4; i++) + { + int currentBlockColumn = columnIndex + (i * quarterBlock4x4Size); + if (i > 0 && currentBlockColumn > this.FrameInfo.ModeInfoColumnCount) + { + break; + } + + this.ParseBlock(ref reader, rowIndex, currentBlockColumn, subSize, superblockInfo, Av1PartitionType.Vertical4); + } + break; default: throw new NotImplementedException($"Partition type: {partitionType} is not supported."); @@ -334,7 +448,7 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(), false, Av1PartitionType.None); + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(new(1, Av1BlockSize.Invalid), new()), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -861,7 +975,7 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); - partitionInfo.SuperblockInfo.SuperblockDeltaQ[0] = this.currentQuantizerIndex; + partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs new file mode 100644 index 0000000000..910309f873 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1TransformInfo +{ +} From c0fcb42cd39a8f2bde73176503180136ca8bcbdf Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jun 2024 21:29:56 +0200 Subject: [PATCH 065/190] Namespace rename --- .../Formats/Heif/Av1/{Symbol => Tiling}/Av1BlockModeInfo.cs | 0 .../Heif/Av1/{Symbol => Tiling}/Av1DefaultDistributions.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1Distribution.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1FilterIntraMode.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1IntraFilterModeInfo.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1ParseAboveContext.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1ParseLeftContext.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1PartitionInfo.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1PlaneType.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1SuperblockInfo.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolDecoder.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolEncoder.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolReader.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolWriter.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1TileDecoder.cs | 0 .../Formats/Heif/Av1/{Symbol => Tiling}/Av1TransformInfo.cs | 0 16 files changed, 0 insertions(+), 0 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1BlockModeInfo.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1DefaultDistributions.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1Distribution.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1FilterIntraMode.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1IntraFilterModeInfo.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1ParseAboveContext.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1ParseLeftContext.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1PartitionInfo.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1PlaneType.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1SuperblockInfo.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolDecoder.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolEncoder.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolReader.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1SymbolWriter.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1TileDecoder.cs (100%) rename src/ImageSharp/Formats/Heif/Av1/{Symbol => Tiling}/Av1TransformInfo.cs (100%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1BlockModeInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1DefaultDistributions.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1Distribution.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1FilterIntraMode.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1IntraFilterModeInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseAboveContext.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1ParseLeftContext.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PartitionInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1PlaneType.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SuperblockInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolEncoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolReader.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1SymbolWriter.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TileDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs similarity index 100% rename from src/ImageSharp/Formats/Heif/Av1/Symbol/Av1TransformInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs From bfed4ee9133bcb14afd29b0f26a7efa2d7c14dc4 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jun 2024 21:31:53 +0200 Subject: [PATCH 066/190] Add spec reference --- src/ImageSharp/Formats/Heif/Av1/Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md index 056d351cb7..6abfa731cc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Readme.md +++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md @@ -46,3 +46,7 @@ A block is the smallest are of the image which has the same transformation param ## ModeInfo The smallest unit in the frame. It determines the parameters for an area of 4 by 4 pixels. + +# References + +[AV1 specification](https://aomediacodec.github.io/av1-spec/av1-spec.pdf) From a8539f24b9b240ccb52a78c07fe6c4a7fadd936c Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jun 2024 21:33:56 +0200 Subject: [PATCH 067/190] Rename Av1Constants class --- .../ObuConstants.cs => Av1Constants.cs} | 6 +-- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 10 ++-- .../ObuQuantizationParameters.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 52 +++++++++---------- .../ObuSegmentationParameters.cs | 6 +-- .../Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs | 4 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 24 ++++----- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 44 ++++++++-------- 8 files changed, 74 insertions(+), 74 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/{OpenBitstreamUnit/ObuConstants.cs => Av1Constants.cs} (95%) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs similarity index 95% rename from src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs rename to src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index d3d2194a2e..d83389d9dc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -1,11 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal static class ObuConstants +internal static class Av1Constants { public const ObuSequenceProfile MaxSequenceProfile = ObuSequenceProfile.Professional; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 7ebca0c235..f3f7e957dd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -19,7 +19,7 @@ internal class ObuFrameHeader public bool CodedLossless { get; set; } - public bool[] LosslessArray { get; set; } = new bool[ObuConstants.MaxSegmentCount]; + public bool[] LosslessArray { get; set; } = new bool[Av1Constants.MaxSegmentCount]; public ObuQuantizationParameters QuantizationParameters { get; set; } = new ObuQuantizationParameters(); @@ -63,9 +63,9 @@ internal class ObuFrameHeader internal ObuFrameType FrameType { get; set; } - internal bool[] ReferenceValid { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + internal bool[] ReferenceValid { get; set; } = new bool[Av1Constants.ReferenceFrameCount]; - internal bool[] ReferenceOrderHint { get; set; } = new bool[ObuConstants.ReferenceFrameCount]; + internal bool[] ReferenceOrderHint { get; set; } = new bool[Av1Constants.ReferenceFrameCount]; internal bool ShowFrame { get; set; } @@ -79,11 +79,11 @@ internal class ObuFrameHeader internal uint CurrentFrameId { get; set; } - internal uint[] ReferenceFrameIndex { get; set; } = new uint[ObuConstants.ReferenceFrameCount]; + internal uint[] ReferenceFrameIndex { get; set; } = new uint[Av1Constants.ReferenceFrameCount]; internal uint OrderHint { get; set; } - internal uint PrimaryReferenceFrame { get; set; } = ObuConstants.PrimaryReferenceFrameNone; + internal uint PrimaryReferenceFrame { get; set; } = Av1Constants.PrimaryReferenceFrameNone; internal uint RefreshFrameFlags { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs index 53aa1c88bb..58aa3d6316 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs @@ -7,7 +7,7 @@ internal class ObuQuantizationParameters { public int BaseQIndex { get; set; } - public int[] QIndex { get; set; } = new int[ObuConstants.MaxSegmentCount]; + public int[] QIndex { get; set; } = new int[Av1Constants.MaxSegmentCount]; public bool IsUsingQMatrix { get; internal set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index fcae54d647..1a90eaebfc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -197,7 +197,7 @@ private static void ComputeImageSize(ObuSequenceHeader sequenceHeader, ObuFrameH { frameInfo.ModeInfoColumnCount = 2 * ((frameInfo.FrameSize.FrameWidth + 7) >> 3); frameInfo.ModeInfoRowCount = 2 * ((frameInfo.FrameSize.FrameHeight + 7) >> 3); - frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, ObuConstants.MaxSuperBlockSizeLog2) >> ObuConstants.ModeInfoSizeLog2; + frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2; } private static bool IsValidObuType(ObuType type) => type switch @@ -211,7 +211,7 @@ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrame private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); - if (sequenceHeader.SequenceProfile > ObuConstants.MaxSequenceProfile) + if (sequenceHeader.SequenceProfile > Av1Constants.MaxSequenceProfile) { throw new ImageFormatException("Unknown sequence profile."); } @@ -230,7 +230,7 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc ObuOperatingPoint operatingPoint = new(); sequenceHeader.OperatingPoint[0] = operatingPoint; operatingPoint.OperatorIndex = 0; - operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(ObuConstants.LevelBits); + operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(Av1Constants.LevelBits); if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) { throw new ImageFormatException("Invalid sequence level."); @@ -388,20 +388,20 @@ private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, if (useSuperResolution) { - frameInfo.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(ObuConstants.SuperResolutionScaleBits) + ObuConstants.SuperResolutionScaleDenominatorMinimum; + frameInfo.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(Av1Constants.SuperResolutionScaleBits) + Av1Constants.SuperResolutionScaleDenominatorMinimum; } else { - frameInfo.FrameSize.SuperResolutionDenominator = ObuConstants.ScaleNumerator; + frameInfo.FrameSize.SuperResolutionDenominator = Av1Constants.ScaleNumerator; } frameInfo.FrameSize.SuperResolutionUpscaledWidth = frameInfo.FrameSize.FrameWidth; frameInfo.FrameSize.FrameWidth = - ((frameInfo.FrameSize.SuperResolutionUpscaledWidth * ObuConstants.ScaleNumerator) + + ((frameInfo.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) + (frameInfo.FrameSize.SuperResolutionDenominator / 2)) / frameInfo.FrameSize.SuperResolutionDenominator; - if (frameInfo.FrameSize.SuperResolutionDenominator != ObuConstants.ScaleNumerator) + if (frameInfo.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator) { int manWidth = Math.Min(16, frameInfo.FrameSize.SuperResolutionUpscaledWidth); frameInfo.FrameSize.FrameWidth = Math.Max(manWidth, frameInfo.FrameSize.FrameWidth); @@ -426,7 +426,7 @@ private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { bool foundReference = false; - for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) + for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) { foundReference = reader.ReadBoolean(); if (foundReference) @@ -485,13 +485,13 @@ private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequen } int superBlockSize = superBlockShift + 2; - int maxTileAreaOfSuperBlock = ObuConstants.MaxTileArea >> (2 * superBlockSize); + int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); - tileInfo.MaxTileWidthSuperBlock = ObuConstants.MaxTileWidth >> superBlockSize; - tileInfo.MaxTileHeightSuperBlock = (ObuConstants.MaxTileArea / ObuConstants.MaxTileWidth) >> superBlockSize; + tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); - tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount)); - tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount)); + tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); tileInfo.HasUniformTileSpacing = reader.ReadBoolean(); if (tileInfo.HasUniformTileSpacing) @@ -610,7 +610,7 @@ private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequen tileInfo.TileRowCountLog2 = TileLog2(1, tileInfo.TileRowCount); } - if (tileInfo.TileColumnCount > ObuConstants.MaxTileColumnCount || tileInfo.TileRowCount > ObuConstants.MaxTileRowCount) + if (tileInfo.TileColumnCount > Av1Constants.MaxTileColumnCount || tileInfo.TileRowCount > Av1Constants.MaxTileRowCount) { throw new ImageFormatException("Tile width or height too big."); } @@ -659,9 +659,9 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) { - frameInfo.ReferenceValid = new bool[ObuConstants.ReferenceFrameCount]; - frameInfo.ReferenceOrderHint = new bool[ObuConstants.ReferenceFrameCount]; - for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + frameInfo.ReferenceValid = new bool[Av1Constants.ReferenceFrameCount]; + frameInfo.ReferenceOrderHint = new bool[Av1Constants.ReferenceFrameCount]; + for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { frameInfo.ReferenceValid[i] = false; frameInfo.ReferenceOrderHint[i] = false; @@ -717,7 +717,7 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I } int diffLength = sequenceHeader.DeltaFrameIdLength; - for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { if (frameInfo.CurrentFrameId > (1U << diffLength)) { @@ -756,11 +756,11 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I if (isIntraFrame || frameInfo.ErrorResilientMode) { - frameInfo.PrimaryReferenceFrame = ObuConstants.PrimaryReferenceFrameNone; + frameInfo.PrimaryReferenceFrame = Av1Constants.PrimaryReferenceFrameNone; } else { - frameInfo.PrimaryReferenceFrame = reader.ReadLiteral(ObuConstants.PimaryReferenceBits); + frameInfo.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits); } // Skipping, as no decoder info model present @@ -785,7 +785,7 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I { if (frameInfo.ErrorResilientMode && sequenceHeader.OrderHintInfo != null) { - for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { int referenceOrderHint = (int)reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); if (referenceOrderHint != (frameInfo.ReferenceOrderHint[i] ? 1U : 0U)) @@ -822,7 +822,7 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I frameInfo.DisableFrameEndUpdateCdf = true; } - if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { SetupPastIndependence(frameInfo); } @@ -836,14 +836,14 @@ private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, I // SetupSegmentationDequantization(); Av1MainParseContext mainParseContext = new(); - if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); } int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; frameInfo.CodedLossless = true; - for (int segmentId = 0; segmentId < ObuConstants.MaxSegmentCount; segmentId++) + for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) { int qIndex = GetQIndex(frameInfo.SegmentationParameters, segmentId, frameInfo.QuantizationParameters.BaseQIndex); frameInfo.QuantizationParameters.QIndex[segmentId] = qIndex; @@ -907,7 +907,7 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i { int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; int qIndex = baseQIndex + data; - return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ); + return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ); } else { @@ -1180,7 +1180,7 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, } } - frameInfo.LoopRestorationParameters[0].Size = ObuConstants.RestorationMaxTileSize >> (int)(2 - loopRestorationShift); + frameInfo.LoopRestorationParameters[0].Size = Av1Constants.RestorationMaxTileSize >> (int)(2 - loopRestorationShift); int uvShift = 0; if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && usesChromaLoopRestoration) { diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index c74a698b2f..64624e8535 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -5,13 +5,13 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuSegmentationParameters { - public int[,] QMLevel { get; internal set; } = new int[3, ObuConstants.MaxSegmentCount]; + public int[,] QMLevel { get; internal set; } = new int[3, Av1Constants.MaxSegmentCount]; - public bool[,] FeatureEnabled { get; internal set; } = new bool[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + public bool[,] FeatureEnabled { get; internal set; } = new bool[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax]; public bool Enabled { get; internal set; } - public int[,] FeatureData { get; internal set; } = new int[ObuConstants.MaxSegmentCount, ObuConstants.SegmentationLevelMax]; + public int[,] FeatureData { get; internal set; } = new int[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax]; public bool SegmentIdPrecedesSkip { get; internal set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs index ac5379f88a..b995713d8a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs @@ -23,13 +23,13 @@ internal class ObuTileInfo internal int TileColumnCount { get; set; } - internal int[] TileColumnStartModeInfo { get; set; } = new int[ObuConstants.MaxTileRowCount + 1]; + internal int[] TileColumnStartModeInfo { get; set; } = new int[Av1Constants.MaxTileRowCount + 1]; internal int MinLog2TileRowCount { get; set; } internal int TileRowCountLog2 { get; set; } - internal int[] TileRowStartModeInfo { get; set; } = new int[ObuConstants.MaxTileColumnCount + 1]; + internal int[] TileRowStartModeInfo { get; set; } = new int[Av1Constants.MaxTileColumnCount + 1]; internal int TileRowCount { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 45a05653d8..00346743f8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -82,7 +82,7 @@ private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequen writer.WriteLiteral((uint)sequenceHeader.SequenceProfile, 3); writer.WriteBoolean(true); // IsStillPicture writer.WriteBoolean(true); // IsReducedStillPicture - writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, ObuConstants.LevelBits); + writer.WriteLiteral((uint)sequenceHeader.OperatingPoint[0].SequenceLevelIndex, Av1Constants.LevelBits); // Frame width and Height writer.WriteLiteral((uint)sequenceHeader.FrameWidthBits - 1, 4); @@ -168,7 +168,7 @@ private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer if (useSuperResolution) { - writer.WriteLiteral((uint)frameInfo.FrameSize.SuperResolutionDenominator - ObuConstants.SuperResolutionScaleDenominatorMinimum, ObuConstants.SuperResolutionScaleBits); + writer.WriteLiteral((uint)frameInfo.FrameSize.SuperResolutionDenominator - Av1Constants.SuperResolutionScaleDenominatorMinimum, Av1Constants.SuperResolutionScaleBits); } } @@ -186,7 +186,7 @@ private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeade private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { bool foundReference = false; - for (int i = 0; i < ObuConstants.ReferencesPerFrame; i++) + for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) { writer.WriteBoolean(foundReference); if (foundReference) @@ -237,13 +237,13 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead } int superBlockSize = superBlockShift + 2; - int maxTileAreaOfSuperBlock = ObuConstants.MaxTileArea >> (2 * superBlockSize); + int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); - tileInfo.MaxTileWidthSuperBlock = ObuConstants.MaxTileWidth >> superBlockSize; - tileInfo.MaxTileHeightSuperBlock = (ObuConstants.MaxTileArea / ObuConstants.MaxTileWidth) >> superBlockSize; + tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); - tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, ObuConstants.MaxTileColumnCount)); - tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, ObuConstants.MaxTileRowCount)); + tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); writer.WriteBoolean(tileInfo.HasUniformTileSpacing); @@ -348,7 +348,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, } int diffLength = sequenceHeader.DeltaFrameIdLength; - for (int i = 0; i < ObuConstants.ReferenceFrameCount; i++) + for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { if (frameInfo.CurrentFrameId > (1U << diffLength)) { @@ -370,7 +370,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, if (!isIntraFrame && !frameInfo.ErrorResilientMode) { - writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, ObuConstants.PimaryReferenceBits); + writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, Av1Constants.PimaryReferenceBits); } // Skipping, as no decoder info model present @@ -395,7 +395,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, } } - if (frameInfo.PrimaryReferenceFrame == ObuConstants.PrimaryReferenceFrameNone) + if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { SetupPastIndependence(frameInfo); } @@ -436,7 +436,7 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i { int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; int qIndex = baseQIndex + data; - return Av1Math.Clamp(qIndex, 0, ObuConstants.MaxQ); + return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ); } else { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 46f682db75..2be0110be9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -59,7 +59,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2; this.superblockRowCount = superblockAlignedHeight >> superblockSizeLog2; int superblockCount = this.superblockColumnCount * this.superblockRowCount; - this.numModeInfosInSuperblock = (1 << (superblockSizeLog2 - ObuConstants.ModeInfoSizeLog2)) * (1 << (superblockSizeLog2 - ObuConstants.ModeInfoSizeLog2)); + this.numModeInfosInSuperblock = (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)) * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)); this.superblockInfos = new Av1SuperblockInfo[superblockCount]; this.modeInfos = new Av1BlockModeInfo[superblockCount * this.numModeInfosInSuperblock]; @@ -75,7 +75,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo this.deltaQ = new int[superblockCount]; this.cdefStrength = new int[superblockCount * (this.SequenceHeader.Use128x128SuperBlock ? 4 : 1)]; Array.Fill(this.cdefStrength, -1); - this.deltaLoopFilter = new int[superblockCount * ObuConstants.FrameLoopFilterCount]; + this.deltaLoopFilter = new int[superblockCount * Av1Constants.FrameLoopFilterCount]; } public bool SequenceHeaderDone { get; set; } @@ -108,7 +108,7 @@ public void DecodeTile(Span tileData, int tileNum) this.referenceLrWiener[plane] = new int[2][]; for (int pass = 0; pass < 2; pass++) { - this.referenceLrWiener[plane][pass] = new int[ObuConstants.WienerCoefficientCount]; + this.referenceLrWiener[plane][pass] = new int[Av1Constants.WienerCoefficientCount]; Array.Copy(WienerTapsMid, this.referenceLrWiener[plane][pass], WienerTapsMid.Length); } } @@ -117,11 +117,11 @@ public void DecodeTile(Span tileData, int tileNum) int superBlock4x4Size = superBlockSize.Get4x4WideCount(); for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) { - int superBlockRow = row << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; this.leftContext.Clear(); for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) { - int superBlockColumn = column << ObuConstants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; @@ -137,7 +137,7 @@ public void DecodeTile(Span tileData, int tileNum) CoefficientsU = this.coefficientsU, CoefficientsV = this.coefficientsV, CdefStrength = this.cdefStrength[superblockIndex * cdefFactor], - SuperblockDeltaLoopFilter = this.deltaLoopFilter[ObuConstants.FrameLoopFilterCount * superblockIndex], + SuperblockDeltaLoopFilter = this.deltaLoopFilter[Av1Constants.FrameLoopFilterCount * superblockIndex], SuperblockDeltaQ = this.deltaQ[superblockIndex] }; @@ -415,10 +415,10 @@ private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) int num4x4Height = planeSize.Get4x4HighCount(); int subX = plane > 0 && subsamplingX ? 1 : 0; int subY = plane > 0 && subsamplingY ? 1 : 0; - int baseX = (columnChunk >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseY = (rowChunk >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseXBlock = (columnIndex >> subX) * (1 << ObuConstants.ModeInfoSizeLog2); - int baseYBlock = (rowIndex >> subY) * (1 << ObuConstants.ModeInfoSizeLog2); + int baseX = (columnChunk >> subX) * (1 << Av1Constants.ModeInfoSizeLog2); + int baseY = (rowChunk >> subY) * (1 << Av1Constants.ModeInfoSizeLog2); + int baseXBlock = (columnIndex >> subX) * (1 << Av1Constants.ModeInfoSizeLog2); + int baseYBlock = (rowIndex >> subY) * (1 << Av1Constants.ModeInfoSizeLog2); for (int y = 0; y < num4x4Height; y += stepY) { for (int x = 0; x < num4x4Width; x += stepX) @@ -455,15 +455,15 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; int subX = plane > 0 && subsamplingX ? 1 : 0; int subY = plane > 0 && subsamplingY ? 1 : 0; - int columnIndex = startX << subX >> ObuConstants.ModeInfoSizeLog2; - int rowIndex = startY << subY >> ObuConstants.ModeInfoSizeLog2; + int columnIndex = startX << subX >> Av1Constants.ModeInfoSizeLog2; + int rowIndex = startY << subY >> Av1Constants.ModeInfoSizeLog2; int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; int subBlockColumn = columnIndex & superBlockMask; int subBlockRow = rowIndex & superBlockMask; - int stepX = transformSize.GetWidth() >> ObuConstants.ModeInfoSizeLog2; - int stepY = transformSize.GetHeight() >> ObuConstants.ModeInfoSizeLog2; - int maxX = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subX; - int maxY = (this.SequenceHeader.ModeInfoSize * (1 << ObuConstants.ModeInfoSizeLog2)) >> subY; + int stepX = transformSize.GetWidth() >> Av1Constants.ModeInfoSizeLog2; + int stepY = transformSize.GetHeight() >> Av1Constants.ModeInfoSizeLog2; + int maxX = (this.SequenceHeader.ModeInfoSize * (1 << Av1Constants.ModeInfoSizeLog2)) >> subX; + int maxY = (this.SequenceHeader.ModeInfoSize * (1 << Av1Constants.ModeInfoSizeLog2)) >> subY; if (startX >= maxX || startY >= maxY) { return; @@ -724,7 +724,7 @@ private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode if (blockSize >= Av1BlockSize.Block8x8 && IsDirectionalMode(mode)) { int symbol = reader.ReadAngleDelta(mode); - angleDelta = symbol - ObuConstants.MaxAngleDelta; + angleDelta = symbol - Av1Constants.MaxAngleDelta; } return angleDelta; @@ -811,7 +811,7 @@ private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int colum int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); int xMin = Math.Min(this.FrameInfo.ModeInfoColumnCount - columnIndex, bw4); int yMin = Math.Min(this.FrameInfo.ModeInfoRowCount - rowIndex, bh4); - int segmentId = ObuConstants.MaxSegments - 1; + int segmentId = Av1Constants.MaxSegments - 1; for (int y = 0; y < yMin; y++) { for (int x = 0; x < xMin; x++) @@ -912,13 +912,13 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p int frameLoopFilterCount = 1; if (this.FrameInfo.DeltaLoopFilterParameters.Multi) { - frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? ObuConstants.FrameLoopFilterCount : ObuConstants.FrameLoopFilterCount - 2; + frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; } for (int i = 0; i < frameLoopFilterCount; i++) { int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); - if (deltaLoopFilterAbsolute == ObuConstants.DeltaLoopFilterSmall) + if (deltaLoopFilterAbsolute == Av1Constants.DeltaLoopFilterSmall) { int deltaLoopFilterRemainingBits = reader.ReadLiteral(3) + 1; int deltaLoopFilterAbsoluteBitCount = reader.ReadLiteral(deltaLoopFilterRemainingBits); @@ -929,7 +929,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p { bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - this.deltaLoopFilter[i] = Av1Math.Clip3(-ObuConstants.MaxLoopFilter, ObuConstants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); + this.deltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); } } } @@ -963,7 +963,7 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperBlockSize || !partitionInfo.ModeInfo.Skip) { int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); - if (deltaQuantizerAbsolute == ObuConstants.DeltaQuantizerSmall) + if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall) { int deltaQuantizerRemainingBits = reader.ReadLiteral(3) + 1; int deltaQuantizerAbsoluteBitCount = reader.ReadLiteral(deltaQuantizerRemainingBits); From 671c89d287c6c9df497f9798cc65710f1c5a9adc Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jun 2024 21:45:01 +0200 Subject: [PATCH 068/190] Extend readme --- src/ImageSharp/Formats/Heif/Av1/Readme.md | 15 +++++++++++++++ src/ImageSharp/Formats/Heif/Readme.md | 2 -- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md index 6abfa731cc..fca0c25f4b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Readme.md +++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md @@ -49,4 +49,19 @@ The smallest unit in the frame. It determines the parameters for an area of 4 by # References +[AV1 embedded in HEIF](https://aomediacodec.github.io/av1-isobmff) + [AV1 specification](https://aomediacodec.github.io/av1-spec/av1-spec.pdf) + +[AVIF specification](https://aomediacodec.github.io/av1-avif) + +[AV1/AVIF reference implementation](http://gitlab.com/AOMediaCodec/SVT-AV1) + +[AOM's original development implementation](https://github.com/AOMediaCodec/libavif) + +# Test images + +[Netflix image repository](http://download.opencontent.netflix.com/?prefix=AV1/) + +[AVIF sample images](https://github.com/link-u/avif-sample-images) + diff --git a/src/ImageSharp/Formats/Heif/Readme.md b/src/ImageSharp/Formats/Heif/Readme.md index 5dddf81f10..6c7469528f 100644 --- a/src/ImageSharp/Formats/Heif/Readme.md +++ b/src/ImageSharp/Formats/Heif/Readme.md @@ -4,6 +4,4 @@ [HEIF reference implementation from Nokia](https://github.com/nokiatech/heif) -[AVIF reference implementation](http://gitlab.com/AOMediaCodec/SVT-AV1) - [Apple's metadata syntax in HEIC images](http://cheeky4n6monkey.blogspot.com/2017/10/monkey-takes-heic.html) From 19ca2ea3c893f4c458ee079fbbafe8fea1863aa5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 5 Jun 2024 20:50:26 +0200 Subject: [PATCH 069/190] Introduce Frame buffer class --- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 137 ++++++++++++++++++ .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 28 ++-- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 65 ++------- 3 files changed, 163 insertions(+), 67 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs new file mode 100644 index 0000000000..8ae1eec8e1 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -0,0 +1,137 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1FrameBuffer +{ + // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4). + public const int CoefficientCountPerModeInfo = 1 + 16; + + private readonly int[] coefficientsY = []; + private readonly int[] coefficientsU = []; + private readonly int[] coefficientsV = []; + private readonly int modeInfoSizePerSuperblock; + private readonly int modeInfoCountPerSuperblock; + private readonly int superblockColumnCount; + private readonly int superblockRowCount; + private readonly int subsamplingFactor; + private readonly Av1SuperblockInfo[] superblockInfos; + private readonly Av1BlockModeInfo[] modeInfos; + private readonly Av1TransformInfo[] transformInfosY; + private readonly Av1TransformInfo[] transformInfosUv; + private readonly int[] deltaQ; + private readonly int cdefStrengthFactorLog2; + private readonly int[] cdefStrength; + private readonly int deltaLoopFactorLog2 = 2; + private readonly int[] deltaLoopFilter; + + public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) + { + // init_main_frame_ctxt + int superblockSizeLog2 = sequenceHeader.SuperBlockSizeLog2; + int superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2); + int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2); + this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2; + this.superblockRowCount = superblockAlignedHeight >> superblockSizeLog2; + int superblockCount = this.superblockColumnCount * this.superblockRowCount; + this.modeInfoSizePerSuperblock = 1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2); + this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock; + this.superblockInfos = new Av1SuperblockInfo[superblockCount]; + this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock]; + this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock]; + this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock]; + this.coefficientsY = new int[superblockCount * this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo]; + bool subX = sequenceHeader.ColorConfig.SubSamplingX; + bool subY = sequenceHeader.ColorConfig.SubSamplingY; + + // Factor: 444 => 0, 422 => 1, 420 => 2. + this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1; + Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling."); + this.coefficientsU = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor]; + this.coefficientsV = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor]; + this.deltaQ = new int[superblockCount]; + + // Superblock size: 128x128 has sizelog2 = 7, 64x64 = 6. Factor should be 128x128 => 4 and 64x64 => 1. + this.cdefStrengthFactorLog2 = (superblockSizeLog2 - 6) << 2; + this.cdefStrength = new int[superblockCount << this.cdefStrengthFactorLog2]; + Array.Fill(this.cdefStrength, -1); + this.deltaLoopFilter = new int[superblockCount << this.deltaLoopFactorLog2]; + } + + public Av1SuperblockInfo GetSuperblock(Point index) + { + Span span = this.superblockInfos; + int i = (index.Y * this.superblockColumnCount) + index.X; + return span[i]; + } + + public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) + { + Span span = this.modeInfos; + int baseIndex = ((superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X) * this.modeInfoCountPerSuperblock; + int offset = (modeInfoIndex.Y * this.modeInfoSizePerSuperblock) + modeInfoIndex.X; + return span[baseIndex + offset]; + } + + public Av1TransformInfo GetTransformY(Point index) + { + Span span = this.transformInfosY; + int i = (index.Y * this.superblockColumnCount) + index.X; + return span[i * this.modeInfoCountPerSuperblock]; + } + + public void GetTransformUv(Point index, out Av1TransformInfo transformU, out Av1TransformInfo transformV) + { + Span span = this.transformInfosUv; + int i = 2 * ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock; + transformU = span[i]; + transformV = span[i + 1]; + } + + public Span GetCoefficientsY(Point index) + { + Span span = this.coefficientsY; + int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo; + return span.Slice(i, CoefficientCountPerModeInfo); + } + + public Span GetCoefficientsU(Point index) + { + Span span = this.coefficientsU; + int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo; + return span.Slice(i >> this.subsamplingFactor, CoefficientCountPerModeInfo); + } + + public Span GetCoefficientsV(Point index) + { + Span span = this.coefficientsV; + int i = ((index.Y * this.modeInfoCountPerSuperblock) + index.X) * CoefficientCountPerModeInfo; + return span.Slice(i >> this.subsamplingFactor, CoefficientCountPerModeInfo); + } + + public ref int GetDeltaQuantizationIndex(Point index) + { + Span span = this.deltaQ; + int i = (index.Y * this.superblockColumnCount) + index.X; + return ref span[i]; + } + + public Span GetCdefStrength(Point index) + { + Span span = this.cdefStrength; + int i = ((index.Y * this.superblockColumnCount) + index.X) << this.cdefStrengthFactorLog2; + return span.Slice(i, 1 << this.cdefStrengthFactorLog2); + } + + public Span GetDeltaLoopFilter(Point index) + { + Span span = this.deltaLoopFilter; + int i = ((index.Y * this.superblockColumnCount) + index.X) << this.deltaLoopFactorLog2; + return span.Slice(i, 1 << this.deltaLoopFactorLog2); + } + + internal void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 76528351f9..0ee2d80497 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -5,27 +5,31 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1SuperblockInfo { - public Av1SuperblockInfo(Av1BlockModeInfo superblockModeInfo, Av1TransformInfo superblockTransformInfo) + private readonly Av1FrameBuffer frameBuffer; + + public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) { - this.SuperblockModeInfo = superblockModeInfo; - this.SuperblockTransformInfo = superblockTransformInfo; + this.Position = position; + this.frameBuffer = frameBuffer; } - public int SuperblockDeltaQ { get; internal set; } + public Point Position { get; } + + public ref int SuperblockDeltaQ => ref this.frameBuffer.GetDeltaQuantizationIndex(this.Position); - public Av1BlockModeInfo SuperblockModeInfo { get; set; } + public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(default); - public int[] CoefficientsY { get; set; } = []; + public Span CoefficientsY => this.frameBuffer.GetCoefficientsY(this.Position); - public int[] CoefficientsU { get; set; } = []; + public Span CoefficientsU => this.frameBuffer.GetCoefficientsU(this.Position); - public int[] CoefficientsV { get; set; } = []; + public Span CoefficientsV => this.frameBuffer.GetCoefficientsV(this.Position); - public Av1TransformInfo SuperblockTransformInfo { get; set; } + public Av1TransformInfo SuperblockTransformInfo => this.frameBuffer.GetTransformY(this.Position); - public int CdefStrength { get; internal set; } + public Span CdefStrength => this.frameBuffer.GetCdefStrength(this.Position); - public int SuperblockDeltaLoopFilter { get; set; } + public Span SuperblockDeltaLoopFilter => this.frameBuffer.GetDeltaLoopFilter(this.Position); - public Av1BlockModeInfo GetModeInfo(int rowIndex, int columnIndex) => throw new NotImplementedException(); + public Av1BlockModeInfo GetModeInfo(Point index) => this.frameBuffer.GetModeInfo(this.Position, index); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 2be0110be9..da734fb133 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -14,9 +14,6 @@ internal class Av1TileDecoder : IAv1TileDecoder private static readonly int[] WienerTapsMid = [3, -7, 15]; private const int PartitionProbabilitySet = 4; - // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 DC + 16 AC). - private const int NumberofCoefficients = 1 + 16; - private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; @@ -32,19 +29,7 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; - private int[] coefficientsY = []; - private int[] coefficientsU = []; - private int[] coefficientsV = []; - private int numModeInfosInSuperblock; - private int superblockColumnCount; - private int superblockRowCount; - private Av1SuperblockInfo[] superblockInfos; - private Av1BlockModeInfo[] modeInfos; - private Av1TransformInfo[] transformInfosY; - private Av1TransformInfo[] transformInfosUv; - private int[] deltaQ; - private int[] cdefStrength; - private int[] deltaLoopFilter; + private readonly Av1FrameBuffer frameBuffer; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) { @@ -53,29 +38,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo this.TileInfo = tileInfo; // init_main_frame_ctxt - int superblockSizeLog2 = this.SequenceHeader.SuperBlockSizeLog2; - int superblockAlignedWidth = Av1Math.AlignPowerOf2(this.SequenceHeader.MaxFrameWidth, superblockSizeLog2); - int superblockAlignedHeight = Av1Math.AlignPowerOf2(this.SequenceHeader.MaxFrameHeight, superblockSizeLog2); - this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2; - this.superblockRowCount = superblockAlignedHeight >> superblockSizeLog2; - int superblockCount = this.superblockColumnCount * this.superblockRowCount; - this.numModeInfosInSuperblock = (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)) * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)); - - this.superblockInfos = new Av1SuperblockInfo[superblockCount]; - this.modeInfos = new Av1BlockModeInfo[superblockCount * this.numModeInfosInSuperblock]; - this.transformInfosY = new Av1TransformInfo[superblockCount * this.numModeInfosInSuperblock]; - this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.numModeInfosInSuperblock]; - this.coefficientsY = new int[superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients]; - int subsamplingFactor = (this.SequenceHeader.ColorConfig.SubSamplingX && this.SequenceHeader.ColorConfig.SubSamplingY) ? 2 : - (this.SequenceHeader.ColorConfig.SubSamplingX && !this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : - (!this.SequenceHeader.ColorConfig.SubSamplingX && !this.SequenceHeader.ColorConfig.SubSamplingY) ? 0 : -1; - Guard.IsFalse(subsamplingFactor == -1, nameof(subsamplingFactor), "Invalid combination of subsampling."); - this.coefficientsU = new int[(superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients) >> subsamplingFactor]; - this.coefficientsV = new int[(superblockCount * this.numModeInfosInSuperblock * NumberofCoefficients) >> subsamplingFactor]; - this.deltaQ = new int[superblockCount]; - this.cdefStrength = new int[superblockCount * (this.SequenceHeader.Use128x128SuperBlock ? 4 : 1)]; - Array.Fill(this.cdefStrength, -1); - this.deltaLoopFilter = new int[superblockCount * Av1Constants.FrameLoopFilterCount]; + this.frameBuffer = new(this.SequenceHeader); } public bool SequenceHeaderDone { get; set; } @@ -129,17 +92,8 @@ public void DecodeTile(Span tileData, int tileNum) this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - int superblockIndex = (superBlockRow * this.superblockColumnCount) + superBlockColumn; - int cdefFactor = this.SequenceHeader.Use128x128SuperBlock ? 4 : 1; - Av1SuperblockInfo superblockInfo = new(this.modeInfos[superblockIndex], this.transformInfosY[superblockIndex]) - { - CoefficientsY = this.coefficientsY, - CoefficientsU = this.coefficientsU, - CoefficientsV = this.coefficientsV, - CdefStrength = this.cdefStrength[superblockIndex * cdefFactor], - SuperblockDeltaLoopFilter = this.deltaLoopFilter[Av1Constants.FrameLoopFilterCount * superblockIndex], - SuperblockDeltaQ = this.deltaQ[superblockIndex] - }; + Point superblockPosition = new Point(superBlockColumn, superBlockRow); + Av1SuperblockInfo superblockInfo = new(this.frameBuffer, superblockPosition); // Nothing to do for CDEF // this.ClearCdef(row, column); @@ -150,7 +104,7 @@ public void DecodeTile(Span tileData, int tileNum) } private void ClearLoopFilterDelta() - => this.deltaLoopFilter = new int[4]; + => this.frameBuffer.ClearDeltaLoopFilter(); private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { @@ -350,12 +304,12 @@ private void ParseBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnInd if (partitionInfo.AvailableUp) { - partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(rowIndex - 1, columnIndex); + partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex - 1, columnIndex)); } if (partitionInfo.AvailableLeft) { - partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(rowIndex, columnIndex - 1); + partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex, columnIndex - 1)); } this.ReadModeInfo(ref reader, partitionInfo); @@ -448,7 +402,7 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(new(1, Av1BlockSize.Invalid), new()), false, Av1PartitionType.None); + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.frameBuffer, default), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -915,6 +869,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; } + Span currentDeltaLoopFilter = partitionInfo.SuperblockInfo.SuperblockDeltaLoopFilter; for (int i = 0; i < frameLoopFilterCount; i++) { int deltaLoopFilterAbsolute = reader.ReadDeltaLoopFilterAbsolute(); @@ -929,7 +884,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p { bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - this.deltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, this.deltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); + currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); } } } From e64d74d09783f16c94b77ac607067e1770ebb64e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 8 Jun 2024 11:18:21 +0200 Subject: [PATCH 070/190] Simplify IAv1TileDecoder --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 40 ++++++------ .../Formats/Heif/Av1/IAv1TileDecoder.cs | 31 +--------- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 62 +++++++++++-------- .../{ObuTileInfo.cs => ObuTileGroupHeader.cs} | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 16 +++-- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 20 +++--- .../Formats/Heif/Av1/Av1TileDecoderStub.cs | 15 +---- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 28 +++++---- 9 files changed, 97 insertions(+), 119 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/{ObuTileInfo.cs => ObuTileGroupHeader.cs} (96%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 1fd5d3c2f2..b21696c64c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -8,38 +8,36 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal class Av1Decoder : IAv1TileDecoder { - private readonly Av1TileDecoder tileDecoder; + private readonly ObuReader obuReader; + private Av1TileDecoder? tileDecoder; + private Av1FrameBuffer? frameBuffer; - public Av1Decoder() - { - this.FrameInfo = new ObuFrameHeader(); - this.SequenceHeader = new ObuSequenceHeader(); - this.TileInfo = new ObuTileInfo(); - this.SeenFrameHeader = false; - this.tileDecoder = new Av1TileDecoder(this.SequenceHeader, this.FrameInfo, this.TileInfo); - } - - public bool SequenceHeaderDone { get; set; } - - public bool ShowExistingFrame { get; set; } + public Av1Decoder() => this.obuReader = new(); - public bool SeenFrameHeader { get; set; } + public ObuFrameHeader? FrameHeader { get; private set; } - public ObuFrameHeader FrameInfo { get; } + public ObuSequenceHeader? SequenceHeader { get; private set; } - public ObuSequenceHeader SequenceHeader { get; } - - public ObuTileInfo TileInfo { get; } + public ObuTileGroupHeader? TilesHeader { get; private set; } public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); - ObuReader.Read(ref reader, buffer.Length, this, false); + this.obuReader.Read(ref reader, buffer.Length, this, false); + this.frameBuffer = this.tileDecoder?.FrameBuffer; + } + + public void StartDecodeTiles() + { + this.SequenceHeader = this.obuReader.SequenceHeader; + this.FrameHeader = this.obuReader.FrameHeader; + this.TilesHeader = this.obuReader.TileGroupHeader; + this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!, this.TilesHeader!); } public void DecodeTile(Span tileData, int tileNum) - => this.tileDecoder.DecodeTile(tileData, tileNum); + => this.tileDecoder!.DecodeTile(tileData, tileNum); public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) - => this.tileDecoder.FinishDecodeTiles(doCdef, doLoopRestoration); + => this.tileDecoder!.FinishDecodeTiles(doCdef, doLoopRestoration); } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs index c7aa943f8d..daac2ef1d9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// @@ -11,34 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal interface IAv1TileDecoder { /// - /// Gets or sets a value indicating whether a sequence header has been read. - /// - bool SequenceHeaderDone { get; set; } - - /// - /// Gets or sets a value indicating whether to show the existing frame. - /// - bool ShowExistingFrame { get; set; } - - /// - /// Gets or sets a value indicating whether a FrameHeader has just been read. - /// - bool SeenFrameHeader { get; set; } - - /// - /// Gets Information about the frame. - /// - ObuFrameHeader FrameInfo { get; } - - /// - /// Gets Information about the sequence of frames. - /// - ObuSequenceHeader SequenceHeader { get; } - - /// - /// Gets information required to decode the tiles of a frame. + /// Start decoding all tiles of a frame. /// - ObuTileInfo TileInfo { get; } + void StartDecodeTiles(); /// /// Decode a single tile. diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index f3f7e957dd..ffe384e688 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -15,7 +15,7 @@ internal class ObuFrameHeader public bool AllowHighPrecisionMotionVector { get; set; } - public ObuTileInfo TilesInfo { get; set; } = new ObuTileInfo(); + public ObuTileGroupHeader TilesInfo { get; set; } = new ObuTileGroupHeader(); public bool CodedLossless { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 1a90eaebfc..889fe51f0d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -7,11 +7,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuReader { + public ObuSequenceHeader? SequenceHeader { get; set; } + + public ObuFrameHeader? FrameHeader { get; set; } + + public ObuTileGroupHeader? TileGroupHeader { get; set; } + /// /// Decode all OBU's in a frame. /// - public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) + public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) { + bool seenFrameHeader = false; bool frameDecodingFinished = false; while (!frameDecodingFinished) { @@ -40,34 +47,35 @@ public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDec switch (header.Type) { case ObuType.SequenceHeader: - ReadSequenceHeader(ref reader, decoder.SequenceHeader); - if (decoder.SequenceHeader.ColorConfig.BitDepth == 12) + this.SequenceHeader = new(); + ReadSequenceHeader(ref reader, this.SequenceHeader); + if (this.SequenceHeader.ColorConfig.BitDepth == 12) { // TODO: Initialize 12 bit predictors } - decoder.SequenceHeaderDone = true; break; case ObuType.FrameHeader: case ObuType.RedundantFrameHeader: case ObuType.Frame: if (header.Type != ObuType.Frame) { - decoder.ShowExistingFrame = false; + // Nothing to do here. } else if (header.Type != ObuType.FrameHeader) { - Guard.IsFalse(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Frame header expected"); + Guard.IsFalse(seenFrameHeader, nameof(seenFrameHeader), "Frame header expected"); } else { - Guard.IsTrue(decoder.SeenFrameHeader, nameof(Av1Decoder.SeenFrameHeader), "Already decoded a frame header"); + Guard.IsTrue(seenFrameHeader, nameof(seenFrameHeader), "Already decoded a frame header"); } - if (!decoder.SeenFrameHeader) + if (!seenFrameHeader) { - decoder.SeenFrameHeader = true; - ReadFrameHeader(ref reader, decoder, header, header.Type != ObuType.Frame); + seenFrameHeader = true; + this.FrameHeader = new(); + this.ReadFrameHeader(ref reader, header, header.Type != ObuType.Frame); } if (header.Type != ObuType.Frame) @@ -78,15 +86,16 @@ public static void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDec goto TILE_GROUP; case ObuType.TileGroup: TILE_GROUP: - if (!decoder.SeenFrameHeader) + if (!seenFrameHeader) { throw new InvalidImageContentException("Corrupt frame"); } - ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); + this.TileGroupHeader = new(); + this.ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); if (frameDecodingFinished) { - decoder.SeenFrameHeader = false; + seenFrameHeader = false; } break; @@ -465,9 +474,9 @@ private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHead ComputeImageSize(sequenceHeader, frameInfo); } - private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { - ObuTileInfo tileInfo = new(); + ObuTileGroupHeader tileInfo = new(); int superBlockColumnCount; int superBlockRowCount; int superBlockShift; @@ -633,10 +642,10 @@ private static ObuTileInfo ReadTileInfo(ref Av1BitStreamReader reader, ObuSequen return tileInfo; } - private static void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, int planesCount) + private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, int planesCount) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; int idLength = 0; uint previousFrameId = 0; bool isIntraFrame = false; @@ -915,13 +924,11 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static void ReadFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, bool trailingBit) + private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; - int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + int planeCount = this.SequenceHeader!.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = reader.BitPosition; - ReadUncompressedFrameHeader(ref reader, decoder, header, planeCount); + this.ReadUncompressedFrameHeader(ref reader, header, planeCount); if (trailingBit) { ReadTrailingBits(ref reader); @@ -934,11 +941,12 @@ private static void ReadFrameHeader(ref Av1BitStreamReader reader, IAv1TileDecod header.PayloadSize -= headerBytes; } - private static void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) + private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; - ObuTileInfo tileInfo = decoder.TileInfo; + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; + ObuTileGroupHeader tileInfo = this.TileGroupHeader!; + this.TileGroupHeader = tileInfo; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; bool tileStartAndEndPresentFlag = false; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs similarity index 96% rename from src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs rename to src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs index b995713d8a..848fef55b6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -internal class ObuTileInfo +internal class ObuTileGroupHeader { internal int MaxTileWidthSuperBlock { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 00346743f8..e9847ffea0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -10,23 +10,23 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public static void Write(Stream stream, IAv1TileDecoder decoder) + public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); - WriteSequenceHeader(ref writer, decoder.SequenceHeader); + WriteSequenceHeader(ref writer, sequenceHeader); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; - WriteFrameHeader(ref writer, decoder, true); + WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; - WriteTileGroup(ref writer, decoder.TileInfo); + WriteTileGroup(ref writer, tileInfo); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); } @@ -218,7 +218,7 @@ private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHea WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); } - private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) + private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { int superBlockColumnCount; int superBlockRowCount; @@ -444,10 +444,8 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecoder decoder, bool writeTrailingBits) + private static int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) { - ObuSequenceHeader sequenceHeader = decoder.SequenceHeader; - ObuFrameHeader frameInfo = decoder.FrameInfo; int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = writer.BitPosition; WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); @@ -463,7 +461,7 @@ private static int WriteFrameHeader(ref Av1BitStreamWriter writer, IAv1TileDecod return headerBytes; } - private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileInfo tileInfo) + private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo) { int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = writer.BitPosition; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index da734fb133..d283906a0f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -29,16 +29,15 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; - private readonly Av1FrameBuffer frameBuffer; - public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileInfo tileInfo) + public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; this.TileInfo = tileInfo; // init_main_frame_ctxt - this.frameBuffer = new(this.SequenceHeader); + this.FrameBuffer = new(this.SequenceHeader); } public bool SequenceHeaderDone { get; set; } @@ -51,7 +50,9 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo public ObuSequenceHeader SequenceHeader { get; } - public ObuTileInfo TileInfo { get; } + public ObuTileGroupHeader TileInfo { get; } + + public Av1FrameBuffer FrameBuffer { get; } public void DecodeTile(Span tileData, int tileNum) { @@ -93,7 +94,7 @@ public void DecodeTile(Span tileData, int tileNum) this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); Point superblockPosition = new Point(superBlockColumn, superBlockRow); - Av1SuperblockInfo superblockInfo = new(this.frameBuffer, superblockPosition); + Av1SuperblockInfo superblockInfo = new(this.FrameBuffer, superblockPosition); // Nothing to do for CDEF // this.ClearCdef(row, column); @@ -104,7 +105,7 @@ public void DecodeTile(Span tileData, int tileNum) } private void ClearLoopFilterDelta() - => this.frameBuffer.ClearDeltaLoopFilter(); + => this.FrameBuffer.ClearDeltaLoopFilter(); private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { @@ -152,6 +153,11 @@ private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSiz } } + public void StartDecodeTiles() + { + // TODO: Implement + } + public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) { // TODO: Implement @@ -402,7 +408,7 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.frameBuffer, default), false, Av1PartitionType.None); + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.FrameBuffer, default), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index 9f0dbc2844..b3346e48a0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -2,23 +2,14 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; internal class Av1TileDecoderStub : IAv1TileDecoder { - public bool SequenceHeaderDone { get; set; } - - public bool ShowExistingFrame { get; set; } - - public bool SeenFrameHeader { get; set; } - - public ObuFrameHeader FrameInfo { get; } = new ObuFrameHeader(); - - public ObuSequenceHeader SequenceHeader { get; } = new ObuSequenceHeader(); - - public ObuTileInfo TileInfo { get; } = new ObuTileInfo(); + public void StartDecodeTiles() + { + } public void DecodeTile(Span tileData, int tileNum) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 89b42535f4..c24eac5555 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -5,7 +5,6 @@ using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -22,14 +21,17 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); - Av1Decoder decoder = new(); + Av1BitStreamReader reader = new(span); + IAv1TileDecoder decoder = new Av1TileDecoderStub(); + ObuReader obuReader = new(); // Act - decoder.Decode(span); + obuReader.Read(ref reader, blockSize, decoder); // Assert - Assert.True(decoder.SequenceHeaderDone); - Assert.False(decoder.SeenFrameHeader); + Assert.NotNull(obuReader.SequenceHeader); + Assert.NotNull(obuReader.FrameHeader); + Assert.NotNull(obuReader.TileGroupHeader); } /* [Theory] @@ -67,29 +69,31 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b Span span = content.AsSpan(fileOffset, blockSize); IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); Av1BitStreamReader reader = new(span); + ObuReader obuReader1 = new(); // Act 1 - ObuReader.Read(ref reader, blockSize, tileDecoder); + obuReader1.Read(ref reader, blockSize, tileDecoder); // Assign 2 MemoryStream encoded = new(); // Act 2 - ObuWriter.Write(encoded, tileDecoder); + ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, obuReader1.TileGroupHeader); // Assign 2 Span encodedBuffer = encoded.ToArray(); IAv1TileDecoder tileDecoder2 = new Av1TileDecoderStub(); Av1BitStreamReader reader2 = new(span); + ObuReader obuReader2 = new(); // Act 2 - ObuReader.Read(ref reader2, encodedBuffer.Length, tileDecoder2); + obuReader2.Read(ref reader2, encodedBuffer.Length, tileDecoder2); // Assert - Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader.ColorConfig), PrettyPrintProperties(tileDecoder2.SequenceHeader.ColorConfig)); - Assert.Equal(PrettyPrintProperties(tileDecoder.SequenceHeader), PrettyPrintProperties(tileDecoder2.SequenceHeader)); - Assert.Equal(PrettyPrintProperties(tileDecoder.FrameInfo), PrettyPrintProperties(tileDecoder2.FrameInfo)); - Assert.Equal(PrettyPrintProperties(tileDecoder.TileInfo), PrettyPrintProperties(tileDecoder2.TileInfo)); + Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); + Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader), PrettyPrintProperties(obuReader2.SequenceHeader)); + Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader), PrettyPrintProperties(obuReader2.FrameHeader)); + Assert.Equal(PrettyPrintProperties(obuReader1.TileGroupHeader), PrettyPrintProperties(obuReader2.TileGroupHeader)); } private static string PrettyPrintProperties(object obj) From 60869e1f47c6dd4ce5c60551a16fda76d020c826 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 9 Jun 2024 16:56:35 +0200 Subject: [PATCH 071/190] Bug fixes in OBU parsing --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 2 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 5 +- .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 2 - .../ObuDeltaLoopFilterParameters.cs | 2 +- .../ObuLoopFilterParameters.cs | 4 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 122 ++++++++++++------ .../ObuSegmentationParameters.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 15 ++- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 37 +++--- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 6 +- 10 files changed, 121 insertions(+), 76 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index f47ca720ea..4dbbfe56e0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -49,7 +49,7 @@ public void Skip(int bitCount) public uint ReadLiteral(int bitCount) { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); uint bits = (this.currentWord << this.bitOffset) >> (WordSize - bitCount); this.bitOffset += bitCount; diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index b21696c64c..8824c957a0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -18,8 +18,6 @@ internal class Av1Decoder : IAv1TileDecoder public ObuSequenceHeader? SequenceHeader { get; private set; } - public ObuTileGroupHeader? TilesHeader { get; private set; } - public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); @@ -31,8 +29,7 @@ public void StartDecodeTiles() { this.SequenceHeader = this.obuReader.SequenceHeader; this.FrameHeader = this.obuReader.FrameHeader; - this.TilesHeader = this.obuReader.TileGroupHeader; - this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!, this.TilesHeader!); + this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!); } public void DecodeTile(Span tileData, int tileNum) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index 7ad04f6bba..83dddd2fe4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -29,8 +29,6 @@ internal class ObuColorConfig public int BitDepth { get; set; } - public bool HasSeparateUvDeltaQ { get; set; } - public Av1ColorFormat GetColorFormat() { Av1ColorFormat format = Av1ColorFormat.Yuv400; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs index 76450d968c..da39e19f3c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs @@ -9,5 +9,5 @@ internal class ObuDeltaLoopFilterParameters public int Resolution { get; internal set; } - public bool Multi { get; internal set; } + public bool IsMulti { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs index 51e11610f6..12e5197138 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -6,4 +6,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopFilterParameters { public int[] FilterLevel { get; internal set; } = new int[2]; + public int FilterLevelU { get; internal set; } + public int FilterLevelV { get; internal set; } + public int SharpnessLevel { get; internal set; } + public bool ReferenceDeltaModeEnabled { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 889fe51f0d..91c14a7f33 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -11,8 +12,6 @@ internal class ObuReader public ObuFrameHeader? FrameHeader { get; set; } - public ObuTileGroupHeader? TileGroupHeader { get; set; } - /// /// Decode all OBU's in a frame. /// @@ -91,7 +90,6 @@ public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder de throw new InvalidImageContentException("Corrupt frame"); } - this.TileGroupHeader = new(); this.ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); if (frameDecodingFinished) { @@ -202,8 +200,9 @@ private static void AlignToByteBoundary(ref Av1BitStreamReader reader) } } - private static void ComputeImageSize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private void ComputeImageSize(ObuSequenceHeader sequenceHeader) { + ObuFrameHeader frameInfo = this.FrameHeader!; frameInfo.ModeInfoColumnCount = 2 * ((frameInfo.FrameSize.FrameWidth + 7) >> 3); frameInfo.ModeInfoRowCount = 2 * ((frameInfo.FrameSize.FrameHeight + 7) >> 3); frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2; @@ -366,7 +365,7 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu } } - colorConfig.HasSeparateUvDeltaQ = reader.ReadBoolean(); + colorConfig.HasSeparateUvDelta = reader.ReadBoolean(); return colorConfig; } @@ -387,8 +386,10 @@ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig c } } - private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) { + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; bool useSuperResolution = false; if (sequenceHeader.EnableSuperResolution) { @@ -417,8 +418,9 @@ private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, } } - private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private void ReadRenderSize(ref Av1BitStreamReader reader) { + ObuFrameHeader frameInfo = this.FrameHeader!; bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); if (renderSizeAndFrameSizeDifferent) { @@ -432,8 +434,10 @@ private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader } } - private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag) { + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; bool foundReference = false; for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) { @@ -447,18 +451,20 @@ private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, O if (!foundReference) { - ReadFrameSize(ref reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); - ReadRenderSize(ref reader, frameInfo); + this.ReadFrameSize(ref reader, frameSizeOverrideFlag); + this.ReadRenderSize(ref reader); } else { - ReadSuperResolutionParameters(ref reader, sequenceHeader, frameInfo); - ComputeImageSize(sequenceHeader, frameInfo); + this.ReadSuperResolutionParameters(ref reader); + this.ComputeImageSize(sequenceHeader); } } - private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag) { + ObuSequenceHeader sequenceHeader = this.SequenceHeader!; + ObuFrameHeader frameInfo = this.FrameHeader!; if (frameSizeOverrideFlag) { frameInfo.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; @@ -470,8 +476,8 @@ private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHead frameInfo.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; } - ReadSuperResolutionParameters(ref reader, sequenceHeader, frameInfo); - ComputeImageSize(sequenceHeader, frameInfo); + this.ReadSuperResolutionParameters(ref reader); + this.ComputeImageSize(sequenceHeader); } private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) @@ -670,11 +676,8 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade { frameInfo.ReferenceValid = new bool[Av1Constants.ReferenceFrameCount]; frameInfo.ReferenceOrderHint = new bool[Av1Constants.ReferenceFrameCount]; - for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) - { - frameInfo.ReferenceValid[i] = false; - frameInfo.ReferenceOrderHint[i] = false; - } + Array.Fill(frameInfo.ReferenceValid, false); + Array.Fill(frameInfo.ReferenceOrderHint, false); } frameInfo.DisableCdfUpdate = reader.ReadBoolean(); @@ -761,7 +764,14 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameSizeOverrideFlag = reader.ReadBoolean(); } - frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + if (sequenceHeader.OrderHintInfo.OrderHintBits > 0) + { + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + } + else + { + frameInfo.OrderHint = 0; + } if (isIntraFrame || frameInfo.ErrorResilientMode) { @@ -807,8 +817,8 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade if (isIntraFrame) { - ReadFrameSize(ref reader, sequenceHeader, frameInfo, frameSizeOverrideFlag); - ReadRenderSize(ref reader, frameInfo); + this.ReadFrameSize(ref reader, frameSizeOverrideFlag); + this.ReadRenderSize(ref reader); if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) { if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) @@ -852,6 +862,9 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; frameInfo.CodedLossless = true; + frameInfo.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount]; + frameInfo.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount]; + frameInfo.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount]; for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) { int qIndex = GetQIndex(frameInfo.SegmentationParameters, segmentId, frameInfo.QuantizationParameters.BaseQIndex); @@ -871,19 +884,24 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade { if (frameInfo.LosslessArray[segmentId]) { - frameInfo.SegmentationParameters.QMLevel[0, segmentId] = 15; - frameInfo.SegmentationParameters.QMLevel[1, segmentId] = 15; - frameInfo.SegmentationParameters.QMLevel[2, segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[0][segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[1][segmentId] = 15; + frameInfo.SegmentationParameters.QMLevel[2][segmentId] = 15; } else { - frameInfo.SegmentationParameters.QMLevel[0, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; - frameInfo.SegmentationParameters.QMLevel[1, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.U]; - frameInfo.SegmentationParameters.QMLevel[2, segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.V]; + frameInfo.SegmentationParameters.QMLevel[0][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; + frameInfo.SegmentationParameters.QMLevel[1][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.U]; + frameInfo.SegmentationParameters.QMLevel[2][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.V]; } } } + if (frameInfo.CodedLossless) + { + DebugGuard.IsFalse(frameInfo.DeltaQParameters.IsPresent, nameof(frameInfo.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame."); + } + frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; ReadLoopFilterParameters(ref reader, sequenceHeader, frameInfo, planesCount); ReadCdefParameters(ref reader, sequenceHeader, frameInfo, planesCount); @@ -896,6 +914,10 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade { frameInfo.AllowWarpedMotion = false; } + else + { + frameInfo.AllowWarpedMotion = reader.ReadBoolean(); + } frameInfo.ReducedTransformSet = reader.ReadBoolean(); ReadGlobalMotionParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame); @@ -945,8 +967,7 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decode { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuFrameHeader frameInfo = this.FrameHeader!; - ObuTileGroupHeader tileInfo = this.TileGroupHeader!; - this.TileGroupHeader = tileInfo; + ObuTileGroupHeader tileInfo = this.FrameHeader!.TilesInfo; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; bool tileStartAndEndPresentFlag = false; @@ -1013,7 +1034,7 @@ private static int ReadDeltaQ(ref Av1BitStreamReader reader) int deltaQ = 0; if (reader.ReadBoolean()) { - deltaQ = reader.ReadSignedFromUnsigned(6); + deltaQ = reader.ReadSignedFromUnsigned(7); } return deltaQ; @@ -1038,7 +1059,7 @@ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader re { frameInfo.DeltaLoopFilterParameters.IsPresent = false; frameInfo.DeltaLoopFilterParameters.Resolution = 0; - frameInfo.DeltaLoopFilterParameters.Multi = false; + frameInfo.DeltaLoopFilterParameters.IsMulti = false; if (frameInfo.DeltaQParameters.IsPresent) { if (!frameInfo.AllowIntraBlockCopy) @@ -1049,7 +1070,7 @@ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader re if (frameInfo.DeltaLoopFilterParameters.IsPresent) { frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2); - frameInfo.DeltaLoopFilterParameters.Multi = reader.ReadBoolean(); + frameInfo.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean(); } } } @@ -1062,6 +1083,11 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob if (planesCount > 1) { bool areUvDeltaDifferent = false; + if (colorInfo.HasSeparateUvDelta) + { + areUvDeltaDifferent = reader.ReadBoolean(); + } + quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); if (areUvDeltaDifferent) @@ -1088,7 +1114,7 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob { quantParams.QMatrix[(int)Av1Plane.Y] = (int)reader.ReadLiteral(4); quantParams.QMatrix[(int)Av1Plane.U] = (int)reader.ReadLiteral(4); - if (!colorInfo.HasSeparateUvDeltaQ) + if (!colorInfo.HasSeparateUvDelta) { quantParams.QMatrix[(int)Av1Plane.V] = quantParams.QMatrix[(int)Av1Plane.U]; } @@ -1115,14 +1141,31 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { + frameInfo.LoopFilterParameters.FilterLevel = new int[2]; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) { - frameInfo.LoopFilterParameters.FilterLevel[0] = 0; - frameInfo.LoopFilterParameters.FilterLevel[1] = 0; return; } - // TODO: Parse more stuff. + frameInfo.LoopFilterParameters.FilterLevel[0] = (int)reader.ReadLiteral(6); + frameInfo.LoopFilterParameters.FilterLevel[1] = (int)reader.ReadLiteral(6); + + if (planesCount > 1) + { + if (frameInfo.LoopFilterParameters.FilterLevel[0] > 0 || frameInfo.LoopFilterParameters.FilterLevel[1] > 0) + { + frameInfo.LoopFilterParameters.FilterLevelU = (int)reader.ReadLiteral(6); + frameInfo.LoopFilterParameters.FilterLevelV = (int)reader.ReadLiteral(6); + } + } + + frameInfo.LoopFilterParameters.SharpnessLevel = (int)reader.ReadLiteral(3); + frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled = reader.ReadBoolean(); + if (frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled) + { + // TODO: Implement. + throw new NotImplementedException(); + } } private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) @@ -1238,6 +1281,7 @@ private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, Ob } // Not applicable for INTRA frames. + throw new NotImplementedException(); } private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame) @@ -1286,7 +1330,7 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt } // TODO: Implement parsing. - return grainParams; + throw new NotImplementedException(); } private static bool IsValidSequenceLevel(int sequenceLevelIndex) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index 64624e8535..07d4a6cd8d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuSegmentationParameters { - public int[,] QMLevel { get; internal set; } = new int[3, Av1Constants.MaxSegmentCount]; + public int[][] QMLevel { get; internal set; } = new int[3][]; public bool[,] FeatureEnabled { get; internal set; } = new bool[Av1Constants.MaxSegmentCount, Av1Constants.SegmentationLevelMax]; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index e9847ffea0..226116c797 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -10,7 +10,7 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) + public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); @@ -26,7 +26,7 @@ public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFra WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); bufferStream.Position = 0; - WriteTileGroup(ref writer, tileInfo); + WriteTileGroup(ref writer, frameInfo.TilesInfo); writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); } @@ -145,7 +145,7 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH } } - writer.WriteBoolean(colorConfig.HasSeparateUvDeltaQ); + writer.WriteBoolean(colorConfig.HasSeparateUvDelta); } private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) @@ -520,7 +520,7 @@ private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter w if (frameInfo.DeltaLoopFilterParameters.IsPresent) { writer.WriteLiteral((uint)frameInfo.DeltaLoopFilterParameters.Resolution, 2); - writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.Multi); + writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.IsMulti); } } } @@ -535,6 +535,11 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O if (planesCount > 1) { bool areUvDeltaDifferent = false; + if (colorInfo.HasSeparateUvDelta) + { + writer.WriteBoolean(colorInfo.HasSeparateUvDelta); + } + WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]); WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]); if (areUvDeltaDifferent) @@ -549,7 +554,7 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O { writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4); writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4); - if (colorInfo.HasSeparateUvDeltaQ) + if (colorInfo.HasSeparateUvDelta) { writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index d283906a0f..a1edf35570 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -30,11 +30,10 @@ internal class Av1TileDecoder : IAv1TileDecoder private int deltaLoopFilterResolution = -1; private int deltaQuantizerResolution = -1; - public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) + public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; - this.TileInfo = tileInfo; // init_main_frame_ctxt this.FrameBuffer = new(this.SequenceHeader); @@ -50,17 +49,15 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo public ObuSequenceHeader SequenceHeader { get; } - public ObuTileGroupHeader TileInfo { get; } - public Av1FrameBuffer FrameBuffer { get; } public void DecodeTile(Span tileData, int tileNum) { Av1SymbolDecoder reader = new(tileData); - int tileColumnIndex = tileNum % this.TileInfo.TileColumnCount; - int tileRowIndex = tileNum / this.TileInfo.TileColumnCount; + int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; + int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; - this.aboveContext.Clear(this.TileInfo.TileColumnStartModeInfo[tileColumnIndex], this.TileInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); + this.aboveContext.Clear(this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex], this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; this.referenceSgrXqd = new int[planesCount][]; @@ -79,11 +76,11 @@ public void DecodeTile(Span tileData, int tileNum) Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; int superBlock4x4Size = superBlockSize.Get4x4WideCount(); - for (int row = this.TileInfo.TileRowStartModeInfo[tileRowIndex]; row < this.TileInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) + for (int row = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; row < this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) { int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; this.leftContext.Clear(); - for (int column = this.TileInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.TileInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) + for (int column = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) { int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -165,8 +162,8 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo) { - if (rowIndex >= this.TileInfo.TileRowStartModeInfo[rowIndex] || - columnIndex >= this.TileInfo.TileColumnStartModeInfo[columnIndex]) + if (rowIndex >= this.FrameInfo.TilesInfo.TileRowStartModeInfo[rowIndex] || + columnIndex >= this.FrameInfo.TilesInfo.TileColumnStartModeInfo[columnIndex]) { return; } @@ -339,8 +336,8 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); int txsWide = planeBlockSize.GetWidth() >> 2; int txsHigh = planeBlockSize.GetHeight() >> 2; - int aboveOffset = (partitionInfo.ColumnIndex - this.TileInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); - int leftOffset = (partitionInfo.RowIndex - this.TileInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); + int aboveOffset = (partitionInfo.ColumnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); + int leftOffset = (partitionInfo.RowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); int[] aboveContext = this.aboveContext.AboveContext[i + aboveOffset]; int[] leftContext = this.leftContext.LeftContext[i + leftOffset]; Array.Fill(aboveContext, 0); @@ -870,7 +867,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent) { int frameLoopFilterCount = 1; - if (this.FrameInfo.DeltaLoopFilterParameters.Multi) + if (this.FrameInfo.DeltaLoopFilterParameters.IsMulti) { frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; } @@ -942,10 +939,10 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn } private bool IsInside(int rowIndex, int columnIndex) => - columnIndex >= this.TileInfo.TileColumnCount && - columnIndex < this.TileInfo.TileColumnCount && - rowIndex >= this.TileInfo.TileRowCount && - rowIndex < this.TileInfo.TileRowCount; + columnIndex >= this.FrameInfo.TilesInfo.TileColumnCount && + columnIndex < this.FrameInfo.TilesInfo.TileColumnCount && + rowIndex >= this.FrameInfo.TilesInfo.TileRowCount && + rowIndex < this.FrameInfo.TilesInfo.TileRowCount; /* private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY) @@ -961,8 +958,8 @@ private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize bloc { // Maximum partition point is 8x8. Offset the log value occordingly. int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); - int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.TileInfo.TileColumnStartModeInfo[columnIndex]; - int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.TileInfo.TileRowStartModeInfo[rowIndex]; + int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[columnIndex]; + int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[rowIndex]; int above = (aboveCtx >> blockSizeLog) & 0x1; int left = (leftCtx >> blockSizeLog) & 0x1; return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index c24eac5555..2340fe427b 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -31,7 +31,7 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) // Assert Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); - Assert.NotNull(obuReader.TileGroupHeader); + Assert.NotNull(obuReader.FrameHeader.TilesInfo); } /* [Theory] @@ -78,7 +78,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b MemoryStream encoded = new(); // Act 2 - ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, obuReader1.TileGroupHeader); + ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader); // Assign 2 Span encodedBuffer = encoded.ToArray(); @@ -93,7 +93,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader), PrettyPrintProperties(obuReader2.SequenceHeader)); Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader), PrettyPrintProperties(obuReader2.FrameHeader)); - Assert.Equal(PrettyPrintProperties(obuReader1.TileGroupHeader), PrettyPrintProperties(obuReader2.TileGroupHeader)); + Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader.TilesInfo), PrettyPrintProperties(obuReader2.FrameHeader.TilesInfo)); } private static string PrettyPrintProperties(object obj) From d8e7078bb994fb197aca2d27d2468d930b9f2f54 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 23 Jun 2024 13:04:35 +0200 Subject: [PATCH 072/190] Implement reading AV1 Codec Configuration Record --- .../Formats/Heif/Av1/Av1CodecConfiguration.cs | 63 +++++++++++++++++++ .../Formats/Heif/HeifDecoderCore.cs | 6 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs new file mode 100644 index 0000000000..e0cef47753 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +/// +/// Implementation of section 2.3.3 of AV1 Codec ISO Media File Format Binding specification v1.2.0. +/// See https://aomediacodec.github.io/av1-isobmff/v1.2.0.html#av1codecconfigurationbox-syntax. +/// +internal struct Av1CodecConfiguration +{ + public Av1CodecConfiguration(Span boxBuffer) + { + Av1BitStreamReader reader = new(boxBuffer); + + this.Marker = (byte)reader.ReadLiteral(1); + this.Version = (byte)reader.ReadLiteral(7); + this.SeqProfile = (byte)reader.ReadLiteral(3); + this.SeqLevelIdx0 = (byte)reader.ReadLiteral(5); + this.SeqTier0 = (byte)reader.ReadLiteral(1); + this.HighBitdepth = (byte)reader.ReadLiteral(1); + this.TwelveBit = reader.ReadLiteral(1) == 1; + this.MonoChrome = reader.ReadLiteral(1) == 1; + this.ChromaSubsamplingX = reader.ReadLiteral(1) == 1; + this.ChromaSubsamplingY = reader.ReadLiteral(1) == 1; + this.ChromaSamplePosition = (byte)reader.ReadLiteral(2); + + // 3 bits are reserved. + reader.ReadLiteral(3); + + this.InitialPresentationDelayPresent = reader.ReadLiteral(1) == 1; + if (this.InitialPresentationDelayPresent) + { + byte initialPresentationDelayMinusOne = (byte)reader.ReadLiteral(4); + this.InitialPresentationDelay = (byte)(initialPresentationDelayMinusOne + 1); + } + } + + public byte Marker { get; } + + public byte Version { get; } + + public byte SeqProfile { get; } + + public byte SeqLevelIdx0 { get; } + + public byte SeqTier0 { get; } + + public byte HighBitdepth { get; } + + public bool TwelveBit { get; } + + public bool MonoChrome { get; } + + public bool ChromaSubsamplingX { get; } + + public bool ChromaSubsamplingY { get; } + + public byte ChromaSamplePosition { get; } + + public bool InitialPresentationDelayPresent { get; } + + public byte InitialPresentationDelay { get; } +} diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 7aeeac56b4..bfc397bf85 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -1,11 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Buffers; using System.Buffers.Binary; using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; @@ -35,6 +35,8 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals private readonly List itemLinks; + private Av1CodecConfiguration av1CodecConfiguration; + /// /// Initializes a new instance of the class. /// @@ -475,6 +477,8 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L case Heif4CharCode.Iscl: case Heif4CharCode.HvcC: case Heif4CharCode.Av1C: + this.av1CodecConfiguration = new(boxBuffer); + break; case Heif4CharCode.Rloc: case Heif4CharCode.Udes: // TODO: Implement From 84127fa8f1e3edc27f80775955cef21c1f6c0b3e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 23 Jun 2024 15:02:41 +0200 Subject: [PATCH 073/190] Fix mistake in switch case for parsing Heif4CharCode.Av1C --- src/ImageSharp/Formats/Heif/HeifDecoderCore.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index bfc397bf85..f87dd7f4d1 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -470,15 +470,15 @@ private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, L this.metadata.IccProfile = new IccProfile(iccData); } + break; + case Heif4CharCode.Av1C: + this.av1CodecConfiguration = new(boxBuffer); break; case Heif4CharCode.Altt: case Heif4CharCode.Imir: case Heif4CharCode.Irot: case Heif4CharCode.Iscl: case Heif4CharCode.HvcC: - case Heif4CharCode.Av1C: - this.av1CodecConfiguration = new(boxBuffer); - break; case Heif4CharCode.Rloc: case Heif4CharCode.Udes: // TODO: Implement From cb4f9ee890578cd1bbbfa23f4cac5b6e89d7fbda Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 24 Jun 2024 20:23:46 +0200 Subject: [PATCH 074/190] Add comments where the methods can be found in the spec --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 889fe51f0d..46479d77e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -5,6 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +/// +/// Reader for Open Bitstream Units (OBU's). +/// internal class ObuReader { public ObuSequenceHeader? SequenceHeader { get; set; } @@ -114,6 +117,9 @@ public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder de } } + /// + /// 5.3.2. OBU header syntax. + /// private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader) { ObuHeader header = new(); @@ -191,6 +197,9 @@ private static void ReadTrailingBits(ref Av1BitStreamReader reader) } } + /// + /// 5.3.5. Byte alignment syntax. + /// private static void AlignToByteBoundary(ref Av1BitStreamReader reader) { while ((reader.BitPosition & 0x7) > 0) @@ -217,6 +226,9 @@ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrame _ => false, }; + /// + /// 5.5.1. General sequence header OBU syntax. + /// private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); @@ -285,6 +297,9 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc ReadTrailingBits(ref reader); } + /// + /// 5.5.2. Color config syntax. + /// private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { ObuColorConfig colorConfig = new(); @@ -387,6 +402,9 @@ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig c } } + /// + /// 5.9.8. Superres params syntax. + /// private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { bool useSuperResolution = false; @@ -417,6 +435,9 @@ private static void ReadSuperResolutionParameters(ref Av1BitStreamReader reader, } } + /// + /// 5.9.6. Render size syntax. + /// private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); @@ -432,6 +453,9 @@ private static void ReadRenderSize(ref Av1BitStreamReader reader, ObuFrameHeader } } + /// + /// 5.9.7. Frame size with refs syntax. + /// private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { bool foundReference = false; @@ -457,6 +481,9 @@ private static void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, O } } + /// + /// 5.9.5. Frame size syntax. + /// private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) { if (frameSizeOverrideFlag) @@ -474,6 +501,9 @@ private static void ReadFrameSize(ref Av1BitStreamReader reader, ObuSequenceHead ComputeImageSize(sequenceHeader, frameInfo); } + /// + /// 5.9.15. Tile info syntax. + /// private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { ObuTileGroupHeader tileInfo = new(); @@ -642,6 +672,9 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob return tileInfo; } + /// + /// 5.9.2. Uncompressed header syntax. + /// private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, int planesCount) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; @@ -924,6 +957,9 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } + /// + /// 5.9.1. General frame header OBU syntax. + /// private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { int planeCount = this.SequenceHeader!.ColorConfig.IsMonochrome ? 1 : 3; @@ -941,6 +977,9 @@ private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bo header.PayloadSize -= headerBytes; } + /// + /// 5.11.1. General tile group OBU syntax. + /// private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; @@ -1008,6 +1047,9 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decode decoder.FinishDecodeTiles(doCdef, doLoopRestoration); } + /// + /// 5.9.13. Delta quantizer syntax. + /// private static int ReadDeltaQ(ref Av1BitStreamReader reader) { int deltaQ = 0; @@ -1019,6 +1061,9 @@ private static int ReadDeltaQ(ref Av1BitStreamReader reader) return deltaQ; } + /// + /// 5.9.17. Quantizer index delta parameters syntax. + /// private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { frameInfo.DeltaQParameters.Resolution = 0; @@ -1034,6 +1079,9 @@ private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, Obu } } + /// + /// 5.9.18. Loop filter delta parameters syntax. + /// private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { frameInfo.DeltaLoopFilterParameters.IsPresent = false; @@ -1054,6 +1102,9 @@ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader re } } + /// + /// 5.9.12. Quantization params syntax. + /// private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) { quantParams.BaseQIndex = (int)reader.ReadLiteral(8); @@ -1105,6 +1156,9 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob } } + /// + /// 5.9.14. Segmentation params syntax. + /// private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); @@ -1113,6 +1167,9 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob // TODO: Parse more stuff. } + /// + /// 5.9.11. Loop filter params syntax + /// private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) @@ -1125,6 +1182,9 @@ private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuS // TODO: Parse more stuff. } + /// + /// 5.9.21. TX mode syntax. + /// private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { if (frameInfo.CodedLossless) @@ -1145,7 +1205,7 @@ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHea } /// - /// See section 5.9.20. + /// See section 5.9.20. Loop restoration params syntax. /// private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { @@ -1201,7 +1261,7 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, } /// - /// See section 5.9.19. + /// See section 5.9.19. CDEF params syntax. /// private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { @@ -1230,6 +1290,9 @@ private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenc } } + /// + /// 5.9.24. Global motion params syntax. + /// private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) { if (isIntraFrame) @@ -1240,6 +1303,9 @@ private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, Ob // Not applicable for INTRA frames. } + /// + /// 5.9.23. Frame reference mode syntax + /// private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame) { if (isIntraFrame) @@ -1250,6 +1316,9 @@ private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader re return (ObuReferenceMode)reader.ReadLiteral(1); } + /// + /// 5.11.10. Skip mode syntax. + /// private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame, ObuReferenceMode referenceSelect) { if (isIntraFrame || referenceSelect == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) @@ -1271,6 +1340,9 @@ private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSeq } } + /// + /// 5.9.30. Film grain params syntax. + /// private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { ObuFilmGrainParameters grainParams = new(); From aa44d0c93b93232358437c7084e32983cf201f4e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 24 Jun 2024 20:24:29 +0200 Subject: [PATCH 075/190] Add av1 spec file --- src/ImageSharp/Formats/Heif/Av1/av1-spec.pdf | Bin 0 -> 3824058 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/av1-spec.pdf diff --git a/src/ImageSharp/Formats/Heif/Av1/av1-spec.pdf b/src/ImageSharp/Formats/Heif/Av1/av1-spec.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7b634be6291a02b090c67066bef477a2d510d465 GIT binary patch literal 3824058 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%0DRLU!OGR#tm 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
rCQJ7 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

8;^1vq`oN1;PE` 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
-rf=-RX%+oA{!{C6+6?;C5R0;lzaUWaf-xC6vNWdCV9z$9 z__?c zmT;ywJG5bH0POj}|Ho_ilN7S7k7P3RDwESG_FaCCS2U>I@tl~sOoju8d+Xe_`dc@1 zM6uCa&}zV5Cr&dW?{}Qgkk8`sdA_uT7u}+jk}=*qu1wq1+}nmM5Z<$PoCDp?v}vt- z+1i_-OcnAM$8qy#>s{I@O7GG-H$sJIq?tiW7b@{mATg%1cX*LU*f)fKlmDnK>46l= z{Jq~GdFL(RC;)Cv(d(tWxSIG_qho|lhY@u(pXzxuyXAw{2PsF7AeY%f@bH7S#zKR` z28CEh=+uGgX<=hf)Ws3BQ<7wYf>Vv^XSQ_4IWrwHuw@i4%Y_Vnx7N4R1oC=deyzKA z>R!Q}xHg{-RV@6SJV=h{49`;zzN&z{ZC4%}}1i%N6j69ce2%-R;E5{tgHA_ zq?!4~L+Y0=pJJ!a$ynrPliFU*$pWQ1+fQgOIFGTsf4%3Il22;B?Dyuq4lIOb?k214 zs6~P};yZJg-w?UK{kKgI$&DW5BmLi+`L{)XRpFGbW{|6BsJZJ@x%2dk*M#!OUA=u3 zp}x}Vi)Fg?kT}Sn^gg)DE3B5XLaXCrXtv1WLI?QilvR-4kUkHh=f)bXA)yq7+cHME z4xDpm5t5E2m^S)9B9bB;+{C#o7!C47S%;u~XA~_eMdc)aWL)~M$01jqt^Qk8*iSXI zG<5weujA5#26o4;HT>#BTPUqq7;{QYodi|?U%4K(&w{;k`|~g(wOaVYde&gq$o;RDmw~Raq}(nkSH*ba_GnSYJ>^ZFz?CMi46EDk!X)%XiEc3S= ziw$_W*cjV;tb76gzyiKJf-5)6qS zav_F9NoMD6y&rQzKGiMPT)Yu$E3bYlOr+&YbLFY7NRp^#{?(N8=y8gfR9sY=-d4~ox-4zHScy$j^($9tm7}pp zRTrNdVH`B2J*&#zNYc_+lPM92R9SLInT;C!Anfb6J+1IHmbAQ-W;%)_ErqY@+eSQX z-5_asUKeF9QOF!bZA~Q+uIlF*G6#F%D}~HRbiHV^U5U8TX1wnTb!A3#M0{mcg`LoM z`Jwg;7d2f{+=d0bc~@U6^lfQ1X_DTqzTQ^$f7fvFKQHuc`6i)n%l};HyRyJLxUM~L zk4v|HzYSKN|78Dy+D)TehV9*(HgRZ=H4hJsaPRv>8bABQ=vD`px0UigX6+f}V*9Qr z^{id>*6jh!uiESl?9=O%4YA|v>N7KQMHlVb)lNa`mrp~I%52gDu1WrR$v3OLTSd2$A^KxqE3gdn=7y zTm$#{27D7cIAV}-ORH?k4>9(n^}W_FBs**GJxZ`3XS%&cg*n<5F|O$+Mbn)f6#7`a$9n)?UAx1_5)=}dz!K+ z@nJCxRaH1ph8E@XVH4Baf_l6U4JzuJpFH1mV3WPac5T73 z^HR1)8tuxdN3?9~t6NdA6AzqwxDs$0DgxXQh5mEn?^Jd*3BjA&qKt@oy28}WDRNRj z^uW;T>beagN2*Q@mDq<}pfwnh1eqnqfsFjjZ?;6inp*gpRE51t*D{OQGn ztiwG3W2HSOu8`uOi#1IJx1VMzsXo3pKe6=38^}v)g`i%C?l5zF_=P~I?0a}R^cd;( zMs!DFDBL-R(PLd9p2alc}+Op?-#n9TF7LrGph9S3j#fJw8`cX#YcE`o)T@q-tm9@cHO?TBBEK2kn8^ zo?9FgH-*-aJi&Y@TTm8qr9&_*>_c*+?d*yP6)Z!QE)4JGN;-SH>}uPuE-MX-Da?3k zd}1@u*s8|8isn0XetVv!s%KW_*DG}3bvMI-u(RN3nFlwu-doi<56AQphtX0?E{e0% zZn^2^^+WtG0XE7>PT``02=({r4=QpB0{a=$yhhnqUbS>>!|0}AfP|XXX zDObu(J5~`@RFs}(wgonaihS*J|I3-40G3U&mp5?E%<-)fcKx8! zRGwP8oizu9G1j-UJ-1!J*Te^~$Ci?q0oU{8xjZF&itJ4ue#x?L*fT<;We*dvpwf3V znhdzvdF70qV6309J}lHN6S>7A)*qZzw5ho~m3ejM1dOfFg^@y2t6ph3upr!%lmV%e z0;R}3T~hjl;!WQ)yY6a8d&=wx1nUi2C!&y=H9)OFX9*7Ce9_^8sg$HAwL~veTbJk9 zKa+NMCHZ@47D~_uVQ7`TeD={=J!ggjCCp3I-;dv(_ep5t%2p;r{DA-qB#dP`w%Smh zCeulwXwRN7&3+}pP*YyBq$dOuIFC9;>`kIXA^X^4_o5v*h{V`~vOdkTxB_MPz8&7| zz)Tt?y+IBxost~_v3dF(ubnhf7)9xnLj!uPAkcx0k=_zI!_KH>stKhJQHCi&g z;0$)tQFTroBWaIP9=aMXeBtQyx#OUIPf9EAbS>&NZM3fKg3Yld%H3fkQ2l|R0uvJT z|LhYgnMuO>AWER;2^cx+5qzk7wvM(!-Z}73NX{EY zr(9bXg%#G~7D5W2)Ln_`lr|A1*Ib)P#p&_$xUlua`KvX(DO!J{v1JXTTJzR963({5 z5Yll}$N4WSeyTa6%LE&}1(;Y|Qx~Qb{diSvb>m%XbQ5hLt2i!6W;41yI$E^Iin6vk z)lWSun^((T^JF;5pJ`OAaWvpvPM~) z-f&VKWD;=|U?&=yWl(OSa!ts z&^6^hGIq_O!xFt*xg$Sz=3+9NVXd1XpeJC{Idd}!(tAsdRwR8_M6T0s&_VUGn$GD7 z88z3$*AbmrLoSMoNplNzg`o=&AKARc81VA&HnhpCZTrAEIU2XrOm$DU7f=>B z@$<<)Q2xu?@`Hr;Gtn@09yLD+@ex^-)bQ51fsBry+2v90PC;67Q9+?gQOoJSaE8SC zlVRjsuTk3pw9o0E*CG`xnfAH(8n!Y5K9_5|u$kwaHm>+3?JL7;p;SO<&WdXJMa8KTZ>ylpNsZ$IyRb_N)yYjgBKxO6*yzdJegriN7bBp)$qg{#2w%tdy9= zinEU6&h0_`&;j}Nj_6C5{qXx9%K8ySs234M8bLK3RVnm6{*v%31s|S?W6kHiNM-q| zdlqDNScEeBh3<1L`ZH0ls=we+P;U>S1E2daZ7kk_HPA%X?kV&AVcgYN07DoxyxFla z*0Coid}JYdexvM7@wj8fg($#9!HO|d-@<>Oiz>ew(7~ZadV0F!?9?@27~TD zb3t-Jf;C%Mly%FeN9jmZX6!RzH}IL;3*N&)#pqP!xE)=)8PFx8Ywi6UN=G zT~S2yhT4ds>OgA<{azXVX{e2#Jq^-VzWtq#k6d=oH8`^%&2>Z~>`i8CW|l+S!?B{* zOEi&|EqVzSFW&aufDI{IhzI}b1;rU|Io5)MSy~f7 zhLSpT=(p&niMYN1E!9xrVF@h)KmP&J_5w;ASRatLw3<(#{08}K%c-iboEqQR|DLtG zusQvHFsd`c8t{stx21hJda4cVmuvmh?#Ha5;=nO&C}qy?0`m<75Wv~e7hK?@TiE{z z($+PKT@8AXUE6qtFoT@=IfV;sK9Fn2*mCw#zUf(WB&`5#Yr!zR`jMsRJ~+6 z?Z$J7KLXv#JZe6BZKaTy#QOMoG6ri;qKpP-Z-!PiXOuU8di{xUu6J}ho3|`y9>sdU zQ7phUtRrYM0>QOitj!I}5FMJ~_4NsQ2ai(A*Q!yp;b9y0C%N55i?7Mi;%#5!H+tZY z=`le=P>iJEMxVZJ^^du8_6{G~IM3gHp0vp^d|b%ua877m;ev0&hEG}J4y|BuNI?rK zYNW=x!4R7~!r%S;OtVYhu-CU_*+E%Ja=ETclVT`5pg*Oj`kQ=M2?oUuWG%>44goz{ zCa=RXJG`acq^FL>=>l9l+J$HgT-mF)^rp3bR)GdlvZ#*vWI-v*V&oOvgg{vjpGfhe zJ%I`66*@BUcGlfIM!fkkTYV$dqV>xZyx8}+h1n=9R7ap08-W&2teTB&j?T;dJ8=n` z95Y&-q}=8HN`}pC^JrbiFltf8>WJ|mUQi(puCZ`9p&+;@iEu;baug)=zQSGU&1uhn zx3k5ij(8qwDfFb@&T?+7j<_Kk^HR-9gXrIc8ldNM83{5vC;K%YYI1+9b$RP3RlTYbGPC)&R_@AUQX3X_60XXEW` zR@+WcEPEEStsprMA|~ZOn~-=)SeL+GjVe>U0}ap=V%6vUO#g%O+j+L6R3f`QJwQ;E z8nnrPq)iNJeuJcamglY&3c0}qd%M;eE8{YeDqj8SWqhqf^QtO0y|pzXN!H#h!+(5!vof#j ze8d&yi@P9xRfL4dC90QF0|SMBTj49sKkgeJ9QNj;C9S;u(&^;fxJuLX_!OfRR zR%6a8l#29jzh~A3TZof=?)$m?S#$2*FA8ypqek5J>UN>IXvO2I+z@loGGmb@GS>cj zOL>BoL9J2kJLj4_Q;mNe@2akpeh04L_*hN0tVnJ@NYqw;X`5L5JT_g}(E6+*B2%HP zt8a+#GBTE|TrD=MeZ0!=DtE9nJW^3BYpF5tDpM)#&1Gt(^rPyckL-jp=^(YdM5gW; zOPVKFL{o;ehsb|$e3@wQe2>wF&+6WOCSB(S_--%j)F-vB+h;LpfXqp8nUTaqy}NAQ zMjzlpD_d5$=IlBY37q2S?3{qLga~xW-BrMSLtr0eDVhs-dU18lk8T0oksU|3#TE8oC$H#ic!R7n`iD4}4`GMBQMf&Xl} zXlE0oI-9AkjJj+-puWCE(>}T0-*3ye!bc*BN+!GBu1d(Vg4$7e{j1!ofy!+o=ASDm zUN-w-{^n8hExr%`^tZl;M}4W;6Zo$l;UAA$sERyqGJo@oglntqE*r1fVX}YqpphRk ztS5a~Z*{XPNZAhNv%q?so7|9fguB5(ci1a({r0zPfrmg9DPS*d#Afy?s~ja@GA0%& zD?k46 z(mPyU-IfameoB#h1e2gpzH@{`7PuEj7*as;jYw6%TGFUs+9~EEr_q=W%Uakw?(UX9 z>yI5ek+{*@_U@Gpl1E2x4HFIV5?CIc+t2qiwCrgy$6sn@`OoXei`a}2jOg;1sHz7> ze+p=-o%ayBiZs@e^lTWOAB0^JSx79}u)cL(GRVr?w5kigs(v7ilpO#5vw+wJqml!Q za>_A3TZHEI%a+4t{N6dJu9gAvzIOV^%-vr&PteR2!-%wy_|KTdCRGQlA-k zFWQ&S{u#{U^xuq3>Q{km4CXV$eHe#xvs-`NOM&)^o{W!Awu4C3#j4f)!68Tc_U`u+ zgH3wW8~qXkJ}o%gHpjOe{rE}l$D-g>zwIslf%asFlqkVsaS=ZJ1;MOP*zMM}%Q8=m)x<=yz|-lh*$%Y7MUSIjA| zl=?B~wY4MQ;_d>q)Jv;Jf>}_7Lt)^z6Rn0m1*a8~pTqYt&?<-9-uXC=-5f_lhixoh5UsV1IRW~%F}4>kN3zFx%EF!Gxxc6# z#tGs;qX+2y9@_~`{;VpSq&pCi@J?dClhZpf`Vz*&E?IRXI$dcEd7cb{KRcL^d+$xG zeftH=tItnfBpw<*>Q2Bh=-xcmQn-hi{xy|T9>SzN0|_ADetGD!m}!y-o5zrlfmRP_ zF!WcLa>%x9hop75767@?mE;x(Jr7Tv?)8?Zz%9AK)*KLKAc>t!xk=%1nmu^Ld9y`V za9d2)YOo%Oy?w!FOohek?Z>-<*TV-fBCwRnRY2mPkraTAly2|$K9KXC*L&v21&!`gEMri0Msz<7Bt%) zeDv*tgO7jnIvCbves0Jl`zGsS{{23B!<$%uHz8dwztWpI-!cg3Kq?{%5>ilEz<+?1 zyTwNIO@((1M}Xoy4>SjkFp;TQn={GgM}IkZp&smCda|YL`SX;7^4By&9{>1p&IhAr zr+gXL7_A}6gMlo%Ly?2w%(dD#Pa&~$r1W9XT_V7jI8Gpm1N=8VvnQ{Ei~e0^V1j$e zFTLfZ@Ofo#RZ8<%wqfVshpEkB=5;{rL~?4Ln~eShw(UO}{c zi0dHXaK0$@MdCrkaNi)t5hwaQcSoGG0Iu~qR3DZew2m2`oTNI&I;`FQ3yooiki`Dg zzECJzOV9K>HxE0BGjyzA%+krwylT!Epg%1)~!(Z@q@ z=!fGEo7GSUECvDP8j%i+Z%rq{hTUcxE3h;y+CLV#7m-ymv?j$9lQ*^MFokV=w zh6uAo4gn(m4~KZD74TrL&w{Bt%`d*x`Jri?p$oWohSJjxadr0tU-b%H z$Xaor~$lI#=V^EsEGr;5WFWz1+CA8cJw zg53V|vM!aaubjO}M?qSy|JL0vC`tp#zhy3a5#f~kBZ4K12Re2>q%E!!*BydqZcmsj zk9y)KlyD6d7$!VRBoSlwE*s9Ib;e@FFOe>J2g1oPIg1XHH^HF; z86NxI5$8Ej^wJ5M$h}Cal1r+jb2yEkwyMn(ItxN_&Wm2NcS!feLbG8Bq#Ga-#J_zm zR$|ym8v_oO$+s|V3=8qE0RzaD@=A~m3!dw8I)45YRyP_A%%nA_q7{R|{+JNA=D}(b z=bUoj!`knjmj+J3$SZ^P4%c&S1I1<8@0dXDN_}_&6GP&XIFwMp!$F&d%P$=t(ECn| zMKp!;rlCM)x9wdrbuOExo1G4QQA}_?o6BC2v=R$Sl1cQ`$|R21{k;9dsd0-xsC1fX zb#zWB?W^`Z3@hejDd>$tx=`;byylgqHHiZWm(p^IX;UE1b;MOp9D!ubBIYI+Y#hVS za!LvAj9QpxJZd&4&yzo(r)exMyT^I^ppRHqNM7@`7rj~CL#t^9?FF6glUCu2%#0-w zI#ILm$CNl5L`0+NuEV;a$&hRdiN>}er)rbQvp@(c?T5-kFjAe0o2MOQYQ*=?bf-l_ z*Z~U?!}A_UazA)B(%edx6+F%lpGK>d*!=44${-v1E4^x7F~dVpWXnw&$ff9qIgJeV_OXy?Ph%^Hh=NpQEg4+EXF4AXFBkuiyd2#Ko*z?- zE>GbFt0hjq?h8&3>wBhY7u|I(-UT}gO@VWj5p@{En zjjLO=iPNDMHf^+rd)iBn_;wazNQq{oE(q6seyOGX)VUhv}U9AFr{f)#VQLpwAJxpBi%U4DYLy0VR@ zS?#0!g0%+E$h!X~!HOImYawoQHy9(UMfT501d*yAlutP!zqBZW*}M>uUNpn2C*)7WTlYi3&s|WS6G0chy`ncKKt*|)zZJAvXrX{WMIi&q zSa1;7kEwuBj)E8#>EAE~KejIemCUInhf}W?AKo1$oL@ZMhqE!;V>GVwoiFSSC-t8{ zr{KL|hRfawB7NboD??#^e@oXosCqj%cLTZ%E;^TNN)Y!#$D)UnZ(DY{N zU!y%Ff(LnKvrJmNy)J!c|JdaAnmwk*)Ahwk_Dw5XV{&T^6DB#CtL(maa71IO(clS2 zm0K(Rvb=pF&}`E;r-Xvats{Ky+llK_`}U`Q8u4}>sg)Q)L%rx!F0@AEdzGo_*?lVx z=gW@?gN`KDT-a(|ddqE;VbkVtpIvnif6R1``MGYKMef<{&wKlSYIJ66`vr?zYaYn` zr~SVDU~X7}&ESao)V)D&u?t#O{&ie9xpJ$C;ny~afo3O-i#J5t$)(+ElIO)XK)D1` zCBk7Qw+(gZ*qG-^ZS~e`yB1}pGtgwNZmCj>p+<6s3k@} z+n1(qo9kkhlw_>-ZK>Po6|43P%apj-w`VuKY>~Cqzqnk`Z)mf@(i3(+XQ|llv^}xu= zhT6c^jL^snYI$XiM)>O8fO?W9sW5MAF0oe|+kIpFSn8s#dh*QP*7nsXuhg@SKh}(M zYaRJ4BlEttLQJdAF;z4qb6H;9I-Clhtj>3|t8XxWL~RnRXI}fY$$#=^iLQP2q8@Ne zKxyutCTghRdxHJ%Ibg>T&HfWwt0fBql`AmTJl67 zK@LIdf*nrf`KpPwYS8u}Ske;P);t6iD66P^dh|Nq^>m7y7W0RTgbFVYTr3Ppjti0E zCwSmcNOfau5ZV}wk{goI_^6m*Z9fQvNPJ}7B-WFtn8x!SRk7s|hB%`LpciAu;-<~> z*YC~kGaFyf3Qt-G%H2Ctcd_=NIu1Yjfgnhb!q_7BW^zaR-7gl_s9ttZpUsRlRS_nU zSv9?0l*k=5RkyONlFVe+HO=DClG-eRpYrs?-#vnoe60UyYwNjsSAhSNNx$3nT%NPz zkaR>J9xqFLYo2z>Di)fQ=bpjOt>Vtfxsoa>EBMZWnoT!IduYDB(2dT|^}$N9!nYcw z=6|4)KzKQ&+;zN;Gcg@qKkVfvCw-@`bh>e4oy?)wUsxaAxaqqer9+-b|27bfTmBTH zj@JY5*^~>!U~QjeNJ!AOdSFUYs*}zC@K%2`1VxJ-WvX1)FQ6(jsbxnTbfOPmKWMg2 zE?I-3Ggj%3=BdDi=wM;U1TWKUjBb;C6z8neXNsMwp0dI@!rZBl(aeI@>9Cm{tsP6e&OH-67&pz; z*Z^`TKU8-s-2hYLn_1E?)st;9Jf*g$q>8%4S;+xoHnVClG*#dn;NSE$ zRJ8Q{vHpPIhiFt`P`C-d_hHoMSB8wz!EkHQoTB#%&?IvSQw)Ega%1qJ<}t14iMZL-`70W1zS(C$YBH?U9 zq;f5X+DN=#{To1go=nB27QHqO1*5G;G_yfJ4hl@wkC#x*_R3qQ@xpm^ks(i#kKHDC z2axmWpXy7jfIM7^))Brukj#lxeNZUM*9P?gOHsg;f})}1rJ$c)kNd{k)VXEtbm2@K zbVXQ&93jDIdRJF1X#oIaR@%HL(>gu1=#&WIX4#)tsVRtWa|k2o35Y6)A1Er4ah2Jv zsQS^?Ds^~VRt3ts0B8_k(Ty(mwar86(J6|u&V$U?4lmKFh(x<)fwjw3rJ?X0egcS~ z!~|k+@FEx2NkL}}t&^y-lq)HfZk!GOYP()8+mE&y{7Fp?nbqut8YN2;W`lMM(6NoM z@(ce}Pe6p91R}gg_dm3(o0DgpVg&1FRmb42<&S_@o!V2c{hla9HRF#!dbcEhQM2zBGLy;)OQI2GwRixNgG@xc_9j?c-x z93Ph4yxQLS=wWo8()`sn0pHTo3}WW2(;Gt+*EVep97aWP>T%lIe(GRhBM`>WA z-yUCwKS;iS!<D~>dDNVXqr1L5O$z;Y(&{aXLB@WYUIaX<~dX zp8v{$%Qi)EfwtnsKDNSv0msWe+7jInt2?6Q(3>v$EuS54{cC~T4ppJ2s&m-YSVjmk zK7+|b16qUX%c}cdSV?bm+A^+<9L zPBQo+G%Ki)7Y<{5XIuW$baD*J@!2(_#(jGa(W!^nn`lfZvE+n)yez5n{Pj3YvRnM? zr@V)i`Em{wHk0ep0N$azBEJ)HI}htNm1&|j3sN086;G8HI_t5+c`4~ zNt75GrJ5;ZEQKPp9D{^Z5>lxoN!rwDaq67T@A7fK^VpqP``L64)%5x!z z&0Di|Eyf1r5AfRYyrnTp%b+Onx|gK2sAgc^3u(-(-{?J^$s6R>T-CPDWBCw{KMv>@O zBG=gRDg}%|oFmpF#z`UNk2G@-WU^aaNM5yuvlgov#5J3P*=a8h5(PMBxK@oLJ40$V zYG0XzB)2F!FL@F0Fv~f!1p#J(lgyo&l8LchF7N z)I)4=qoAerO;l4k7C*Hz)=BPs-F^VW$zLap$cBPps(VSr-7{_o-5*Ya;oYfd*1D{I z*!=l~)1h->CY+||1Y3c|`knWx)$yI99YV&lrnFl^>$xw9EGG{|Lko&+?NK7UC>U=n zr|8%XN^$XmD2(U0xMqx+CY{p+6z^iI+|Vw4^jwSba>_|z{-W+f2$ zWj$G~pr3$hlT#JzWh*`-?7R)L|7q@1!cx(yK{TeQnsac1eKX!v{v#-!0mI>BLM|@s zImRP5&jP9i+#zvIx>b}&%pF{hf>s#BvW^yCoKWDkU#3P&Q0YKNyW zuZTGewRY`ITs5P{j&-y-kVuFq{vC6m>ra{pW&qK_wq@01ZAOb|@H}=QB7b(iKl80= zlSiQL<_vcu-pONZ0*^fh~D?uCvH!Bku2jD#R%5ECNqa0 zl1D@ni|0-`t6iG`KvniuAopx<4|;a>IlUD#SbsVGIP>9&1q;5>5FugdF?UV?#W9 z1i)qzBii;=7v%!-Vtk@H@+6^r2a66WlehiS8onu)zwFrap*`DnutxVzw@!uR<;p0n9M*4e3V8+3au3rk z!wAt0n0gY~gA5Km9&CQzEdbkPz4aaWSu-E@bWl-`(`f@QmgnvI^yS&uJ;VpMg)rj7 z*g)D6sD-x6mdVKXsH6L)s(#zTk2dtVTOO~x^yFn`4w(T_8$@qj4T4FD>-+k4T|+jGaS2HGI9}cP$&X^g0}UHv!$PYU z9M_JW#%`d}hi+iym2W+bO0PYf=4n(uMSN1HD)$7tcIo@&;$yvlh217!JoB7|e`bT$ zyj5oRw-p~Lk6Ry%UrSkAbTsE|g179v zQ|r2*(%QSac8M~HVswx=QBjpsBy?}8vZ7ZZ4Fzrjk)kymuR>bWGK-R08gkN%6&_6m zEg*6c;%4bte^_^hFOFKKe3*J=N!&+6^^dhA2rN|1G zc4{~Jm{Ysaf9TX6IyYcYPTvp(-0sB+H!5GU{&aAW9P8UD7PZ>MEQ@$PZcFg3Q~Qn$17UO3lvk$Zf@48t@k#tf1l|l zZtZIIIAllO*`mR5tp%G(+9j5s3;Uv?*Td!x53^T04BgOsie=wJBMKa|_xBm|)>l|R zu`Wh9bCl(YsiJ-mOwW?e9ZJZJD1x5{6WEl))DBD6A%qwQ#J|j8kAZ-OB@*KxJgFf} zC?T;uvo``Bwr*hO=amjmD{rJ1npSP>_s9H64EAn5c>d(TzlL`|p$GVV>;5`uc>5k5 zmbI_Xq?9ncx(BGZER~>XUoqg{cMQc)?g4BVkXBWvbvF61H`%);}9P0Tw^r>UhA= zDzB*>eS6s)25`=yH*RI5c2YaK@{40F83C#6}*Q`KhsIO`LT3CwU9E4g2=#&(WBp2@SUs zXXF6Nvs)h^4N}EuxS7*zQk;go}IkEPsE1wrG2{>Q|@ zf2PpZTsc^O=3@qu2I7N2(tIEq&F2HLs?Vs{XJ05cLiEg>_ERjGLy)!El`^NL;l7sP ziuw&{<@FJdW%{X-Y_VuZLt{gEh?Qao`|42_Ho_N-dl18jhE=W zaTt>*y4GK4d|2<&u)vb(S^B}w5hrKIy#%!g$QopofX2V2#Unpa(ux>Gi3mR(9?_oj zuD2}r8Rasb7$&~JjVQ5cx+m8tzK&;DAyM!sCC2^Fbjv>2V-B&|Q4cz7ZY*@HsQTOb zR@uA)q@6Y0-UKTr_>hC_oTtA-;PevDM8KsQ-;ex+koBn+sccW_7yz_omYlqk5cJsH z^YO)ZT6$YIxeA@S)3s8Yb~pp+LIao^D+#{{(A-J38bgr#a09bgcs5yv#48tfg#4I7 zb@}-bd~BacG0zJrv9C@)#>Q&ctJEi|y_#Y}BnA-H=2!@i6-6KFtW`~q?;DVd=lPUL(nPcI+>CBYFUvBt@_Lq8tQ?7|P^$dG^ zQo0dkwSbt1rNE4pjB;U9v1vAe6cvAByCaUrxiW+rWuTySJvczk1TLCOdirY+Z9vRP zatrqC;P7KQ1BB|D25$i}vfMdv1Cn{fY{Qc2a}PVD_{KdezZeNP;ECMb+WCMGHU0}z zs@&Y6x~3e0Yx7(%8$$3U!{AbeMdi83N>1bZ*(VV?o>)(!xs|Lh*oK9vHM9Eysco^z zqfK-JHtntt15z;X}Jy&FWK{rHOa2B_9^D67# z3Q^!}W4qE8;n+1`d-3$6_01?VQGqv+ich5Lh@JZA<(XC`F6Po|*`wLZY08i34-`8c z*4sBM3-y+=%A-{Q;oAdx9b@{9ay zoEbt?3*!xd!CSqN1RG}Je1ZdA%*llMRUm>$^PrhQ%?eTy}3eWiiHW`MY{EjOcg zY1w{jLT3Js1Tl|WN?fJ>Ss)P~6UG6d$j^IFWi4G3S({Bb$UF=Y%z+}!AT)wq5fHv` zU$5Opo>TGnIZ|PvW6)op#{Knr-p+HM*rF!0il$!Z;8j*!F$1VN1Vc8|r7Z^842#DE zdOlCX_l#AIEdf7r$Cd3uByLQvqvVNuUR4y>ad6lwb$FAlMNe>IS(^6)T+JdWc$<{L} zeVa0-T@3Xm=n}w=R(1@~{hBLV^vs{FEXMHR%Fd4kmyoR@ctI5a5_4_Nqslqh8Q2Hg z!Hbv`%W|=irx&tzoZHeR+(k9wc*bG0O#Npsq}7ajw%caozPpHiQ-D9w6A-pA#P$nJ z8SC8G&IYPAl@+5Bu?O;T9-vvn3(^bUOLdPE~EQ1r; z#t>FD95r0EXHb=o8JUU_I#coBN^k;IPOkBb%XR0H(|(5D_grD8%FRfX#J_TcWz*Yc z*^|G(SN${H+@mJE2NWWZ^6-mj1~Tlpj->FD!AWP+Q%xO#*`IC^*;8U&c5||2-}wtq?xT=s36sL;@BjYFam))jd*r6s6TjmOX718wtO9 zY`g7Msdf82d!e#Db*W!)qle&z^Ypor^{AlpFAk@St=nOk;P|I6Fan;WTW$xE8kz-9 z1RBu4cHpm|5d1zlHJyuZWsBAX;vQnqVo2Y;Gq(mOjee2Td3lii*j>MDxNdVhP<($o za_5aj?XGtmtIVDEeSB4wYi#?m6m#YIYuROX-`Gvt%=otwjWkAEsVZN2Bmk~=@N-t9 zc*^=?td!xmk{|Q$15!4EPAi$ACZja$jdRIJXHrg#9$v zK?_u=6W8Qg@y7QD2ZK^zKG47J)}jRJFQ`FG8ZdJZk_e-wg@g*vvo;~8hz;O z--K26NMbl3iA*0NVIOkv`W$&^RoTQb6n3e@y-^(*Jq4Y);A_X`6^@Q5s=vKebOP~my`3ZB#w>{+uS!C zBc7M`Y}GKokbVuj|2qA1^q%D8ZqGkCV6g7x-gC1->o@dWcbw}rIqDbN&is+D>yHai zeLTI!_px|98g+>l)Zz!i$ZK`%Jq}Z_^2<-CNgAGt{(zBW{MV&-R~`A%Be^7<*zW7m z>u&zza-LpOrlL?#LR2TJ>~&iGC$2D}X8U2)2LW_s&!YY6Pzk=;iS!$J~K} z9qhP^CS`9t^RNG`%VE4ogljo^2`*)z^9f(^ORsB)5=xWYF{1Ds)dRL)*gprS6K^t$ z2uDe7dDpgg%(GP;UjEVE-rTx_C1s1P)Y0Lq{Yc77x(BGLQAUi}4UB^59ZpYiT5j`C zIxIP`s}b$O-egnWYw9KSI$!!M4op+NnHeZJv`dd2lTM`Q2oALa&7^S$_6jxyBO3(h zzP*F%rRJbcr^6pTS7Suc={neB*f@M%7(8F2yaqiQxh>=;Cc%# zvV4dbEk@knyMR=Kqc8Rb1F6IvKuy`z(WcKWE7XXaWBOk|K<}V=RN;nd3tk{gG)CC_ zTqEPf_$&DH2M{`*{8!M{WwFY*JSC9)nVt@3YE&D$fW1#|aW{$1(=%$fb>Vcj<0t*0 znq$>=T<~KhI%5n}Yc2~QI#?=46#yk9nr%<;!BN?Zr0nD?4BxInUok)RoP8H(Z~i$9 zW!gg1^vr)#*7X;x9Nul_2|91n!rH>%nx@mHJ-Z8e-D-S z@EMd!L&c+6Z7C1wjnC*Xa^k63^?cEcemw|(76(H01$~LdL5Fs5SX5(vNC7Ydk#)^w zVJn@pFLiX8kGwiN0{!)VlHQo8n1oGbU=Ld;2h0y1Y|#?_^4;QTU~1T2eSQ>t zWIFSN(69~-#c7bv)ZjHxuwUl_jr)QcbuPBpbB>mi*;oF$8SDFWmN4Auh0r%RJ@fANgrt`}y}DnTOQ5|LoJ4`&(wa zTbJm6j2-Q9d&e4g9s9ajwO7w&mIP(4n0m=Ppwo=~7R^(2`i$2pm%Lv$V1JYrxCT=^ z-1Qv7r!^lB>)R!-EU0>^LBHek<7Pf8u4>n*uwZy_s>f7I$*(VGixX2D_6Ro@ zLPe>}N+7Fx9bQ{1g)c{}y|?6jvBXW@TJPf|me)(&Y7`#LQn`I=^ZV@ix1<}Lj!NR5 zUzSN)YILm9rdSHRrjAK7AKWDRdTR9Zk3U>jnrcrn8b3jUX`rDO^j|Rz*rVzH?T70c z1|{>>ZeH)=@a4pDM*peTwpGla9Jq3r2~y5|I&<9V@-uI%^9yrlK{{!u=kB2$23}b^ zYiWCF{g4@jVW#&@zmdBq29CaD5l|rLnKE%|*=VQe@YZe8+9OW(U>cN(?ZmS5#`=bE znXL7RkCUBk(bNLLh1`&a)?d8kbyntP@5+Z)Rg{TdJ5@^TtwqgstMi3Te~E-GZ!5CP z?8Avr-vrJOWUN?C5mM6Re3;uX^2xF#l&#IdQX$h zM_Tuu5=6K+P5w?|?%>`xZfXRNXVipm0B@ug2xY)*wYsnx*{1@`@G{Mo0YFURjU;~C z+&(Y)z_7bP0nAas2(KY|DP0gH?hO?!eARCdAPayFm zv#;}XEdK$>P7-{br^`IJdG%tF==&4K6e`Kiw?h;2RiBqdeyXP8;N42F zf>I2cnRh^Hwg@0az^{@yv|l_!(!B?=5u)-CQD%B~uy0^OeHiaw5t5Tq^XC37r6uH> za(zCq83a^Nqp$ugf{!qZnsT<522a36P~yz%O@3wAay7}s%dtN&bY>aMU6DXPA57yw zyq*7U3_+SP20~btEQ3F=T3^~1ud)w7;13W@(y9dnnaOP5!4TCdeoV2KRQwvHkB+X5 z>nN>rCM%Ev2(kZ1P*9JPca%DjA9V@fK;%(5LzqAD^%21OG8yPhgH7^!g;4L!_W^o+ zUU@4~lspz;3+&1X0X_%B0zv#nyfJyz0DBHA7Hdq@I&l%lk1Qk#&RWgy$_`Q4kciQ zOd%Lx1~4E_W|A2S00yLzxg|Ulr>F@G3_`G($#=HZ=cj1dgJUkn)h7B=-r(s@D)UVV z`Nyyfe@%LE>4Vgz8lD>Kryd@HCQ}QTucM)3IbnZNo0put=C-qY!(_6<*$k~tHIy@C z^cki~|E!@;3KJV9L0Txv3#SsYNjX5KP!2DFGaz%^!Ftm^AWI@`{4F3V5!--=14lw? z)qL!q+kRX6rw)@K--Lm;g}P_;Y~1Hil0PfcFRzE@v^)jBwXdi|+}a<_?pj*p?-460=!<#-H(XdWC@ zgByEBxPcP?>tAdL_Xj5Yt+DO?<1G0fjtXJyHH7joLF7MHJOMDJO$&f2sXQax`5^!( z&v3#d-Ohy_+fxA4a`rr`GObYrRPF=J#UtDxeWu%^=TvJx%#c_)sxAYmx}2eiY*Qgg z&sl{T{~%v&$R#yzAQzv_o<>#&IEfM#k=x*7&g*6LpdAh`aH}&m@BWb9cD9;ccO_ zKq!Bn)h-XK}0OV@UrV>&RPZFr9zW z%s~@f=Je+saugD4X=7r8+qNyoU=QE5WQXQhmG%w16&lAREa2N$lD6i&MbHAO+{$Rf zrg9!H8WbqL1AN-6jHRBSLhn(4*nikg^HSXiJ!H#hep$FtwpI97S;r7|T779sP!QQy zt5z$b;boL*knSJJZI?>}V8*6|{?^x@ep8;+{vKGor(4H>WVBKlznbyF(6;|B2k{IY zbaYE#u*~-deAH%tq>+IS2-Zf~J@LDxjRlA*7HJ_VK1m*%*>FibJ=JRWtm6f66Yxzi zytuwsH(;N?Papk7F3EB7(q3hyP8VzbF9}I~7*u-aC~XzmOb6XGD5CsL6M@jnF`s3l zwmn;$p_VBsKVcnkazEBcu{b4u2= zOh8TW|Kq5Houlb`5zU#@keM11!hQXCb{Os-{dIA#~xA!(*6~enCOxtL}MTP zbB0|T)KY;-7^P4hgB#+?OEUO zPz;G$wA%GCplo0>Nlu=RGECQP$>DGhc~N zXH=DrZD7gC-Tc`L4i&VB!~E(zCulz4;EUbiOOjEONKC_2;R)#qu;*Y1r1>%7sNA7C zAeFku=A6oAvaM>Jlu$bcwnn0K7L(=nv4|6-tEfO?4;#{X43Ag%WQ8dggV6NB4tDs= zlM$Q6uz69{P=wDWH1VvGM$=wFQf~7DI8O2D!WMOl+f3r!1KDQ6Nei1PjmXaGMD>@| z=*lYR#1U&2c=UToPtx))IHdZuA^(CdoySo8%lFxOe#_VnbTS6kH#PT^q z#c{{xC5K4mLk75l*yl~+TmG7z14Z4Lqa5A7I;z3ajFK{zrX?(N(1jy9sgYolz_H!g z58y!-_XDzP<0z{5Y{_jeI9i+;fq;C=^eZxArok~ndUEjGsIs0`dXm)!WrK7t>Vz~* zF6eV+c#?yQzyFfVVdfVu{?f4mpnZGWAI(lpHIDxCZ}MvR%5X_Q-SZ_|Z+onRyZ6S4 znKL~bkAV@;@*p-l%=>k&W2U*JGH!jiyKCMy_>JH40E^(7uzb=JiR^k~lx4<{u$iZB z*=0l+BoLe8#Q0WrzYPo>C#K8vWI~}BeYr=bz;1Z$LJ@ikrkhxF;Vg|$6D5k8 zvU}(%q$Q;>0!4kb+(5iMxh1E?O;_OM)g+IB4SAWL=zx>G`E8l3GN&j>)FfRmudR#H z6~2Ejtw~n2l;+eZT54)U?zJyGE(6q@TOB5=Xc8ogH|8|dW&5OE3H3`?bh7F{^w*ES z#84VNOqyb>`4U5^qV!)g1peo(#f>zSqtq`k{zGf=(D^ZgIRA&Ffm_<|dh5UU%-djM z2z1-~yP1ziU$y_k+ePA2o7>eo#|@d0yisRdX4>`r_xC3zP7yWN)ZZFqFP3feEz`}k z7hbXxwHyf#v-5uc`iV5XL3raQr=u;Oc z79TJdUbP#WlWiqPylyA( z%j_I8#T`YWyus@I^dvtkPq|O0NX5IY@7g7%*?6zgd6S*`P(v-byRXslS?+d280>%Q z(0ws`nIb*mtdz=*^u87E#0i<{L(oLP_q?aa3Fou7!-SIMB&a!NfRB-<=kz%58GY%E zI8g<354&E``XWX&@WZqkFdV4Z%p`5fIK)g zUzacLDdx5}h9=!?!tp}W86y*h#WgVLLzUnolakh7aJ`GO{w#maHh{yZF{icA{& zRJx_$$pqq$Az7-F-(Eg^jyRR~Ixb5z7A1+N}q=(4Mw#JxnLT)5|Nt%5d>Ag0C#JQ3v zBqt*c+5Qcr%uFbrhk&g2{DoBc-g5GXnhJ!6PA5Zbap_kJn*VYonRev_$PNRJti;qr zZLnbT?*eyVdZCZm1QsosV8kl;G)@9Q<+&zzFJ@}5c*E{vXljM%7{WCBbPr!SZkiIw z8zv;Xy8P`sulLD{p`?AO7BzOfn0-02D`AICK_q~1cp(rRoR;QnCPP1r696f1(Ob-e!4Es$3|xq+z}147Kyj6*{4 zOn_J#08S#@DCB3yN^qto7E7@V@M-|#&MdZHjeS zoKpZ)vo~01K~Hyh|3^U3^EWVw2KYl|#xfv3PvnLUWb&kmga%a#ozg(@#Xu%>Iumm0 zP(A&L30t#4x5laV<)6@0mlW^aLs*=E*$K-{6mN=X*toLkljU1JP@DuIqh5=t{6|7j zQa`=BX!@%j*m2PICv=2i0+B+%lBoh|1_58Q5S|?P62_MFB^Lo4OC~MSLs6HI6wxj= zsKHBKpPkmL8IiowZc<xJ`uT z?#0hHYL6v&9qHx+SJM!g24G)8*3SvwE*xnwCUbs+-EWc6iv6pmd~Ij6?ysQ8oo|13 zJ^GDw!p#oDFAY%!#r9co#h_Jqy99Gk%QB{2yKCZ~{+ylLralLaRSt<5YLUwwZ; z(B&loCQhL{VxjUIe6`(A8v}0c-8c&f#m{~FA9o0KbRmzJ?Pf7!}#>vZN% zp!P$6Zt+_JEhbzvqoEsLu^=#Eb9#5+mJ)=q?Ub6fw8g`Rk7;~E>dl9~4Ee?axHjD}r(o!lz^mcoQj`6H`u6_&BZjg|bI;!3tWh zT28E)ave%*$mIQCL?EMqXAw;!fPsh;5OCN}>pJ0vm2NLyKN_h-vd1hPoXSLtvwqs? zLUn3(M03?8UY~nW_!5WvSIBMWPI`A@0BJ+f8bZ{kv43$7%j9&mCPoh-<^w#3o@Q)?`2(%}oS2#M?QMpCUH|<) z$u^%;T4ukE-O#XaN>QeP;fR{SSGE0R=Zo4s@r0Lz+A)LmfPR#`m06@&3LlCP+agn1 zg$mD3lOu`=Uy2E;ImeQyy!}+f3p3>bp6?Jt1!h*Ev&hld4x9dIK%%bcFe-SlE}wiD zxQp^eBn^Guv90{64l%NL^OYKNVl6e?H^4Qc$+Kn;^`WUOSAP)=a5A`fUVg2AZ0X~cZ7Nmn%#mOe)9nkk3_!U5#V3QL zPFHg|OBVzxw-z-weR~Z){QCSIYbM@o`iIJ9g~agTgaa{r@v&fnu`^OzsG##M+H0zd z9iR=>!nhQgW91l?obL+eSTdLQ_;85TMz1xHP^D0*UYhE_*RX-__hW`-B=9;jKub%-JDp9A{K>(UQ5^1!^R+a>H2Ri{Y8+^QVg z^j5XWfvb%L)f7hsv-`>-*~m#pJL(shP6BQ@D$qr7`vwjOhWX37J$6+p!W-@FV)k2Q z^YcH;lYI^?JMVjFQ|rrwc(0$yQz=K-i5ZXl!`&yzw>K;s2n&xn)?*=_?EW)_gmI58 z({FtRmK1Qcq<5poCOwV2Y}6Gyx=A6G_})<=6+7$onAB?30NP* zT9auP4b~LOQE`~ zw3S6i35_oXYZw^{o;8>;?n0bx1iys}dSVMLr;dvqT3c8>@U<>t$y$efOR7P<}ReHO@^2_=&#R;qUYB2@Cx?x>@A4>8=Zag=@v zTyyEeCKFq$lC^kX%#zAHj<{)sZ~wSL_CSf9&XCcIIzJ*_V(Ea%g!mCJ-X!O)_Pe5F z40jTEHYgid)ipNia%}Ku`wqVn9K7%@e51I0QI+@hcD#n21G-7j47d{av++C;6?ZZ z%wEQOu3hTH+K;x^8@SIK#Bv-&?t##QtqkdW)gI)u^W_s}Q`3XHr#?e7e#vHp^;TbT$8Eyr*E$0 zj+5SK{KoB{%$sb1%}yg;{U~vJ=}q#y;zv0M3D!Mp{&O;n8 zlez@J>YZUKuyvDbi9T2e>6?$A_#5{^O%19N6&i{qw)Y3KI*X>}3TJ1hF}36rfZ744 z0QvDG&62~Fb$)Mta0OUlXzp765GE}+t1Pi?>iQJk9AR5U_r1)cpm%BPUjAIpAHm3~ zYFJR5N$MUr6yU3#(`v8cqJ!w7WgJPsQrohn@=;m6p)g~&rudn>i6Mksw>BCwN%oBy$%)DC zJDN-CN=DfWTA%dJ6yYt4Rz*pas4gwRTHw|2wm7{+{>j-So9Z6Vjs_@RmH!-NA2&v~ z^_ci#50Ra$wj}duRrMl8UHVxo3tf>%qMfb*{0ZLH)|SlOm{U4`>gXOpPRXK%MknWM z!sa7Icqw!_Uu2go6-NbK&@!m4uLu*pD=L!JI_)@9m~u9$HTzzc$iw~J?81g`lUA9( zDPQ}B7o@(MxlbmE=wPsv`Mn>vh7G`}wW&4>ycVw^JaJHLM5zRT3aCVW@Nq+Kg-uqo z4bw*~%-DA&lerWK!upvB?UZ;#4w|KjO0>+|wSA{6R6z+(Cy>T)U|_sI+^Kc`(g?G` zgy)`h%TknKNAiqi0x5kWAS{EDlAK={i6ugy-vk&4AX=@2YEe)QJe#eH_t%ZZ;J{Tr zWJp@W<4^>bdp&xZn$#!m?5)7g0PoDhOJ1mAyS&;#x@RtUds}hA;g%mDi-6%mrn8Sz}>?BP*6S^+PZw3@-BM#^h#Zux@91zpa(RsiDlju z*E^B6gMnFe`&MezItq%QBji&&0ZN7_=9nE>CZqG-0R&;@x10uBvwlWPIc{QhZta%3 zL*c?dXz;vFHB+ljvlQ+5fw;EeJj zNfBX;7sdRpIG#idfnNvb#70z<19W-pw@?GjFsD0@)vww2QYn`$X2AI9qF81?AP*z> z4FgKsrde7+Gg~?L**Qa0OpD`jrGL`oGk}8k$=CFz`jp+paekBS`d)fdsWac};+6G( z`#oKmwMT3AGg7<<0`mw!X#Csyf@4g5Zz6uJnP>t(%wTwPz!9<;FkI%sVosa~$i^)a zhX0IY$R;!D-9={GaV1>zFS0gr*RiRHr0eaQ(M7cSO$bokqT zrlWwCP%nDtgozjl^bKhhNC+GN%bZmN6c=vl!4UpSfv(0%kKPHiBU@qMGd#^1J|iP} z`xj+JfK%3w+uA7XYrh;K=RRRGACV&a$b|2~va!r-$a7CCd{V4}HQkFKpmxdyDB-iI zc6>&}iwOWy)(xOtMe$6SqxJg{=?NEkb_rlckf6s)B!Y(=_uh9~Y6}7fJP{N*FJ+vX zI?~42wWOLj-Yb&?Gn>Fs&{^InnY}EmwW7zcy9wBKb><@!_n-sMW7hj8IvknNaQPXj zquT@NaO`NP;#H~&3dy$MNK=v3ZBiag%~fouO{tkqmr2dF@yjmoBFq9t>B>Gf7JEXO z)O+&&$zx6HJzc$l!eiUs`*>XH(_Ir-#^F~1crw6KyV(2|?$2&P`lMJJlB`4k;Zx21 zvGcIw4`1p^u>~B$a52+B>{|>eX%-Ui%z(XeI*S7T#)bv3KUpWXEL{ zfn5_!Krf1a+DD5K1TKvbP{`j>W?cQ1#!rDefwbZ&&g;RZz1j|DJplgo%UtE2*qIeg zmCbX2W)!byaV=iY0tNdW&-6wO><$^?wi~`~tDmI4F6zTg|9#A}C4TAs)VcPie{5`j z`{R>nt8=oGwj&1KwR`H58WrwQsVmBAtc4t}tU%Yo&LDNb{<0(|kw-&wm32@|k)ov_ zrK+jIL;7}8>6i@r>OU3>w^S4qKUntM_F-;LN4a0s-_e@+{Z>RbgGUz#1oDAU`$ur+ zpj+s+Cl7*z))$MT9oV{J+wEFL`mn?xvkW$CySaD>=!+R=N8cB0c4N7G)wje=Xoh_O zVIVi}Bp{CVQD@p;?LKEkZAKKKPqgE02!ZMx(m~@V?%(09J$TN?=TVRk)g4~7vZ8$z zBs4!DAMqQi+e&-qTr~hv1&?No0X64GjqM^=1J}>jc4DpumxFJt6@fQbJ!CbHgvkI5tYCS+ox|$ zgdIm$ySVOtMudEHn&?C6Cz(?N9|e5)2h1?kr)v-zMoHlQ$4Bt=;|Zfxa$?Nf;VkXL z{fJ5q53*EWDh3t^NO)rzpz^~Ohpis6uco*={fttC`~VQw3)tEb+X<=Wg5|^L?%?J) zZ1Qv?&Ji%*$WvUx|C%vNz$d8&j}M8anxn#v1{N;tM)jiVYUl ziAoNfI>=)&aV;XI9w7zWsTtZz* zcrGE@Hrmz$ZzjL|ztX948sSa^E#lB!451&=W3kKexK1_y2*cf@e?HmSu2EW7MuYl1pd$s4kl%XU$?wDX6WXl z*JsVdH@}0o$5RoM9r4PX8Y~ovDzm!m{S~=lTJydbH(1rd4>Lk~O13wP*+&oOq`~%c-%1=xpo@?8` zEau^Zs;jBVk`yt(Vwh$H3k)Vw(Z2`*S)a}mA+3{Q>?QJU8|Pow?dE?d82as3VxMEu#& zKRriRWk{8cC2A^gXtCH;+b%6X!_t*{MOVfmo$p7%`$n}q& zQ_J0^;cZxtbyGI_+qeO9BawM;*Er;u%9V*#>9WO-UY;N%AUSVHfWBA72W+v)5}Kfy zOPw8?HPNw}vVg|we(r}>V{rdXJHXKE7o?_-ktq#h8yz^p7)VRNKtg8w;CnFgmc)0GGz8XWq-)7ZPQCf{lx7=Hv`YQlKNNS_A?b|KLguB#cqmX)+W) zeCU!k87#p_LIBz{?`2iON{~iMWBHA7Gbqj7Y66d4clel@wA5&2Bb!DC;s~p1x0KuI zBU_j?tZAERkN*Q+u5R63N1A3-pHE?DiZpZWnJ;CYD60;+>%-CU$9Jcr9G5mFZ`#y` z$D>?vYy$AvTL2p++FXIZ7F_e2Pc~xLxc$NL;)}V;gqph;1~Nb%F9wtwEOdtCu46^`pQ%j|Mge4w-BfDzmP&@>8ZZ(^AW<8?l;> zuOIDW96oE=M;p6!IcxU>EO~2_a|rbmY{%~*c0IT8wQ&Z;UgUl_gBl<(_C&+rPy%H7 zF&w#BdP5spk<2>m3Evhbtt!sT>Sr*)<~RF%@VdYOMy6IyIpqhO-MHjp>J1Pas@ImMoZ+djOVMxHTb;GYL^6PfV9?RT){DQPkhkAeZzD)O1BsO=SiVg*Z#TTf9 zjN0rxV~F3|1T@hV@6I90_k;?VK^$irmyo>pAf<5aB$=Jts0Li#O2iJ97q^V zI4f&NZ`Bvlak91V0BL?li|urx8$g4 zlu+pL6Y5ufd(^M8^-Jlr9C~?YL&QzDV~u6$K6YM5%CEQTT#lKr$w2EukoaEQ0_~Kk z`?vaA{p(yRI0HI;pZ19j+g)w5-b}kdZ%U3>JiX!f`sL!LUmG4w)vn4_krS>QTOVnb zP=7r6)JwPrgjHB~WrT$D|EdTKtJrW8q=9R#wqKnYzD_dpVc6=%)UP;RoEPvXtWcE8nC++j!ef-shk`yp$ijn54 z3$hL-lY#%5EAT&Wt!%9E7Dx5!!uUV5Rt}vXFvw!-o*K!h<~0v*@2uPhu0Y+UoW?M% zgD*_22i`M0I(OQmZy&BwB=`)p+-@xGKPa&F&CP(Y(dNNkCM}`+Wxp354{tSFa+8~pKl-5a|gdSe+iyLl9(@T^8Ugh+O#x1V!1&=Cly_kedS&^Q!1z28Vcgo2>679$_xihp zeK}hG$`72XCX6`f@9A{Bg^K(uz81xvq{~lDBvBYq?WER9ejDs2rzJ z;h*{i%-aYpJ;}%|om}dx8)6ER`dDfxE?Y%bMEz?fljy~}eQ%v#2vAejYjOmyElmhf z?%g}kJ=Q%LPFoGk2HL>AJ>iSOEpF_MAfCU)NXxmrt|S0+uajwMZZ zkFUBAq^Olh0GgVf1d=#EWet2JjN!9L@4e}TJkTk2z62*KuT?ICMK-|$U;hsHv1gPt zZ_dGZ2zXCXiuN%~8C&yN5wde{c*6*$9t~CLFBy{S7ls(<#6dJN*D`(uujz@`M!AKA zw_z*ZCeFqKtkGl{d-an!2GPut0O0rRtY7;;-T5@or$c-@z6P9uSFZmB@9!bpt$96( zX9s6uI1v09s?(=e{W$%DD*+oE)fB2Cp8#|pvxjtt8=kx)vGQ!g34@pjWzElYDb5K% zDf_BbxlaXQXHaA%^Tv!*SnTnD8~WEqGhU^`zms+q^FdGoFhw&tC>y|_f2Qk@O5!9k zq7b{}$?6N77GA7DP}k|477p#ic~LOsZa@7qOx5#Q14O9<0ixo>J-wMEcg|U0f`F(# zrA_dg@bWzbqTs*cv_Pd3zhjuSm{r@90+`X(7pFup2Zp5VvD19|Z6-a)RL=YXNEN-? zO5X|Jw~K&&^|Ot^+@DIPUju|fzHkMFCH*}P<%}3MeZvMzD;ur+xukvRy(aNtmbC9c zuNf10Jn|yAD%8YnfAjt}Ga~uoAxS?1fQ@TuW|Fp7rN5?z0sZ(3!^GgPVz_op;j`gu zBboyK9+|Q^i7SxhM{|KLO}J%v*g%Ts^!&RaBGNZi1@tdp8$o6Gv@oW z6g&Vpt?$KXJWeBS0HM?PO+Rhg#iZm45#9&m37OdG4CLsIJpaJmV0B1n6f(p-rKHGe zC!ank(g5CRwDeoMW-=e!-4+5j4J3B9*JR%5!?(`O>D*+{ctqJ9C_A+M+iv-ES$2P1 za6>(o_C;Q0)m{hFc?b_f(+rmM=HTB(2_(MC{5HU95k48BvYU84E$0U&0?IFX!)=)- z86zNrN|(h$h&V85u{i&z3ZrWFJ@d2JefTF5gfr*G>ra7ys5{UuX?tqcwo2TY|TW7@-Z z_ytnRPi)6>%TxWW$AJdFVTb4p1nr=f_B!1;D<0?j`#|jjc<{*sFP2P3yz;db5BOtdOLVFgBZ2N6zh)v+B5)4iKJ;o%zokXOg)U2iv{yD%eXA!0mdb{5PXKQCN z=E~DhLpOK^xc68{yK0zkosDt}Muh#Yd!VsBELSc{BD2&rosV{K!2zM|ngn{6$K8w? z4vR;kxCw>{oa>40EN)L`auI6<_zz+@|fDTNw^{jKkA7cf3% zMMTyt@MgDe7WVL5&M8mN+%F6gdTH3Lb8kKQ)yuATc#&sFfQjZ{uu6{lE z`&QSq&cpwq%1ot?3F2=cGRISu`2h1?;Pla%&jO0z;W;enocO{2G!qR+s{Zj)5-yhi z_AkmKUzn`ftg)F%teguW?B}9kM2V@Xy~!Y`B`LH&haX{kDAyS{TU&ekzuBH`zhXM_ zR!j`(w^MThJYiV`m(p^TOKHLK7jM|I?)>37H4w7>M-3*koDWs`)g_n>SEC}SWS^@z zwOYPSc8f>52fgY>&Brx#~{#nm0Q?N$Bj z>5+}tW4J-iyn(+WGjwp}h7%f7&w3e=KQ07xrtuXjgUvQg7!dOZ!5340`~sE=6=m~y zdm1vzc(`CWQ}SN>Mn9TmyWUSwx;16r+5G`3O(}q77j@WxgLD=(2A89HtgO8r{YY9n zrzZau^)FPyfL-Ojho43>BQaD8zuzx9Jh^y!+p*5_W6^;t3M#@h{t#KfV0SU&51uF} zhffc!Bn&oiL8rOYEOOf<%4(z=5jI)QMUxBZ>NvMx`w{IF+jHW2P)$Gf| zKkzzIUb*P`URrB)8;%yIDBc&LWGZlD+}K~J<1+GGK?>7+zs7t6xTceo+Tot%_@?(> zjYj6fNe+!UTSBl}p$ASSD~vxL&iBKP)?*g3@9-l-x`5Ri9prCk!6Ey-T~||LsLNyMn(3I z+@9;a1Cz%wTT$4+Y()-<+|f6c_WtfCM#V*qG^)QQ}Rv0xTgtwieN@r+gE(YJ6q9|{vv+(S0^%MsG_gaU#zz14QsvG z5iVZWk_U{7_}*&tJflol%2hHKx15;w=yrlb19qTnKdKDu>`oR(btQd?yju@DklC@4 zU+%Nx;IOUAL^g=yq%t27n5Wc_oS>_iylD6t{Yjp=k3@a0juqSO`=!DwVaz$5s~L}5 zE4KA`Gw>>NO^Kb@T3Y#F(w3DtKE2)GFePF}?`{w5SL2vi?{SW~K1p*2Fp6|**-!Y4 zK+Is4ChF5-#rTBefN;G>t08bVh8E&MEiIAfn%03}d20>*k~^f!nTJe$f`GG1*c`w6 zh}F*P8~pX}dj8Se5$xYUxl6XJB#L@Q&4ex3E~}XbKmxpIJbP}wQZepOrX0O~Y9+f1 z*bWN8zeE1j(w6>FKpjruCnt*>+q!Q>ge{qsOA%_RcpzX9c)NPyv6Wj1l}1n;haHJ$ zVk)%*mN9p+Fw`W#MGt$`ep(t;CTtNDf$$cku>t;s8|+2kGq_Rj8}BU4-U0BC1Wz0&4oB@g(`G#4`|;w@%`3UOfI0TNX~*q`lfGy^qpbzClNP) zIOQnJ{n0u0H^-&I(4%>)PL15W_(_l7B6b$eaqh9u{=sbxgR!B$bzPGegB`>3yyQRh zX7l{puasK-R#PU9*8S`*jiS@1tER)H=h^-=w3eNWO17fw{q9E|Qekp2P_zvZL@f!!}a&?BvVag5Shz&1v4ko!>DS)m}WA|0Ct(|cV*P>t_{ zjgVp0YQp&X$WO5Pu8-P_M^Y1s^1wPU){J3YvrV`X$M1Cg1vE156p0DlGlwQr;KsZY z$R+{X?K{pra0n>{7$#hJ(Uo}`3}3Q>20i(2xXmG?=&6VfqebA_^eAHz<$b;bB<49R zil$l2P(CRzTF4Ht7IU^?HS7E`*Yj2`eG@K}_ho;d>_@QGOW)y$=D7xjD>PoXkdzNG zO)|fQAi}A^N=WgHmhAT4`rlc92A^%M3!2X=?C0F1CDPJcJsl6i<{M8fpMN35;lQiZ z{7(H{|DJCc&;Uk4=aj<&>8exiM|CpiNJfN80v#f}jmo#lhnX@`fRR?o0&}gKJ*#3z zhIg{ua%;z{cgqFh123IRCEqNB>pIS^E;7VI?wvc2D;@K9p(+!;3S8cnS7$_d%^t`OdUcTl< zl1)sWD!A<>YCP+tBW|f*q;PF6X(|#c>J!{Vrt-S#7?Gz;daBs#igB9z((0wHr3C>2 zQC2PY1g%?^9!**!6-`-mbK^&!?6XJI^ay{F@kC?#3Ho;zqX`=3xc?O?fjt-hpSChE z)|d+Qll}kDC%E9-nCDLe_Y}+J{gS#E&ivB&?7lVITsyD3)P2Ru#(kp)4Aif^(YWfM zz3#o7pvvUd&lWy$kG;6sFI4Nr;p%NxML!-dgnL0*-L1n0nPd}eACoDHeJ*SnF7Wnh zu20WysH&>nnNpY$pmk0n5lFZG9VCd$uvZ)tUyc!l${S@hM;nS4DP+~<@n@UF&rg-R zO3Qts@~fm@;n}`+j?}}wNuC`q@=dGH5BF*+FQ0!~w7gJW6BKkIA?STwnO$p&NZisO zDxTXg%&sb3*DAWTM4mF-J$q5ost4=8m7M*`PATXT;|)$-4O1 zh^j?IR1mMX&nz4g6GK#mqFDU1q`~wAzR%$Gw)Zm(2*SJTEO4GaN0W~C8$&eB8 zxXrib?MtmK`q-^`+pp_}Ua&K`;Bd#Uzs+xUg9ncSW#MKgK*Ii9sSlNIt0#;7TMX7T)rZ|CqoE?%ji=!EiYDqRxti5$L!#4_S$<{=?o?hvjGZ}bxZ59sZOE+H<> z{c4i<#Jh9iEdZHRgYDEj#x$w_c!sK_NP)=b?1ASTz9%?8`>j#O} zpv;)ofGA_;eIgN?+dXeBs;Wzm?+`4h65fBF>*V!5H|CA`a{q^3pPNLMM;QVOIvU7i z7E=R8FQtIOcDV8_!_>4?Zo4OHN^8gFQ6uXRyj>UYOGIyW+_J+BDzg`Qy?DU4Sh+2b=-B>Y4?@`X?;?Y4199b!cZ((Sy|#E*h*HaC7Vh z1cb0H(jhRcEN3*k>)%@BCQ%xY`6avBI!?Sz48dGq}>7fs%3siyye4FW7jVR zP?A0vFp2Q z#zea~0Ak;q?FYg@#jaCKsCD(H3{*1eI$>;e4O0lA(|RACA^kH*S?;)mVcZBb&EZR?!vWP|BSN|W*G2U)w$to>QrebmZ+`GW zxm63z*j9~pJAAe-zb8xC8bju)>!lH*JcktU-s%gp2pnoEc4MCq==g#@f`T*i0a0+Y zG6Y0`8C!`kP*%*%Q^)!OkW+E1nh?T*3!hWH@?JQSP%;QF17ke%PrNkW*8dzJqAgtx zjhfJ~53sQ`^(CA%El8(N$A(5br+J0cUk9~+<8`T7s6(XsX@)~ob z0idXv$*EG1Hift*ge?+w!Loz-k-2ybE(4KLO zh`xkjG6rOh?p0qBXVrDhq&QpI#DY%q3(@l4i~D*^`awZj_h$o}DQSrv)6Vya(`Xe~ zR{p$y`i`s#OVGaTx-fkCm2PAPqyB&{!I$w1D@c$Suq`IL%z;Juk7Rg(L3?wG*9SC-c z_8(l!$e`ywIPt|u(n!bAvkYdwMJz@fykrV8QWn*M8sjlb^)igNW1=fNP{qI7k?e*3 zju>FDv?E0Bkv7OM1sCFke9Nz;sjDxGECfuga$pQrRrUf&BbpNgC&=K*N?A3!lWAyR zbUKVR#jPMxFv(Z8?=;f5E>h*vD<#0l)%6MB%D=>ls%{QBlz>Fq#DHrQk67_ApafBW z4g+reTfr29Thpgq^gT1iGOa{O+I0zun?4NQ1!4#LfTHu)q?cYl22C7w*XIuzb_+UH zHpk!fKiM>cGD+O${m&CR53q26t-~2~Kf#=_D6(6IwjI3d$b@kr11VFi4(?&CuZ+Y$ zF(3F=#AE_vdNeT?q9`{sU%32Pw>G0z&-UX{Mr>5i4wNw}nVTi>tyo0;ZbW3HFVJHF zWes#3khOt5kPnH%k~@W}g*HWN;7!sse?t4iSVI~q3@*1M-i0Qz7YbH9_13e6# zWzHVrx5C}etid3Pu)DaW)lp+7-`w~UYy70&QNw}1Mv0r72r7?=ab^f&Dj1`UxNgiX zTbih3&<>c#|Nn~w#5EPt?Nm_63A3NJ_bzSsXp-FpT^b**i~Cq+HRLaZ1I7&VqC5EVhCMGcljCB)bTVgW%^ zM3fGbXbcJpwnzkF6cwzXfS?XAQB;fwh=>RXBOoBsq)VBZ_qx~KGYrOa-t#@@{eC=u z7#LvoUTfWLt$khhRsOHyccRXAN+PT_>5SirE)3<~Fd%@DO1bvoTnXY0L?xn$x85`g zq3p&VXg^E`4ESilADF#8s4+13`MG4N2Q6|Cy2I<{fJB)Ju%4Y7o&3Je&7-Y)Y%00kg08nZjMyFxrVFr_(Gpm z2cS?|aXXFB?VjAw1+t#Bw?Kg86A!$;ycgA*+9w38$eu1|Owq~kMnyYw&Mr~MX?eHg zUnH}`I>6krh(OCcqR=64he@$vq8kh8sPX9ntX|-fCzq?az@nZs^&ZNzf>v;5xhsG_ zD!EA8udRkW3Yo#PsnZBgstKC~o^~^R@t>8;E}FP}0J)q^zI1Oxqu!lx?Rm=%2F$9k z*z45|^oq{f%>6DxA#L+ckVwE$^!`Xj1Uqm9=xtg=q84CN*oVQAV_8wnyZ`}S)eCq@ z5#zKT;LjLHpG2|>AK|N3zdCLQ7i|)**+gnmbVrm)0{nXR;A}BoR1yn3yps*2U<--7 zIr1JAF!1+rMAYH4nINtQOW}59Y|bMNFsZeT2>uWzBL`{@jw>`NJY$$vUc^d#wvz$` zyLNWx9*?ib>9er#WYYBVLm$PyR#6qO4_uSF*nz7=Rl)Yk?C2tTR<%O_UgY| z9bc0&E>s@^&l6#(qmXTF>65@k(;(5Vax;#3BE-TVrf`9Vi#jaaV3lYIjuyCrb}>-J z5q>glU*yLk?eRe{HSbzm<*urA!88P!t^tk@UHl?*2z2FFxHkoS*rC~9%&)uH$8@B- zJ)F$|y$!knQmVH}aYDzMs`olMD_w@N@&f__{9L*|?R^`5;aw>z%S&+F0)1{r( zDmdZVTzW&<23thIwuAb*V|TFsW7+}ey_?O+%LZa6J+*Ty%h86hW97mN@))IW*Rd2t z_;lwT>O)sgSD!(J!)#{s$|Xji>#!S5aixjl2Vkm@QY-mt4fBmOybLE9%#_(lMi&b= z;4&g_ZU|mP+T=1TA5^t_^93ozR29OU2q6cYLtbAe~)5LWp#KGWq&HNDcLz#c`Z4t=Jq2+>Ayn!;K*KSHDN_SKES>llMM1J@rap+$r zq#xiu?Qo&bKPjee1zk*Kw+YxiK9FGdVAF9WIcQr`;kO`3F~1ctO&ch+AXtMUg2;`s z(F|n;xT5r)vRYy#)8Ff+Qd!;DipxQZEYdHH)ZA4+Zsgd6`y0%OYGU47SsD*juqf#C z0p80wE=Q3a6vc*rPuqCd9+Y`f4r?^|>$DaYJ(QF)}tiDBO6>`fFw90Pak*HDK zrjofk_aEKqFp(XIyz?b1I>TGfw}BH`VXZD$1Y_|U<(o=^27RRcQ$^Q>LWfnZw7AOa zEgXE-WfRqhT%B@m`DUuC$570nZO?6rAvl{WXUVml(5WA$d#%y>x$N4ImY$8L>F zlKc(A(yyXES=16RV7!TXJH_j+sXEbxjx@qd2bX4;u;N(ogFZV6Q;nSb1`aLOP&gx( zF%Bd}DTF5~)Q>5IqS)R;aTvw6xyV#QLryZ&@|Z;jZ&OwtEmWU@Q$ZEmV%shRUSfYL z>SE?n%Fk!l#EF~PT7=8alQ)Xxic?YAUy{NkCu;$PidUuKTUyM{jyDfPdbZlckSgl-d^w>%I zsCRY=UhemPI@%~l?cq;&FUhC5iu<}gG^Ba>wC+^*+1 zEqbQs{l6TREZPxpKC~?`YlYdl(6;4SD>9}$jd^Tzm_BCnV<*{=PpWgzpexsqOB)}? zWQHzsPuF}5m5Rm_wWq)~StGbkpPSR8p04aQT#YWGIckxIGuS5I+g})me;s`p+>;rS ztd@>SvGmJr9`;)uR|Zy8S8cVDzls-JO>Nv>8WEu{XcJb{muG-gl4__5VY0?T}dk&+qk{TiZUdY~c90Bh@bY zzWhqR$Aa_^CcTWjKXJ~ibv@$LW_f!pI~Mr-knx90voivJzn1?bxj8u0!MEylMN?CS zIJHGB$+InMc!pJ!P<926qk@GRnuUUD$j{OT-v7&U?(Ml}>9=LinqVU$^toBYj}WfH4Gv}o(! zT-@Kxq^0A4IH9%4$~CO;PPV1AXhWl>Wnbf4!I7_huZDhbS@?mG#cyMyX91zN?x5|x zeaB{n4I1}DsMt6Ev&?EIrbQ7!IuHuw0fLZlf&;)OI?s*uTFh`&x`!BiBq?-FeY``$ zyW$6$#0>yCHl*JoVL#G;n3kWuw+f$LsGShcJ zA77&)oBWgLfVpM24M+}s2wWPT);x8T$sz)B8Kmzm)@AL37YBaMlplDkC+i)Vc%q+H z1Va=-Z^54+M!M)7jV`yqrb!L$Qnyt8V+-{tul=R|+G$`~yH3L6b)?vE2oMnx7=Rqc z#ZMVv<3k`_yJ#QiioOS2(>c)wsti!Ppcc`31SYX(0tih(gwW`l$SEKIGmaMIVP21) zTA%68cTJ{-k$S8jd;hfQnN#M+65(Sd?#a{)zx))A;42B3Zu z@k6P$Krj|I{t(%dS_YFR%?(=yerP|VRj4}OXse&&PZPiA<~rF_IrTKPK4^P)-yv*V876vn^n=rmnpFxOpu^X4Pzz z>`eo#$jon;eOdVYlg2w~=GVbPSwnj2IXaBN(aotN=zE9BH}HfOTc@|>fZ9wrX6he$ zCieif_|)|<_Ap*ATj!vr0MlqQfe2pQ;}pYR)*jJY6cT@J5!c3-@ukA3J8!3pE1Fdo zT&I@MoLiCF98gkFr6bqE8dUFQU2Qq2ugY&Te;VS5Mz_?E8%~Aps{3ELJuZuo$iCi{ zFbv?Zv5!FB>M~%^ing8}H16;^QgHodEc$YuVIvCB#fS{#Ju$tYAqb7e8w+dj8zmTZ z-#bq2#WlxYdqy@KW;64^6T&9GLWF|hqh7XJ`2N$N^-KY3|&LNWg-NA`wd2L~) zZeEJUuw4U~pTf1Ep(1Q$_&edw&;AfkFJRy$jxnbaPSJWEbcXw9a}Okf0i-Ls3aSSL zS74}P($HC;jP6qOjNy=77;T3^bItM+cK)n4jkRCz8c0U%drkMP#UQ*Jl<-cpV*ua? zE%o=?l05?)=q-niilRR>dIJL)n=YApk2S+z!+PZ-p2LYwx%A!YJU?bwf#+8UV8y0> z^`3RiozNXTY6J(598?TrGw-bu$jDz8#0-lVN~p7Y41h}FIC^%@;}G{ASEXBQ_9gWJ z3=OzZG^`=~T1zt{v3Y9~1Bd{{2-;S4$pj5P-F-p7>LX=^Zs!rZkBszyzQmoqn-AW1 z3>z{g$dQX)-S397Xi4m)@OL_Jr}hQ>C)3XnvxAK!`@FA_k=g7+^U|2uEz-!P8f_$mVuv~^wAQNF& zb6+yQA;?~r1xZP3zD3Qs*K-~AsqeT4`V-`-#_V@;BqUfS45N_2uB9u=0!0c?%e~}L z{7j!JVM8;(%3)|XNf8LalgKQbn*%bP`ExEFCmVU?tZ#3eQyJlMC^bdizVqb{w{XAe z8|p1$O-nCnc4@wv+#}(Rz#%XyN&Dl->xr*YUTuuBH1XK{X6y9+*Mq+Hcu~A}VgHal zJx%(o8E$@VqTaX8!!xH$x1YFJcRYu~k6GD}6`Vx&Z5~~MnAW;-DnUVo_?Ioh`@kob zO#1L%#Ud-_2!}_^JCjotQ}VK-@zbv{S6wt(~{0MmXo%0a$SJMon zg^Utr-2SX4BF%v;^w{e1yW;cEW>~YT&)5LuxZ^k1d8e~v*D)d_G2@|%9=xQ&Tpw5t8gDQiK52Da>cbsfc}qo-+c z+rsBkrnAu*)^X-y2}?f)Cu4KSNf!3cHmGnU%&^%(N3OoIyE?3NJT*ISvbaRBZO1tS z$o+&-DQR(Ru=tJf_#ubXCLg-dst$B%pGEj>VEKCJ;2oU0r6eQZRJL{`g)7l?ymOlr}ws71QuN&rK&v^}7l>rJ)k?3%CZSfjlh0XuWlzQ-9$HQCLm(MGah|fDY9G9E2^X}}WPiRhBL&h=ucv~bSa(t<~O~2#1BFE^DFxbf23oj^Cl+wHe zSzO<^pEY&oWNTVSgd%A(!4^wa;;JNY6eo>aU$X$tr;W>nOL?E@Fe)J{`Ozfz7v4t3 zn9Dk12aQuyMi#YI1kL3;sV_HWEDtev?Y5*f`;oL-T?pW76JaG~{&=qSs`nB4D9{5$4E zW6ZtekhHkVGkl~2V^r$$jw8Rbdp1e?S;4`9ES2;Ei~$VI2u_6V$7%GV#oNu$E@PJM z+?!+z{MkgcpSgTFe6dQ!eB_(f#3zap{qT56>!!Q=Kpr!)WET=Q5DUjZzTpdnA7f`? zXa$)gMJi|hgnB9hVi*Y2^{J5YF_12~*qH3qGFoJBcEckqvdYN(J$C!JI zTd_ene6BDh1g@&?jKDxB^IDc8_>V5BfSgTGfot1Q7CmQy)qwu~KA0<5KIU*fq-Qn0 z9e#*2(8%QZ*sDPc@voMIJIb+}UaZRwN2mY#zT+kBHn;aJw77cpqfn;Y@IgjDt=(YU zKq0PbH;YrBb7DFIJ>+$mXht$<6`Y>UP^N~_gA{b&q9tRXffHo$YG(jx#d$7A70PW( z4H||hZhAsnr@VOh5=}L z(I;%S+S!U`rMAuE*v&nxy*|n+*j7f;Hj)oB-aQ*pNbf0>W9GbIEWe@;sHo&+U;>21 z{Lp?rgjqwWm#?O-XH_S=m?O!&u=6WZWT!9x7!v2F7X<4@?2AbgoPS|#rE=|2N> zD&IXja0I5D@E@w~bsxL`K28AoETromVW%-X;pJ-_5V`-wL~EMKOb7v-_1+uhkSgSLU-*UE9k-3NTZZIFdwr@zl{$Z_!b&%g^n44&3aBu1{ z{awwiZa1GU`{>4^{c|6$RoiIhGbCVNufdN4vJ6kTz8-dIa;RzD%Zxo%b{c1$e(`C} z1%W1Z7Kweh&%EP$CZl!Ptk?`Ff(R>i*WE9jA(&}Y9h2I0qwsJK&~QlXn;18ZbK0vO zl+SMQ%-g8x2ThLQ>)l4%3g@(~G4t6OY9(&Z{ImKR8d+I{=fsG-Ix?f=n*uA`Ubg!z zmvngD?5Id)`Wq$r0xNHq)`k+1rnvo0a!a6C+E^k=5ekw#+l64k6bf8hngi8LWN(Uv zb){LR4_oZTZOwHBDZ;jr(%{;p_BVBA4)UF?rPU^~rne1#zcgf9>g3qn7}gpeFEOz^ zR^i@SC$(PLY^Epgk#0O`{rev)DCMrFcWGgza+dzz5ncY*%U$cgPwra(Ka{(M`o_z$ zoglh&_^#^a`_ulQ^5KWmsy2_Y9Cq+v;*_C12RvIg(yrgjoQ-o&+w5#HWa=9m>{cYL zwM?6tcA=%TzPYGGu+=27uDU^TxvnwC2SgVmS)G};o4hO~N@AGc<>~gPjYCdgkE+UY zL4DoN0Rmeq6X{fey-;?ut)yHKU#(-=q$5deEqT?T6OdZ(+Flx=9}pn4T4(h%zV3?g z^Q88oG=0H={Q4AOM@@>pRco@iFsZ#ZKxb`+WpRnnqfK32S5PbOrFl`UZj+U?qWG1G zk;&Up4sPE^y-HpBsQ$}WC8>`9rmMPe-%=f*=VT!E^L{fmusp+ya6~OPt-Lsj`u<8? zmo~n=35d%N&TB0EiMc-G|m)93?&h zyqXcipPP-_;$b**md{MqL$(|}WRFj}*_PXqW71AP?3pw((%1hY8b;5r&25JkDD6lC2DvSdw#wsSOb`&snUTH}| z?NUEc7{TsH^&uyDew*>HK$@Cy;CMDw!f{(9q1~#N&uY*9xU~Z0&}*s9cBV1K)|gf>5!uq zf0)vP_Hu@lSAnenCDS-=B`^hE*whkk!|kzN?epKJKjAuXn*smAHB*L)!LkI~Bc>G< zuZ&qU%C-;zy9*?EAaj^lfI`opA&X%Qv^_CFxC87|>4h+-zzYG>h->a3_6s)7c&!?; zc(g*wqT$SCwbMQ8TXFT)QR7WUXfeu#Qaz>EH2;ci3Q*+tm~x24A3zVlG zS`N-$(S?%aH8^1vT#bo$V4&hAFi?C@7t%s7F_*0Q?#2MIyFK=--9L{Rsny4&l2&zP zzw^6(dO(zuP{^+qZ=1hlmZ&}#Esfc@_^%v%zwI!SnYfW;Q!at5OeA(6BtC&3fnnmr zD*!MTcSGxJfB;K?QDPk{+YRjohn$?l^^zuUIPQKooqAnwFmJP{ra2Fu^dQQ!5EdZ1 z0{IbKlk8Fnidtq&7(neyo`44?ZQxqaMsaoh+kmLmWX=AgKW#)>AXA z0X^(D4w*pk#YfGonB;0X?!De{=98lPAGa0{8fK4{t$e3t z#45n9uVwm!_14OQ!!ZjhfRuoHbb*O?yKZDXdnQ*k&EGFhF@n?RAPQ%W(^aCtiLh! z!iVgdMo7F&#nMf!$E?9lfUvD7^H5KwFPMW2~R_-_?1{f;={omBGS02KB1#=%s8gU2Gb>;YY!R z&zE!hESypfm%*EFb`z`Gz}vnG>7vBxCW)Ahu{)#=x)Jf-L03Bl?>d zkD=Xa`->gSxpa2XrBosrRbBsgA)YS`#67?|^1&gJwJ6CZN8IDDK@|ubkQzFJ$Nen0 zlA00;OaOd4l*ndokDop*`M59Xoi*>OfgQ5G>9Yrywy0AeOI+tY^Cso;Ur{tzvGiyI z-YK{!J)|-BaI5P0Z<6V*orxN~>Hu1_Pob<_8CNANm&pUdEB; z7hQ5UuTa3xLD=V2-8eA@L{aGnzmiuc1&x%t!xL3!LP7CcEIH4}*%j@d62-Fpb>&jo z-JFXclQSGN$P097RGu{`9)yd7G&5X^zmxF!bIN!@Eo8SkJsiMWEr%PU`iPR&enx4` z>*`2~k@3MVaB+oZ(UtuKr55ia*M|2JcZpEI-|=4$kY5o?c0KjAo=loI4QHw|oWH&( zP^-WDw^~t_bf2P(o}zpOYed@T80G-1Ol8f2=IymlSo8Kqi+_@Jge3I>lzE0OkN&6RX#taNA%`16A4XFy$>E zPmPh9xERv)YdR3>3ax^+_;>U-_+5>HA6_9MwKo6|Hus3_*s_;*_s5c-ZYU~dF3d%b z7w55pl_^btqR+^zT{xBo@@bkZ(ySor2o~EyOFKTHls62+b8UEg$Qqg#}zx<1h5(0PCwkirFfvX1;yCWxF^yx>{}kRk+f3`=diml0MMHFFJW zO_&MmY`<7yxSdsVO=eWCHh6IKiCXY?LdKD!rvZ7pWf`=)o2E2{l)(+)&j-(u*qzl3BN}l#)bxdwA7S=J^q_0%#3A<;l*>lX))9NNa=`%b@RboJV@_w) zoFn9ldoz*(A`KJL`XR;uN9)1Rn;&sQZyj}x1XPIVi=6;?1s&t0xPBi$=Y;vUS0z*}KJHIM zUaOc&QnTU0Qqxz5WtXv^fXVaY_tG4-Ew_9De%5NN#WC&MWTx$C2kjj1m z94X13>xgvby+OcLDIpQVRe0SKM0F&#!)uB%k6rUX`#Dh}k6NsNR*myJQXw!ws%p4u z8dGr#9^U+>qu*B*hnnA=@!!z!f-Sb~KRs?)caDm$owHoJ=nZkq26AeCtF{qaI=BEh z*q>XwkWLFW7hm*Q+PLHKvGvt7zZ-UK?jAB=47J)2$EBd<14UDGWMM<%+ny-42N|^I z#-Y_#PD@j1tr#D#eqz$_8SPa#RWGLNz_i_nC&X=IYDrVulFb*;mZ9l&LrT+js0VSrka46;txL6tlb(D2#b7CsHytrOK;26ELsSWS z6Q%>oR8J5)45Y#DBu?zK6=E~Euixm1CE1=jny?K~?tQd(%76GOXXLC z#%ZEDO0qa!t3}#Y*@B|?2+lKAHbooEOO~JufTH;a5g8Fi*y%>Wb!w|Y2Q0q@V*k+o z1~@2w|HVdW0nI1$ccEdkou~gP^b0TviCtgOIK0?)gSY3QC4%vKPuE`s)r+K=Hg0yl%yxb2oyPQ#CbXF_C1F2B~Eacq_Y zKT{)(!t(^Fum##HZ;J|A#9nV(g&DGqp6wl@%F68pP1Wr#LfI2>fvh80P19If&>|2^ zo23G&R2l^ZUd^iN<|~_Z{5$enOcKRgQn$CarsT(kl$XgrFkX53==&dODFo&9bZ1Og zVah0;bp6*%ng8{I^7`)+l-K_c1?5SVWkElt%JS>}J@+j7nXj~X{`H^xK02&bbI-uC z&+SQh7Gv!D$wNL1h`8y|acu8>&*tKQtD?sVF$= za=g{ztQ|8u-b&Ld8XNao2RW2X*Ho)b61g=*OkZdvZYW3zUfxibTCz+Rr0dvy1s=wel&adk&)qVW2Y?8t?su-2Y9GY7HshNrui`7 zqd{ZpjYbI;#J>3ELBl5ujg$2a9*c8`S|@{0jilFCjlHt!pV<4Pn0VRE7!jvon3oeF zxarnZ)>Kq2)p7B8Yj`cWQh%R3wLB#@%l)^;#K0)AXKUnz8B=cW{BX;__sinUdyf2$ZR_d0OHx)%x1$)I>3u5;W8TC zwl+Vt`%d&KHZkDL)t1j%NnRrA=f{IlpUmBW=C6~A2}~GkS`OgGwaEw4In0g`!Mx*A zETisj1r{PUw?QK_A(%lIs*aD)Nz-FJV3+7I=p$OBiu62cua!v%YJg_J%{GGK(y9Q4 zDHKLC*w`!d+y~mOd=s!+JBH?ZT)vx{vx*Tes=@1V2zi=N{!#S>bS_&jqEF$>Eym5Zu@^{&z;oT&Ze`fIvGYa`YtqE~a5$Dw}BF^EpV~G@W>zRzNKHl2w6p^N8BK7~=V<~hM za(l*pF`v2*w;65t4sEOfWc9dz41z&e@!HQ#isjh-<5jSam$SkyzIsG{Gv($5{}bfW z=q$eOInSsFC&3Ex=)>z;E`4K83MxGGl4EHRSPQ7QaosUsHRzwdp|i#(C*gP5lZM~3V_mm(G3?5!{9S%KCh=(OxxIhgXW_d0GNfNfAc*50ei;0UV(oM zs=C(ZMty%ROw1U6hcIZ5tNa^|5*CU?F(4&iVg^Nxm|jj0cfj?|^~oTF{FNnoJ2u}iXpX*3pIoZIg z_&Ez6#x7;J7X=LT{PJcJu`&`BO$fW3<^NsAHZFX8cwza=7aX+(_~S23ysO z_nvt+;#v_pvfHRXM#@M>%-dMTH7h&?ogwzFKz;s-u|SgRwT{F9nZ8>E$EFfX6B;) z*)90~hbsT}36p^cjj-Cph>`vzWdL~gZ` zE@v=+9M0ldi4SF8&%3LYu}3Yp_4s<`4qn_<38NNBgLeAH{b8hJTYA3=+}NNYqj|t~ z7U6So2Tzx7*vg!}?ZS=4(+`~=H#mB43H5{ieUr_JbA9@2fvPAZ@KWl2yog8dEwIVyerdRlL1J10R`qTYL zV?h?vw=nu>M>Uvj$YB9QP-Uz(EX8^#9}Q()=zEZ;Q{RxekAVfS@xez;(wiH*bJYr^ zCB9w(HWT9&6 z0kg5k&47qusKuJiQ?SQR6-cO;vo=?|)kRU2(f%`q9fCOyj0p_2kt-lgOKz0Y_?h%m4tHltv8RRz;OZ5n4*rjvF#PG%;g{+1 zIzm49O=%tdU}Ha?Bpo#7LB?<$x&x!3i)h7bRx-BN2-5UGzjz8saD+oc^z*w@nFBy1 zfEb-jRoSuO%pSR0u2})mpmhwIJU^zPW1(3!HO zR6O4|MDqX;vM8$v`ia=fTIY+r2Qw1m82p7=ZpCsjW7V*Tq{t1-Sy0unjvAW$$L58!6k$DDWx@DL_5f=HJ=tN$h4635h>U;8EZr5KEct3Kd?^DTti>8IeEI zmrGJ8!->9Kh>~7m&^#lo)ZQ^|It8=m66_!lQKwpxv|+FK=Px?pw*H)cOG?#rvTq@@ zkJbc-2sg6u!R0o}`ap7T2crpk2}Cy!nftnB%XY+cA^^oNj=9m}{bdW1+Ol>UHD4`? z?S_&0v<~~4Rl(v9(CohRM|?8XMVycfBbURcBt3p+ZtV3B=3+cqQ+=Sm0jw+UXQ+4p zPQKLF{=H=ruD?*W3%Xp8-Qg7JdXi$(2!kD@50;od5R+T;2UOM&M+-h>ag}kbjt14AQtUMD1`~-Jg_93|X545g^x8!&vjLn}QoC+@q5S&1zKaKqMY)-ic=gX`*qU z`r>ZKK&@wI-gfir7rYOb@w>EkQwpJ--3@}=*A>yCA71Bz4sAqi!05o@(EgOd2n`Mi zw9PA+>9|yCkdRH{QzD&3=G-Ld8dhVX9YQVJ4@u91c{T@mhm#VjT(}nH)V0Xl%P(#M z_1*ZS6EP2st|Fz(8^|hUNQPt~dJkk5qA8_)2~Y0t2%1d6gGFzn4am?-zCp=`!UB3K z1u@993t~JdvfDXluxQUh=j>q28!7ei!Miy6D_gHY2Cu$7oAWh`$P2wqalCBg68(fELJnBKVb7wJ6Q=C{{=A|+O{m)T1c)|-zwrlhZdiwINVxU9Uc7Ef%Y z4PROfv1^nJWTXx38`v+>Nnr_;Z?7ZYEBkdkhcZaZKai9Uk2Lrt4*Ge=-HQ|AqxA$% z>CcEj=rPg0p$05|n()=t>L=#=^5o}GVhsi}nG`$cl;I>hgoyHTZ{tA=3F5{L4JJSa z8V4n*U<;_t;-17d`zffk!OGs<0-mpY?XR-Zkhvt;uil`%n-3dCExA+XwqX}KR@l2Hgyh#XzF8X3R1thE zLi0sYotf3D#Xed3X7U{kuhS}%g>q?yw5TcAFQvjJYkPJTcP=$NXOX-%wbs?GFv3$y zy40zx)TP^x;{LuWv4pP!D{~&N|3=R?j$Bz}`ADcTZd7JToTiS>tBihq{5g8$jwnt9 z1v-bAX7^e0zG1T$uf`d$<;%-ilGq!a(@%$Qb|Y^~9&YPfvXM zf2b#3aLd2o>F;}rZiE*558d?nL0|hVhj)4$8yVvGk6uR0QxBh-JG@6+k6EEfU!>l> zIIQYJ*YvijSGBSy$p2|_HSVv}Sq>d7`4U+l;l-#l z7mrq-jmzXk`FAs3YN-{t7-}}Q=07t1#Baa)QbPEyCMb1CLtwL$sjLN$Gu?AjolH~3 zgL(#D{yuJ7?kCp0xNlxt3-Kh(z;GiP7=It_TlCATKd?FfnKt4 zCfchLtJr(iLRpp$-AaVK5tp}YCa9rDZbZhsd)5X=x6t>5VDFoei=XL^EE21-1W!K+ zv?$X3;d)iX?l>dMjj}gZqSE^xyJ^N6smB`WqmA<>zx0gQdwyRM(?bgchA@rhx6|-w zkpB6}uc^y)+VR*B_mCxM+e1Iq-3Uu5AA5ga*iH}Ejt)^|WsXjs_(76qYlLK(cHo@W zB&n=G;8|Cnb}be>7Pd;j##lp9si zv)Vn;xYGF5?|kE|{X^Q2K4BSeH-li)l4yJ2OUt0%Ae2abEb4%jG zu7_DRzx6q;e4&-5MXbhD2lOq9E=V;tK||*ep)m(iou}^~!mqAty7ro8$43eCaz=_R z(ia|dniY>F{9(n0gmbrv%X1qK$Uj-D^Ui44_?n_K1TT8A3xFI!*ID^7d%?=iQ7)tE zpmdk&&hi#@8qt;euI76PYOx2{)q(UFUd1*81j9ooVuaj_+4H;MzR&qFJkZn5FPf2m zeX~#b<`Qm5k#9~|T(A1XT3x$+x@~IzMF%C?|6=a;Tu~=GET_l)%9jB`JnD7NWiPXH z6;AQy1Pn9TiQIwNCG6CBPjoMC>P$w&HZ{oGZ++|*58ltX-TnX;cg|a0SY3}VZ;&~D z^!tiKSRv{$iuFdL&oEjVS~@@lZ!e)`QX0L|_1HIq9%ysdgE^#}w!rQ=)aIh-Y#14^&P~zdKu|dsXhb*)dVd7X+bNnc z#WyetdB*^45!}*G;c6u(t25*NbV)tlbGtCFxkeBFya^Y;&0f$7y54=?pe>vQ;=?b% z&p7WkTL`?4(&$|u4A#z1tb+Iw1(g63b~7nflWyUjG5~d=>x*vNo!OR~oobxeZ(~t( z!0tH9jrUHo2vfUjY$Pt)HJlmYLWjTnL3s(d6FP~W3-kg6<+5e-9Z+^|a3jTa&3sB= z?;3=n=VF`)!Km98*Q+q=wE#TtDMw(_>~T5n_(|26_IGo{-;)3ich}jep??62Mi<*h z9&EMfH@YU_-JU3Z)k>bwX*PovkKU6SxozkK^0DEGN6NO&m^v%HTLBS=)*h-0+vGRa z7$^kAjD`YssQjq6G0!M8bfi8eYvDsG0d6dtE7Tt#OtZ^)a5f9mMN}Txcl15$<Wsx$Tz*aWQ7?DJ6m+q#lIV#?$ps@0(cD(Aeci1`4h`3MVWQ*(SrJ>%)jJsb zg^$&=Z07^LyrCh4e8IgK)uAylW&zF12JMM}*%qXRz8yY>-f~6v_1D&BhSd5w8ewdF zK7Tzn=Jtw{**jnV-V0ZqGOMshi2N_{3z+U=`Ad_r^CLUM+3U3?#>*SuKF2EJ+Op8e zlpyLvY__>DXC(NgDsLevK%G`rq0^rkr&gLqOK!JxarwaVWp}4)Z{Yd z9o-z;i;qR6z0-DX%}VX2(-~kWFmsu9;!&+NaTK(=e#1gAW!5g=uyjs=1qQiklV-Z= zy^{T3GcC#gcpFzj7**A6`S8jpn7V!t%QC|jIkG@92>#6?4VLD*p+7l8e9spYKLb+~ z4zeeCuVZeP=^40Xsk=%A@po@i2LCSK@TEzB$;-QRA9M_ETxb$ke?dsU26X9HtN*{%G*rA%SRu#P7nRclMSVvG$1$^5xg_^S|ImW#DtDPyRz#wX^-Wo1c^0!#`) zAd2A8AuNz@8!&M@GRQSlI*3;UkH@VSc(NLCtMM9h*L2FmPGr!A8@bBqNw2=DWd@~- zK?F`63?`R3fnmD_8D-e%5@c9GPw#X_o2*$`J2&|?*uTG4YH06;k5}W1^#-*5RHi&W zzw*gEpmx0fpc!n((wSv>lkO~P`7O)-!UazM(}2TD=PtgfT8&BbwbIS3F5IBhx}94Y z>_lpH6jd{-Gn!^pe+FnlkErD`kZg&M1*%m98OMFjxIdw4{a;>E%N?yKJL|vy*}|X> z@)mrq*leNr^12+(iZFn(jr*?h65VZmv(@-rdt0qb6(rkL@@1Hg$2!CXzUWXS1F~wR z4R`&9s|>i}JT$Mk<6mxUSkP1&mGYZT|C8kbGYTsnahIa^TLQKstbk3zm4f7(^eeU- zC^z zM5PZuc2n1-diG40q*a~g|A{5@X9fsr zKbgXgEq--hGOM(I`%pI_RX8aTiFct9l?NU?=e+9x%sqt z*L%)QU}qg6V5wuD;{ufiIE2M!+S8($ENunQiPd-ZJBdc5J=G}3X4({!^wQxaqo1C> zl(NIjnh?@EjUsf6hwmQ!G;6!DWRAzO%GQq3QF$?|GDwSHlTm;};qIo`(3BL@OA$?m z-pN6)9V^{BoToj_a@P>AJV3v4Si0Wm_{OZz5I4Q*8&fh&Jt7}EWrhm-=4d2twTUR~ zU;k<8@T|{OE@^FKP>{7m$s>viiyl%|fYbYe1^dBtc%$ZYGcy)#nHW?((8QD{YCiB)Q#8(Q_iJ8kE zLvkbWgE{l+id}~f-}*A~MxsZ@TcM9;;mf+#mS%TZbxFBoMyPlDXxK)BRNX;CVpL$Y z?T$z)+vOzhm}hMHrcOPiJ;LNzZE==Ei)UL$xqm}Vgsh@cUu@weP0NZiGU{kd4vvV( zFe!fC?9z1I#Mo0>5Ue9#qn7U=uCJ_>B&4-v_(aLu>gL_?)2yq@F_Q0-x3=YoZ>PNW zdu!()fAb();ObU4(JlXU+rG+Knu6XI1EXyPmu=-<=EbJgs6aD6_lwK%jgzQ{xE|Ur z(<`|a_STEhZ%RoG_E@4Namzds(JNq8vpasV5EPZgOPq_NvyO&buhO+1r){t(cOUmw zDxFqLp_;@!OJjZi9p-`9@K}7GH27!~op=0Ux=;Q0l{iTES-GRwbg1&<`Yr)uFPS*X zkD>$R2lPnyD+*^?OHwVhzg^?JDYw$}dexKY92{obJy=X{q)r^YFI8L1JNOIBA8l~BJHjS&pSs;MDbBIS(*QcJ_?2~!e?2BG zk5-B1fIy2?&D+h3qQ6VEL^8B;$a7n`OB*7?yH2oEr|}QN?)W{RgYx7_Gr_*{kY;^K zBgvtXn;Xa+_Mhn6Vzv9FFPlIu53lNf28M7`dabHy6dRS_JWQmPNDisEFX@F8f7%@o zqbnB~Ja+T*a-pLx_OM`}jbi!M4#vXu^hmb$nE=|*T9UK7zYK#u9x}EXvOX`zPI_6$}uHU zJ6pwAHUit+RC~~@@#$|cg2&N4aOaVcuZcwp!)P}kPN_(Ie~!8Ug{QtkWi{mtOuFTec{Og}Dd z)oxF>&b5lRy>g`c#4ls8)90@wvol^{ZksD51xyv=??GD|EsrZle*5xUEs=n!7PfG* zr}&RGhm-@f-}oEpWVsmN;_spXg%z@i=?IHK)ywyiY5r zw0!0a@9}2#77S1^@!cohER2ot;@x@@+dTix!%$ezMM9|8Pk{;3I8}atV&%?TzdNY& z)|yd+Mo^GA#0*sXUF*MGd*%i`<@onj(sf8IqObsUtxC3-$bdCaF8~>k|3QpG=UbKc zF1>QkYEB*VDvTa;tYpVDw-@`rGNAc4m3Z|?u^vk6xaIqU${^ky zCA{MY)3esNZ6fkxAj@_n+8sY~((mSbX7iwF>cvm#Vb$X+oQDac28DF43i7}^jtzfM zzQ@L+@W7yGgw1d~5(wmmIQU-;an9gA&Y01m&T>fjf;!IZ{Fu=*Ch{e%#c`YBJy=tZ ziMZSq?3?UzcT)wc_`A;haMoJwDav39X;J#sb*25vQ@@+0zEjl~-OiTLu@9g7x>Jnm zgK?2wTG!#xh1l9sDD4a`G_(+9yrD4Wd>rSbQqQc{_4Rujn(d5T0!I&c`PHkLINxUe z5q(ac_-suki`2idVSqHvuV2nc;>~kWt({Y2WitVjC68C643-yEmi*gPOUsKB);Qa- z+*rBDBRc6wSfbt#k5&wt7`x|phsXu6TSL+0hO!e2dso7q@SoaT|Lvk;Wp*kMu#Q4fid6`5 zyuXsquLq&cb2U-7Z03_TbC4)e92N zJ;dXInGeGTpv@L0munU^$S83pOiPgyZ{g_s>@g!(JavJ=Vn{Gpl^-aYE6v9% z(a!Ps$EaQ&emsLcj(n$D8e7$e&*=)I=WbtNvIgiP^(0!EK)O>3qijOyYDua_6&{1Z zX0*TTG?{$=uFE*-HBHO!lU7-(-@s!n-dy?m?+W&Y_(Xy!SgWxfeKkTn^}`Z1V0YiO z3S@Z$_hNtWN37v)!VaXeFq9MA{72Wwvr?BFovR5a4TYYLuWr??#X~P35a5OWkKW^c zU`gu{jq3d))uJMmtEiCjOLy)?@L|csJIT(?&FxNW>9v!o69>F3K!G@;_szvPv4_Fp z^k1bUb(R~VLPW$FibewOE2UaYxKU@ow^upKozMg1Sb==_81y@7$-zua5#r2WyWu$M zVAO0@8jgiHXZZIvOIAny@hFI)YHUHrH$}ZjlaFeOFBQ8uN@BvYBV1A3JGZ^=ZDU=A zjfJs1tf9fgRKr*rZx!`iBXVa-!^-CZK|^z@jz&`3o2VAGri=(5ousPXYuZyAhR5f~ zG~#o*M~2Lt-kRC#W}h`HXhJ@CzJ~MWKU}J*D8d^oqknY$hH9zO-ea2+s;>73#$!e+ zqEGuiP}vP^-w|5EV#W-EHSWo1e$j`XqjMf5a;D9|i?cdQ2cjayAt>g*ZXYJr$hE?c zOe~lG+9oXXC#dYN;S_<-Mz9c)qL2_ z5A#l|?-(`~+VrNo*M8Wg{JOP$AiLHdf0Au|^$RYNDz8;jshqiRZybYAUp5tfdfr;i zc-C+DyWOSZFPvLQi%dC2=&)*yHktJB<0E}$0x!&sL*IQjs(fqm`xeWim^$o5vHcn zv^u%bzRfT5{AksSDmPT0`#mj}v0UQgNx7s&ocjx|)-I{~Q&CaAKqtx##;7h8HmLj? zZFU8Rz*66b8xi2SeK(;5usbbzRdqqNxKjdfF0v1-kwKPU{6PfRokm|?qbJiME{Rz_qw4-V8@f^(?~4^8~3MbI}sG4VNt$z`%6dc_)+?JeJ>Z? z;>~kqY=SA69ap)bc``TkMGWe82lQltCWS_N>l*oh5iLZ1ZZPT-kPe z#M>GB$7qX~y<&eSP_tr{-UNPW3%h9vdP%nIs#2ep{(YW*`G6qH6Q^QF6h!@z-&}H6 zvcaRYrDl7XphFn;SsyIDB*p3y@RGwhww5~ z2v|CnlBOQTiGW9IRx5QFR1`vrGY7Uc`HEFzlv9|WU;%}<(Mp=kmyHaCf^5Qx6`2i- zMElbdZ|=Q#sI^WzLM$Co*)wWfMQA3%jpB_+M5c-+WyCP8F?^E6S00)ZF68Tm&LED>D zi1jwgg2g5sDait{yg3E^MoN5q@McMTtw7dVbWtvlSNqhOc($kMXnJ>~_=y*42nF+B zHfYs=mA>zQsg*eVbsvOG=6JbBoqM2X9yTw#`k@ z0ai_lfkr-HLy>;Ub+mTbh zn*JZk^%g*`w+vA3L~ql}-G_dDFsQ}ya9Zvc_cx6A^LEv=?WvDO`hS$~H&X1q>R9mE zPZob5amffiFygz-;=!8V_qZ_jMuL~COj7Ez{DzlQbj?!M_NK`pE=3~$E5j_xIMJ=? zO>7xRY>~Hgd{G>%Q`ga8>HR`1kvBWI zMjGE)?jiLtmY`32fFBCJcL<;n_MfzoafrVEw#3YQKTFO5bO+xGH{DYU0V# zLLCFe2`S(lHmotP5uKMSkM#JurKr~i9B?OWz)|zHs-iF)RUV_iLusFYEektKIA+n} z`&xoyO>0Yll`YDq;baJ^AMvau?%)kNuWl+zq!?=YOtE(%z^N$s>Zob?DeC|nn9oVo zz!@*H>Iw?J`1+$cJFQsdTsZN8s281UrsUuZKh2(^d;;n;(Zgwgf=`JeXro!)v8TdW z-X7Cq(D6T~8?ag2&K_W5+TL#|&`2 z$RnbM_DCscXKlQqSABl+aY(}7)W&{`J3t5AJdOJ055Pmq&KZ2f-EnMH(awRiKT0vF zNKssiJq!rSp`%~7G?aVn+d#K8ZSF({-O`?4?$%me^l(>IHw$%OIl1CXu~t0+z477h z8q*gwniAE4T2=6n(TSEN+Xr-hODon{$+|rbyY&9cjP?61Cvzuq(9q^L zGgnb*qhG`Jv-jxqic&w`p@x4CzoWvL7KC4$F3(mvvm<)G`&}C;HafBLVB*P2q_x?D z4@8~5m#fR2J}V?|%Ni*>oeV&$neJ-rdp{>FQTU1dX* z_j(QLtv9Q%J1)>o=aZGX0v&d&45(|gt!&BW$oa{k)C&(4oj2`?n>&Dki>+$yhBT{q z4Dqkd&Tv?laheKrqlAD@sJUIrwW)K_<}GHV!>%%UZ)~Qxtq1VfQ>ja2^43Z7FZ_qk zZ>|AQra@0V$RltCI{CRz8&b5v&14NJ|BX>7i!$5X1}NrYRIcoiSSua-oVN~;rw5Sp z%G^+n(kae)>kJe0zk?g$sZ;}~>`IT?1b}kcHXAf~j)ePl9S^oQwABw416l~jWREoT z2UGp6{JQig7aWXmE)jWO#XeMgHwQ?Tj3dh~&pV-sE|Y(|(h*ZOgrWQpI?aGH3HCIM z4P}M-yRH<$1(F$a`>UMgmM{3OGDKJ3XKF8WhO@v^z58LU9)LqmA=tdiGQIopw6lF{ zj7=R2gDfa4NyUPqR-~`&q>#L@TVF9mhW;4h5YYJibyc1kxMZ zHJTlKjHf1>Jl@54dq0EY@h%tOJ#08VL3AQqHw%FcXN+Pkk2<31x*{rV$b37Po%)eq zWp*^+ptg^w&I>UWVS+uV5nW~5#Lll2_xVleFgmkb<;>hMRYMRwgF-)r6}#M$jPzU9 zfj8!U5-8pN__b}~&64kZM)#`MvrSZwEnLlxrCMuuzw~`T3>+2``@pxrH?YpoQGIp; zFQY%h3+@Jof92e1X`R+CMP}i=@!{mdbckE7PN!6AweUEIyJP~!mG*BVBNsg~1e)}3 z8QdAw0b~UXz+ozj!7>9A8r8cdMX~xQ?*eRw(eHV6<>WX2ZXs(C7H7j1*wejXotX;h zjXjh*(2b5#m3fUeqLgj;$UI~LBECJHU_@dM41Ja@M#UXyQmHUe_TYI^5PdptIm3EU zKX#7}XkH-dQ?7ax&0SXU^)Nl!@T5}(=62U;mgxZ`t+o@}g0jp>54#QRta&$IR+%eR zX3oFD=wB2oD6`y5CyT0+RixFQr8Ic{T1(0{STF9unIeC4S)5XZ;;5KLN&PS@sd+pF z%6n)K{|0AVB`wWk=wUYkGd=OPd&UhaCH|jOBve~wI=j$jh_^^S=+HFa_@7y@2ksDF z#Q&z8J=t16#t~0ZGq~q?<+aFEXyLp$MDmnf{M{0y`KTu( zZYbo@%BqCCP%A`2Wwz*Gk5i1q^lV3w7U{w0|Do>9<7!U(|M7cf7=xj)Wv|;ZLX;&X zEoWv3p&DZ>p&DBY5=wiWF@~n1WNVaaG{{y_DWsZ_gj5oxeUcn$Ioh|ge4nrDeNKyU z-@p6wyZ^c$kNfV-$?070>$+ac^R;i6tCKc3KLD6Wmx8yz)i`(h2AhJ^MwZebV^dqZ zB;&t6K*-P-auG>ELf6W>7`V|3=$dMf(w6BIkR<~dGLGfcvf~y;((Gr;yRc{I_KkJ-F=Q?2 z#ObqzG)OIzx&}152x*xxW-mO~w9%Ox6ofpC7m-5&AR6$%MM(Z6J)qu);UI|;O6lPC z)rJ{X1VNdmRv4pE8fX|Me$BEkid?FYZybM7a#h@xrgHXcW}Nt z_DH&NRYoZPhtb3W{Kt2pSQ?dM$sHxXRSH~l1WU#Z>;@~=%eo9m8K)0jn*PxX3G#0M<~=^t~5VP%ib!zc!5SLJp0v4bNf*Dpa8qnY;> zlC@}*`|nX(-XuG__sG7tefNLJRw4}Foq?rmW6F9K&v;<|giMeLzOU&K5$pmV&j?}< zzlk#%VJ*-YsK0@l#$)4Q!*SfhbVQex&<)fm3X*+XarwWw8745g(vf;wkRLJ)fajj} zVVoK`1?12oT0V@C**gC_N!ndl5dG5$=rNFFq&Sao^Cl1r;yPXQL+XxbscY@7EANiq zchfV8gp@OJmFsk9?40S*%-`(g0sJnldK!hUTzZvURdbrIm*9hyu)8}U3ARCs26^@c z7C}FbILTQeL_I+YYaC$myb;Aa{{Lbg1~i$4-IagGe$_+H6k7h>{5WRNNVy`#Fs;GWe8O%eF-vTHt+C8=wZQOd!ZJN<(wyQAAfF~#?F8KpwJ zAPjO)j(oY{2R)?=WpaIh2;OJVmLFu4px;!Q5c8KjGqMXTGP%`TILi_;VaR+7n^)$z zpkw8HI}mavjejDWktJd#t&J-sGrGDCJ#3IRc_;%I6;%HO0BkDwxOGGm5b!ScDUI? zrN0KforZS{xp%(=y>-zx>)fIu_+rVV>T7}aQ46_MmNh@-YaD6bX`XG|x-_d%{q_ad zZ(KuDW`7FDxxmi%Pv$FUD=l?vxjd-n(5b~sAuM7*mq^crUJ!NBZXL@dWc9=e2^sZXKlE$YbL|aU55EVwJ zxOKEuT8Z;3{mb~$_KY%*wxY^>1!-qOghjIUZjWCQic(`r#5p$(d}fRuaKGY@mb<@Ngq^B&pKl*|Sr{ zzn`uo+(X=v-C*l3t`5q#lkO@KdgiCx;Www}2rQy9_9lr$saL9-i>MDu5%ob4rtq8V zqW76f+D;02DXt;)Lio-*+cw;bEqjTaaj~z|Rocl<-O@b4Ga;YvTFxfY9=ucewb-xX zYyXr%X5Dg$dAawNP9rySi28`il;N1_B)8IKCMUzk1TAE~km~c{9r-;32LWh!(eQ!g z8A_CrL~HVkeuG;kX3h05<*xGR?t>-2D0BBRrP{Th?l4EU3di{wQRZ76 zH@8Jx;(iU;!kye8E4bQSH{jT!{r$rLuUy0-NdzY4ef(4Rx5c zbLMk2&x>MbiX3_&fHDUt865(G6O$mri2ea@j9Bt@3QKbnuZABCDM-`F_0d|!C1kTh zEFof;LoQ(XvEnzM(v+R%c11z0mQ5W^XT_C#VJTE`L$*m^hi!qHUFp84-PMhQ zo}CJm1a36HeK+!%@p%RFC$$5-Fmn2Bis}QI{p6BI;fT5v)n5pk@z);ZlC9!uHlhAu zF!E`m{>jM*F8tPVjXW(}j7n}z2Wm6lGZztCvnu#8n-O)USMI0G<1(h@-t>X1hj*DV z@=P+7BzQQOjYd^CIMd^$n~DVvWJYbvIurLE590ADUk!g& z_&7Gc%foZeegV17%IZ(SdZ9X0Sq5gOMaf+dPN=gnjeYDaTR|yoRG^4ACax4M!CxX9 zhDvNR&cMwL(eC`~CI?tK{TtLS(%C@15?yh^RdDKBu)tFw`)*0!VJ?DA7Xhcg(>a#D%7OEQQTSCJ^03YSyKp{cAjS@4p0GOJAb&=6 zr@ZOJRade*xBrFSxDBQq^0Oxpi`7Tpl?N4XaI%}^)MkU%vk(uML)l_TjpLu{PaJkT ziEgKFWjmgzTo4PG@E{@H)+?iPmaj5?>eXMv1 zC2Q6Suc?O_;-PuTpn$Hv8t+GlRMv!Jx^7Im^c80O_V#kU{u1$5E`Y!V*-H3V zLb0iI0b&Bgw>|wrZr572-PY+1O1|`+61wd!Cb((PSl2n|=+&d9W+p4==lFspV&V!S z%2`&SmM&xig{!+n0T;3+=PQOG=_<*fNNK<3ux&39;%#1w_*#Q;p|)JiaxPd$J`1%{ z3bS0eBmaTu9HP880_YhZr-BR=goGVRCuhEJZXh?;+UaYW#rxh(LGn;SB&YNJF-;fl z&rZsH?vh2^IxGf{%vG*jBXk#$B!BQwR>g+o=&e-ifJurcdX_Fg|z zJ4018h?du5Lx@t)*U23A)SUwH2}#r+jBiK$%B=&j>BwLE16P{$EQYPXJqG78?|LMQ zDXBh>dT!Hq-={5XR`uUCqc?Odpq$jO4?vuwbf5rBG7-S8@01?;0FU5KRd$fWu27Le ziF6J*Wf_8Jfo7KP%4o}T)(ml!;1K!3CHVYtNU%dS}0W*`e$m7shhkoIyVfnSl2 zl<{O2Xlxs+2CU2+jN>TY%o1Q&E{xdW>C0aWKb0T=gVfR|Z6nXaVcz9gGGg>YX{Fc; zbg_&C(fo$cPbYd3A>R^o?f=wm`Sjg$P0YDTIFO*VD=oFi37gL$7R3fN?3ne~N0+XAYA%W3l{?25a$2Kuy& zRAF@7!zPfg!ks-@FO%<3B;CPmAmCBhVa?~mt9@~fO#Xpy39z!{Bf0i_u5YisfCj#! zYlcKc`qM}mv@O4qLrJ}nMXAm8WtQX1JaZ(GZyK6;V$WB5wGAtCL(lV)UBz{om5J(7 zk;B8bT346Gs7Z6LhAe{w*m}P>G;pB#dzK^@r0jBc_6FtuAO1**j`_MAOf0{k`K=B; z?f(fA5KJI5seJ$-PN;M8$3%{uE1*fc;rHtAWin5u6e_PdPEXQ7m4V8EEUGf-l^F9M zS{DD(d+&IIzPz_`+eRIVn6b|Z$&xZFpRVV0l=e3x>f)VcdFb7Ue(U?0QY?RJ8@23H z<01v|Mz+6^zguqc{wv37mxAev7rf`Q=a97{KjJ$I6-d-RV&e46e-N5kaASYrzYvKM z{Z$(S4wXpP4`qs`r)5H|ga9;H;xaSnE_J_ozrwhOr#O-$vcFp<|1N2c?TRq~D{9Wy zW2o@9076ode~i+aVMVXz%F;+-lXf{0&4iImd7>(nTJ&tqq5fF1kUw@nbuIK8nGx5k zhUr7W7V;LbA~f~~s}Ygd7S>C^%Zf}sh;xy=8_zp=3vV(L*u}gtnXEn>N8b9`5_P1WWXn z44CdPWMzci?{abtbxxfQT{!N@V}E;XLEA6QKffI8i?K*#fwsrOdJ%9e3(3Wve8=Vzuwa*F46>jvc)jltfc+I0s?38s!DCPP*xu)yX zzu%(w)SiEu+|{@EzC)_(bw}2=S2@(?2`nAos!mW?!-h*>%^lg&q>Yk>%6tn? zaeJDcf+xx@TYf_w?~WW|1QP*e}2y7{2tlv-EQjtaL!~&wtvy< zAzt;Jk`W6=M6G=B^6}B(Zp#Z_kNf9hVN|TK+2lRtuf90Uv#*r=c*|A|td|T6K^X81TPAPUt(kcz3h*U3OopD5Z;JS!*gK>h2 z^5+Ro>M1^{O`Ssb=9aqC*?iqQ9r>xtU!*P96(vVlv=v=Jr*4Jk!Ol0V__wb&hpA~L z2WX9duKN0blKCOE7|VbPgPa*b_2CAJGZTDHnIIW)sdn^mnpbabHzG1@_-M7FxEh*w z8@DrY_0}*&`j?gs1`-?k75j%9H4Xt4F=lLVIQKJYTG5_@(i62eYph9*YM`FZt;}Z0 z*#3pjUPI?AI)uVj*rBLxK0GsgwEFrWSj*=&rALO})J7)4g>4bOpVJU?^eOB0)2M(= zA{Gye>|V`Sx~Q5Bu1B+LjIU?uXt`bU<2?&)GHlqYXl*k7guZ$V+D!&L49|_k|9}R3 zEOTCN?v$1;>kw|s&E2%cG_di4^w#xCew1X(>q=+Ww$=W%^;(hs(%D}A{MMpVx#dj- zSERdJYbqO4El|lJR)|)}wlKf*Tj{%UUhUpgyHc?${}+pwuDQ}q%U}IRN-ZKb(EsfY zWn`Y#N?rG)J@dyAr)NA;cJ8hI4rOT0bW209I=ezvcn7Pzx;4?`v9a<5j70v< zQIhofCWbQ)bQ?tPipTt1k^A+Fu`#O0gQCL}Hbz(&r|r)OpSRm=SJvknw|p(wKTwsC zzy#l2NiO1CEuqU_cYPF1Pbe>23qM_!#g{)}$wbe-eb!Fi=KuLd*f=4c(Oo1F5J4JOMduUD=^=*9CUZOU*PxDOpY~P>HP)Tt`&1>pl@+8qREH>rh13UwIp#bvDIg-v&xQuZ&}g0& z9_TyAvy4O~SEthKXpzZ8T+lQfX`R-_`06b!k*({C?;IkVDOb?RE~oskvRYB|Y#niq zkddoB(m*j{+lr5LW|K)(PIzq%alCyqQdMdszMu&U{Q7T=z$74<>^Md;80=Xeex6cM z#yECZN&3^NNRBJhOULmt8XX={CMGiJ%v0gJcRaKkRB&+l>DSFwp+3ILPhY4Ew7F#V zSR*@dZDmAu+^+5Rueba){i0;`@U>erC#L&`n`%@TET^afCb0LlxVbYorrN?_j{$L> zCMKzW-=BECW3L(@qnSlkU)8;!jk#)oU;@mY#$lVKj1I(pt4Ge=WAI(eA89Mrt<;D> zy4RA%UlH~2h)A0=H11L+Fzk#sx;V(Z8GG+c4KkQ(9;X4hbFsf0yKHhf1=IH*q_1jK zCQrDedI6gUUo?F;U1mz#pLDg-puV7TT4&=*Mum1tv6zs}$Smi~;*SH9gc_{@zZuyg zvH)U-XF*2n4OiwqcFvjT`&R4wy*NpaufJkb(0}@oZS1=sc4r1%Qs_I!@}Dp;V0pno zYN?&M1#`#c;!cU0^u3<-5~9v{5@?Iyw@@c@{f^(vXjRR4$LBwfJU!!b^#1!J3}UVC z`<|kl7@HUE2fy5d+Y(p}Vf&g$gWe|vrfxC#&e3zhoQ?7G6qvM2OrG2i$E!ftTHmtA z%gR-Fh~GK?QiK3yBto6qn||(1cH8YOf4Vc^GB3}sYqPmIheytgxyZh16EVV+Z6hym z1Lm1|NBtzG+V?i$R9F208(WfdjTcs{fS4_R8aHr(2~O%^ zxX9rZWt)l|K+wLfY5OS!xxd?Alvi@D=I+O_H+LS783>)h{lo)hd`!kawKNAODa|Iy zVaA_D#q#-FDon?L0QjMG_)ha7%yOw?jmad;jlk-s&aPJ1x&d*fws2rQ6KKy=U;FzL zxk16OqxT#?dF|v)3Y0E(`h?zYbj9iZUY7D|##fxd{qDZc#*8d}15sEMZ{R7_V=)6& zVHTJ@QY)7JoZ+Z<_!TaY5%+i;V9X!ua*}gjyKEg*K&sVc#0lxEuy;Ll>(-UxKZn9_ zGvimcMdgn^ zhUkIdme`N!Pust{_$N);eYY@2%b;$qh8E%dV-!)Dn59W)VL&M43Qeat9@9_B=>7=9 zf_Bq;B(xb(%9!js$4zj~aJq%#>W*JS^vjgtkV+@20G^0H(#MuAJ?`wuhHUbUbcrzB zcmNd|^iTZuXGrA4S-yY87(G(coxy~5amVN0*;zg>0c5feO6Pu>Wf+gtCgGeYS%ILN za%|(3DBQs#CFd!)EU$q5)&IN{{v>y4he{#ZgMk${_A?w&t7E(V{oPBFN1!jYVkH^? zbxZ11woJ0|cdN=l6U1n$EfEwB9Z$Hs_9Hsy{Z@=G9_#}VpD>$xcU$5KGv{3jYDK}A z+!^z`_7NoNHqB-n+7jY8&h^Y6OiNt(4FrCTyY-l@N(+ zru+5;ZU!>kwn^oNZlq~PYDsKhwnEvR&SuG9ExtwN?HL0yU93wIp&*`|Nk8@LnVy`( zq0;=o>1jS!rxook$U9+sbLWmnnoM3V84ik)Aznr}a#2MBD;Q%=x4GGm+CwE0&D%bP zP^EJ$bqZ35biLK%v6=qcy3*t9v?%KU1NO;5iODPeL5wPSx{S%>gBN6fP6~JyfJR)6 z$Y3gQV?VWPfB8U~t!?gl7gw{Nt~{pNe`mVSH_EYi707N8t7lI7I=B$ts`Q_-da(=B z_(9hg{%r2Ii}Wpry~P2wkCo=5kZImF9T~thf`<^wDBhNK3a4Mc11&&u-9`>WPDU?= zsTBpqFht4iFht*JD-%lo|40M$2tiT?&;`$lXrJQ%os>9T(nlSWiil}bei%*2tz&a5 z0}M4vs)bI4Hr6L^F{lx7_RA7>!ZA}l>KM>DYkvLOnQBr6Q&Y}V`;0VvaS$5crr;Ci zIm@T|qjNP(NljxS%i}>54S~5Mx1)C2J#yR_uZ4XU1%pS%(>`TT!?w8>zyTNPud>HM zhg&N$G0@Q3a@9HD5bX&&qdkF;Na)Cadei|iU)YvEGBKT`fch&x;`rnjTXS(ro&&dhTv-a=aE(MJ!txZ`JPuBNLt8Y-O{^C?PwA<2U3S7 zXLNDVylrE-Yp69U@XKhqKs-aBrrUS~*9*2&t3*eSdCFI<%WU>s-@Z-aC3zpuF^C}p z#hxB$DGpm{-zG*@`3K2jFyn6=DkD&AbS<(ku~FXbAq+gy-lnfL>fAnG%>OtxMXjDYF7CuE#lH3h=Z@Ffcl9^aM2T7J%mzW{S+!>ai5 zn8@A&gIydjO~*HkYpMrA#AZZA!_5aqd5%040GcbveP(8>y|^~S|oSPplQ}Lp2YzxtwY;6 zw7yz{;|2-&yp)TQr!kGHs4zt~7Waub>)=Q|E{vnMPp zfSMJXeGWDsgm2<*R+vr@L$F!EDNyRnRS`wqPz(z`Atlps)QtLo?)7-gep|K0QH)6H zGk7Dqxrj4IWt+A6SDQ6Ztjr&|9%U>#QVn}1jGv?JBzS}6bAyhkCnC2oGals7EnGwJ z7}8*#w%eJseDCvAs+Sd3kCvmSLiF8$o;@OFj$D31V4}o;%$)&`u(@{=j~lc%ACzm- zwLL4~X&Q-qCFv4vSuEKlm;5%e^PQ#&oq>P11C9+WoVG&C~V&g zpvm0m}iHtpm$(vZ9u-smm+5dLoF{let<#GzZPxliWi{e%SD}xb!LE zWKQ=&hRHxVv@Q%|?!lNvCf92F=Yh0GrV&AO zBu5@Ws*i_}Lft^aYo>#*eUgj5Zf^S0yA^|<3^z#Qch>do@}?j3mPcEicbhD5SEwyGA-H?#pM|m0vJWcJ&rh_{e$ac=_{YrI z>--yZcQ5?>dBszEvEwL&C!)d@-*a^h4QZ*-%Qkjv3AKvQ^J+RHsnVk$hrsQ=p0wqP za^GF*k7o|9o|E)zNo|FlNBha~7b0esmFjiA6y}y^IPs)Sjb5Gkym6A$}(2CJ6N< z&#Q$Byc9`#Yb(!e=X~Ybw7q9eU74BUDJ~IjI-Qr=nc%gpfPbt_dBDnFUw$}VAkSZ) zH-Ao#mN0W^dL{Kp|F6^9|L3QBYWE18bj1s_|I_K7CjMj7Zyc&m((k)=f7Fl%ue2i) zBZO*y8s&X7V{7z-V`u;At-e$JC-;uSGmPx#d-gxWGtqRMF;D!5c?plf;te34FiY%4(r| zXZ`aX2~zDOac9Ny7qNlPA`g?4cM%CGlCerxoa(nn^ZA0eWxSL&8j=ChTX+U7U} z3}TA;RjZF^Th31-BV(r4jY!J~AEf?WmPO~@XloZ!hAz!GNX?8-&IHYOGpNpBxL2Xo zN68$a1hb4%;fPvwpK&Be&j|0l%C~S{!@LkM+#MMf=6(*IrlZ#Cqo(e|I%g;4EWI-h zws7Z78>{L*wderc2OUxTDD^q|P3y+a<@xzTkaLn6-dBD7ww$Ce4SxG^$MJwSLz&0+ zJI7=Od1a;t3=mC1rDMs&Q6P+cqoygslR1w$OQ7o~^tRdnQZQ8o@PP12Fd0+0LJ1GB zwU5i#6Ja{H|1!`dpXMQhf#z|8eKp}jH_jf9iFUAX1Sq?Qdsg5Fw<4T;igy``@sa<8 z`{Ok}p++CXalp4_c~9=RmbtIXVspce-~c-G2*B&_DvWph$q_Ajf4bBEG7qR==Q=>r z&wUl|SdUk!o?Rw!ptr8Kjoh{n2Dup~xm~QXHw#i!A zlkK3aW!r50BudR{@lma>i;{k;t@{kAd!{Esb?Dr~zm5J?`j}5gTz$6e8mx_Ss7wA&h{NefWqzf-<`F|)w!dfY9bEXGdqelajxb*HAi{0OBZjNemEzg z9w~Zb9`fei2W&FqBO_?MmaVoEw{!HTSpr`PQKZi%BF0qE;h4M0fA!dNZcOgnd7J|~ zz8@)APg{6Y{^cD5OoU!xrhApJ9sU;VqEHDV>T=zCliFB{U%{R$wEojKZ&S`8ICZOB z0tPayMBC|pI%lpri`_|9}0=2H3!sUF*Tc+ylE|^VVt>S{I!@t;O z%*E@NKlt?8SXInK!3M+Hxc~|6Oct(VhBEN!F8y_|o|`cPYT~rSSp3zo7#&r3i+a4d z7=9EHBLOv4!ObhsX4L|_Zo75yl0AGxAquVmH;q!9>6~-f{CXp<<=C}>zwcz`hDw~= zm3llz$Ao$l5QWB{g+0{yE$1zd9?D$yjFn2(vAS8fPl=U4W9i?bs5xv`VMCim%Iwv# zS26;Iyt4h)<}^nPe>l~5C8L4_`Y?RPcTLXl01du*!nm0Xnh<8?eX0? z#6!Mw-16lh*go_kGyOO&0<8-sFj@!Bf{~Y0zrJ`p%eKdRkK#D5y!t7Lx6h3JTEEFy z>(<}-If^1c-Mjpk=ZG!xk zzheA@<4skp*9Oj=L^= z>lSv5UOSWEd_QF1H!|%SsCtd*_ZGCyi)MRVDV*d2=EDubl4!h+!lJPwlGVCd637_&?U8PT5HI0>bQlT;L)xcSk^cyBKet#vtdW1 z5q&YnN;hg0Y&a9a+bry%H5e;1ZQ*4w)?DLf4{{oW$SmtJvaGu{7tLqpHX~!RvMv(= zmW~w$y}~%Cab}w98g@S(_BaR>b`KuqT6o2Uf?LDlu4J4Zg05r>;6hlT_}mFLkzSMwKG@*|7Hgf*ng2e9g#OcFC}M_!F3;x^*Oud$r;`N7(6;Ywq8|dN@0K zGA|Qjaj}!PjpUS63%TfEV_d>$IMj!Oy*QtroUgR#8_+;xmp<$CgDvCqX|Pz6L+)mO z5+_R*qd0sErCqzGk(5Ws*YiZuUviys|1?M9979QlQ+Fp0IduP2CPx^=8L@%fQO8|| zY}agL*tUEpJd0m&%R7?Ui~`Iot`9o}t@ab$d( zi*N%m=#xKzJq#fRC<#t$W%1V!B=MP-H)Iw&?$SP$VNcB8P3%o#=U5nyNfCrXcFbV_ zfjL6+*MgAQ(=d+0L*e}MB3S_F>IR6=6D9M}#N-$WJ9KE~WFA}+kn0O}6~@T)1pq-9 zJR*o2^VT|{OUBBM*g_I5kuh&d`vtURIvHqDc%Re9BfH}HoaQ)0u2JIbmb385k)eBh zKY=llt59C>jp3(Yn&#@^>_tWe=|%&4>&N2;BpJID#yVM3&7KgCd-=^xn?S)5wR60te=SFF$t2$q zCdejNRYnwD3G@ZA7UJfNI|+iqe?npaoWmB&qDFlHA&jc&CqO6XGb;HNrFCHC7MW^B zCp&I>G*}@;XaFCI)LYn@i$}srP#d8p8f@=3fs-r5i7~g z6Uqg;(rg`C?BFmsi((cz^{0_o0%K`q0b>FpF5lOhs-~m%=JHL<$Im2eAX{gOQ% z!Cbi%fu=-LM^0e^E}hi)J_i6da%Pqj8Ba13;$e8xjC&*_8T65DSq8932cHU2&g~sC zCXR**h9-7~cJ3nG+JzL(AkoQCEkhk9TA3|Lr27MgY@PdN6iYKA5XMQz>Anp++{jkU z3bXX1z|7xGXPE-jf%BCj2XaYCL?e4cw0_ErqDJ3f`wL;XFli2)rLdzHyzEvA;%S;| zWmYO9y%j`(j3MAK*XS~dyUoR}opn?4*&fV>`NO&KgsxQlWoGFk7{7nleOj+EQ5iHt zC>f}dBhfCllif1Ohq3m+Lomm@c9XQ?WR~n_`2L!0dOTgp<#b7EI*GBLi~VBw3A1h1ARZK$=cDe7)zx=_fL!m zW9289-1FI(Vx^_T#yohQokiq-KmXHegOgQL-3q@K7C1ahP5FKP__cSCMadxah`PJW{lMp{F=RgmpD1zu?IFSWVF#Js44*~zTq_Te=MyC_Ff!t) zu%LPM)r|pgAebzf=QAL8WuR9?$?RPheplOJtN%7ef5XJn(y7;>L2~XHee0p`(^F+C zmbiH}mgycVen)$rPT}8@QAX5(atr1`Fw%#DaHp{kv~zv;Y=%FpTDc46WsD#&EOsy@ zRt!_~m6;mzDK}qkEs$46ZoA)yVt*Pl7UtN}6r}X| z!~YJy#f+ci0coHy(HDW7s44#ejv`1^l^5M&HSS$jQI5=8QU4E(&kML zWNwJ;9HJlaS8@8wdkV`qNW>IXd1g*y+1Im)=0a33GXj{?Oc4pEiMyvG0RhHkCztT+ z4~G?Qzp-p9F2ggM@Z`c2tNfH<>A0`)1<&=83!LUPruTsnZ8qsMWD zsHF;v-(Hyx-S&GxN5Kz_AcPsd(Bic*`%3-fA}KoJ&R>+}sk(`*UCveaD4cm=?yBun zWLV*ytute|<8>@aZwV7rjTN3qI8CS#IUYcyM_ACl`WkyZ!c4RfX_XU85d^ZrV~O6v z@cVoTQUVc=(W_%Vm8{65E9@y`D(BMLoo5E!M}hq1FwCxCuB&6$8U<4~5}(1MCC=M4 zVg^ygeT2@@5RwiGlxe268lH`ct^J~wg5~onS-&nbHJEBnui*sM8)b79V~Y@Lui&R9d2)4)x6xk)xr7ras35#qsHgCd5WKB z^TImE+WY3lo0rb0YiUpL?6h(l=f_WKK!X8(TXkhqx=D&ilwpD)vW-oNxl#{jX+>+8 zq&c-pohNRSq~hLRMD z{a-}8-mqIMuBtmylpj#$+0@wVukN1sDvi?3z=EN z$}NL6bxeK^t}U!-KWy!3@akqkRHk|MFM0HVDE5IwO*frMh35EZqJ~b^-KBR1Px1>M zpNXXSf}E=b`-8>kt|ngdQQ@=QlfPEn-_qydkoOE17jh9{C6mHQ8!^mMbj z)uIfWpU(ZVA~O2VcrJ-jq1B&Bieu6V=Pe!uLFH3}MU)q1q8?%{0q z6jznfs$JQ!WLXWt?Qop%^>>SUJbsz-1ykr?m_Jvh>o_F&ZvAkR+2r_sBcv3ZCICO8-$Q z6b&=pYBVw3`x~>_{4hN{Z`{)GCXbe-M@Iz=cl!}Xf3DyY(?0aF$5FvsSbC*`9^;JrI`VSY_h;ekIg2jf|t0{+M>RmsdB z@Z`cFI)^tiwxm0mi0CYG3e0Ajmp!LzGB|F1-}}^*9G_t}*Be1>Q@Bf>k{NX;?fx%# zGWNTu$#gwtru__u+=?n_*WvAouMRR>*Ux)MRgf8N1iw6a8np2_5oKWwYP&NB+d3{l zY`sO_M)Bm>T6%_ca|(e`Y$`I(E3ER9Gg;c_;S9xAm?wPgGbgnR}$q zE=LU{d|L{SYZ^uNY2Q2l_&@D_%!*lhCM8WtWz383Y$Odli9@r+u9k22jxAbblN@I8 zD!9BdLMt_Z-?HJidPQfy?kDYWKzSNPyJI75;)zw<+=qSXw=_7!?om3ikz~ZjyW)4m zHjeEBCYArAUb6SlCFe4;)Z72abhL5YH0;a6Q-kK|?2i52H1-oMx3IVyaaVFbbUJL! zd1;|wh2RkMqJW@KDqG||>8Yi^ITF~7m9{uO+iDt0_Nx00TH{ll`pvD{Fbaik@ zMY}gI>OALSqaI~6$ZF`hd`*WlyC)uB#11cKa?qae^M-LR7p-T1?Jka_oCh{+hzCZC9n9?qmAN0`NVP z0^x=l6^ndtZ`L1A$A4_IAeR%$->3{)d@fVX;pq=udp-&NtU8999Uh>S=~%qeE~u7i zgNR8acl_;MWswZ8dax(<_tT3%mYsI1Vd5#r(?ukFI6-{+_RFsp>^(D;Bp}_fRpb-H z14C@C4-C;59zff|Q&_IW<4w*aHor)uy&-pJDzO)OED$A&rv8)2oQN_U++C%(v z6d0;9GyZ9^Q}7tS$qzRC=Dr|k_rz6eXE;3Fy{P+;zr!Kl`(*{heD)YdU63t3wI5WI z!vd%T=$>30`Ht9hx9gxZrk-xdr7cOhhyVxO0mU^(EfvNY{~Qc?EA};071xA4Fd6nr zFKLiq<`ZXi%_W@B9eM=aP!}yfZk^^G_ktugqzvhdOrlcRriimLy@j%$Naz zaXiK7%f_*MmZ2X+;0uMJlLkPtDfR7=FY(1|e`JKM^;JdEi*W(-yvM2dOvyPsY^G88 z2$iRs1+63Rl6dfJHK5hm-V9?0(nmGq@*gLmmZ>&w%EKWLc!O2$;_zGAJdcipU}coW z3D(>6fcA@+((gQ%89CQxS8uU;aPp&w-$$#gpS;quHI3GeVAS=r<{O*`gWejX=-9z?FX5J>_7vAxUw}68vmDdExW?n@-RWNikI9<+U}{M5 z9unxib?eHtGn(*_-_?AC!#>`FvX%zv9rYt=rkAWddC9rujtj<*COx88>YE8Y(({Fb z07*=S#1$L_AAr!?r|ynzHH>|lGoGzJwu{PWxt>(EjL=1|#jthy5O)q(*|T|i*UE;m zjjg6o?;oJE;1Ith062v;_2xSdTZhG6a`^1theS2djqwtaWN|O7o=?rn2N;0@C02e~ zvkZJaIa6uA&N!y%Id$5!c@>cv!po$~>Py_`caeGfU{~URk*?RLg+H;G>|rsmxCXBP zhoJN4s@%zsc@hAjcYIO1_~1B|0fr81j=W5?SF}1~jl(~K(=@!UO5BZOtP?Sv{-*ZN zOii1Xe=tc#dYM^DFpu>e90#gC`CMS=@j$15T;7 zG@fg3X?Ww;^m~xepd~|J&HPq>ywUuCOR9sn4gtg&T50k&bWga!&?Bf7>!JYxk8mB- zN!P-`Ol5=NMK(Z|Q9D|QVJCy}@^DO0D)=Da=0ERdwse!(_=Jv$t{#HU1~qNUv@S_p z!&u^vrWVj)G=win7^M0EIH8sV%!Sr4)(9a+# zPML)Dak@_Ji8d`?fxTqcK+8a(*f@U4v>anRnZS)Is0h&@)^EQNE(h5Qj=aKFS`C9E z9vyM~ph}tiD9keXCC(hYzIZFHkWd=ONJc?shETW~iN`=S5MIQ0z$v8U_GKyw&z1jz z{Y8&RW2~=ZGk@av#SK^1?jb>|v66vXMt(toe&=8GP%M*|IsHOPYyfavX(%Uln&0{i zGrZ0Lc)i^M;37HI<)U1SpbUhYrddq7j?Juz`SaXF8+Vf2sW%=2=^7$E3=PL!yd&dB zXz=zVdteAsaBg85s^fQGy9DrZ=c7l$yK~BM?oV5f(gk$$|N0tEUuW0F?nxd@H%Z7C zM^JU{PXZG5bQ6x}j~}ALFQ?^$%n+O<>5ot!1PxJX)DFkwNdxKLl~71X$J5%vuw;_% z-twzo-LIPazdoZ!bOIF!)W=|Y69s{kThxpCnSFm1OVO^NX zSCpLFC1ZWMrL%ZKA0}~`@bwJUD2gX6KdFDrXK^Y^d!SRo(Kq@Lj#gTC+B?`5ch3PA zcB*VK68@7R;R9>xLE?nr@d*+==GcF*Ua4U!Nm!>L={*@vF7N9CIU7QT-=B@>_ml&T zRj?f(`z%y%6N18OhaUiOAGfxta6xlFUm zr5uji=fMq`?qcB7GE8r@cP=~d5DQfwI)2gi&t$q4!VL?J%F(yEzJ@~+oM6eD4CofL zXs$7gMy4lbvQylOza^0s$6!%!NW%c5jJ+8!Pcr2&D!UmR|EfW=!=G42cmp!BzlTb{ zXZY0eRjOQpT`3cexzK((}AL z0jI<`DQMF@Ib5Z$F&t8R-17VHX-F0D^)?nuv}Xn;2>UH7j!PxNrW%9Yf(4U|J(+k*e{zH1ftU{LA@ijA94TFohXpcHb4CSO zUKziFv4ZrzV${Uhwjq2+2&>ynj>GLi;vL=x=TE(d%!64}V;wXeT2cchb*tgP=zrL5 zhpE5gD}rNJ4PVXgrlXasYWmYvR;kP*|65Jd@iEN%)iIazdx=kjbU6hD<NNo+W5i$VKI5OUTpv0j%mhpw4+4#FCV95pyR+VM;^0>dKno$F6aAE$WdEcLwe z=R>&Rr7SpdHKp#-x$NHsV=_UrhIe(KU zs9!w#@DjXej`k6_+|KJ9kTqrTq^fI&Vj~tFPIuKcZd~MVb;0G7QueaOCWlb#FryCL z(oqNS1&vsDibqx>)@#zvUf8+wsi1y|_i+DKcgI4jKz)C^2lT1*go;0%&tI7wza=WFe5pzByIJ4iR4|U@BYT;r8DzwZjEkFQ09ByoRRC; z-kiJ5v_)mKRAK9=(!39cTjZmQHD_zg=@D*`|N6hJQ(?i1|Lt(ghej7`|EHsim-`pD zoTNGx+2~@yxbD%#dht80zewL-doaL+cdRJ$?Xj|tvnn6nAL)Iv-)obaRd+;~T-?@_ zlF=IN?$(hwFI3v`u1Tw?LMVM5p%9(y*7&x(wxaga;3P9Utrz(b{6?DtCMh-+JgE+U zN0{_>M@4S5ZD(eLq!N>h6XcVNllhoj+%%*!Ci#e0b9sZLXsuA97+Bd_)-Fk08|~4V zozW@nY#P$?a+1C{Szys7Y)ujK+%N0ji%62>hD1u6_EzY)WE|*NR3_|OYthG}W7O>j zTXSfHnmQ|4alp*6WEu`>8>UyCutBJ2-(QsJ?9i`km%KzJ=1*xh?3ZPtTWCAs@dKF<@ZT*`whvrj}Z2@aU zFHiDH^VLM*xRZl1>TVCA96pZaEy^;WDZA`Mh*VYR(eQ zS&AF0U>Jq=UoE$CTOu3JOTR_!G$lS$`aD-7gC5y~*mzpwFdgKPAh(1|lr#=IIE!}7 zoksUhc@F?8NMsd5k_}X?f?g%n^3XeSt=p+)h23J9j%{PsrJ#k>hzGfnIP23hmW5rq zg;PYC45qHG3VU%H@p5^aklhDarAGMxRA-!H6)Z^opoX!sDofVrfz{1WA{35Vd?Zg^ zqSGs@tuC(l>@bImXmr#tWvX~kC+!(6DzaPVQ^kmMXEw$MrH0cWRxYnA*MOC$cwD$o z)j(RITvqIHmg(<|%wA+()K6xaz{tdwr(1KWqh-M{6$+~~>5d&%QUM1gVPuEA?zH~N zomy-m-J5znu-p&D%7_}YqCJF})La5GPCQzllG=7{MCKuv+UMBO`MbPW$Bg?2E-6Lc z;i5@vJWeK@l6&QZ?eNLnC2A(+I&fPQ7jQJ^P5-JaisKyCD?akwlVJU&)Ai42?TShp8U* z`RJ<{Il_3?#Y|-Fux-pyb+>wNctjdF-i{F|cbJTUrC3+j8V>=#$xGcM6 zC}qV`5512p8Ex_7tMjEzh_v zp}O<#dJ%!KZ)7i;hRj%pk=3X+!+uemgn1p4H)-V|?{Wo4S8};^1j&o2{$acnjeKm- z727f{c+f1C7Dx}t$(d#@-#T>h7y;M@oi=cFZ;Ea7Y1S~#xhknOa)$GJ=lt{Izp3nYfBd~$IDudBy zFur9BO=3YB)Z0C};tOJ%Xfu6juH6Q;KzoH<**nT4@7^{iY6V8?OFC1(^fZgl2zEL- zskmeB{sOm=8sdzBL0C=uZu3u3OXhR1qgdiqtxueDnTHV}wy8-NTe9#4t6GekM47E- z{53~GK)G(2vYflz7-43uq_TJy9bt9#N!E8Q8tK)t3erNB3mBC$_ajxGa_X%3Bt{sL z-;}u4^jf%m#KC=|81gxsciD-yEb9`5^uINvBEpRnE3FF0s~@{I*2#4Xh*{PU&t?f% zu=%Mvsp9)?8_?tOK^E(O;2|vhwamkcW|8Ety;DgAquYm}bDX8173r}^P9&-Ag%UZ8 zWjPizwzY~5K6d25-`bOWlRbsT!eCA>>pe9mEcQ%AGjzl&42yWaHux`}_|d-;4cfeL zLCPO03bO}LHzbd=4b&Xwj0HO&i|E7j#da9xvN2;TWZv2e$$@#4l z>72`w9+19Nxqm%;lu|4@d8TebL0xu8ybz;Od%~+nW!kI;89H~YVlCAmhZ-mnmTOKd zr^ZSA5>|mLUPcME`7~Ds;?QRb>ASnD@xkQwnQogo$GF_%9WFg2dy{01xtt8)U zaqOU;gX|H2s%OfW$?m6y*465qd<0?fqyK_vhC-|kwMSZM#V%*229s7$;pEMsY1qlZ z1nTF(uoANPQ z(?r^lG`qDM)K2kzdu7dDjEjBilxlBKbj_OSqmtE3AJLyYJkdW^wA zx6}Imx+Y^UPE*NOekQ*ulUt;#`Ba7Ao>PEwFVTUO3 z-os9I-|3s-EK?e-K>}&CO|{AMEe701aDu}snC!FC>;WBxQFmq43G=CpzSw5@jsXAO z7iqGDq{Q}(-gkZE#)(Qnmq-}vgjSWvLco#;?Jla)^*+T#^ts|x0b$#2D63gufr=rpZ`0fE( zz!?mVLMk$wo!x;2(0+$zUz|q20aVC?V7W$}9lKdmg=#=OFq71HDyux8rljc3Vo~%F z*`O7ejKKk7v=qo&iH42fbj}LIVgxIS777%kJ#aGWP|J2_jX2nGFAE>w824whbv)Kq z))Q?QkrPiCSoRpK1kkqQXo$sFq@V*tV58iEQT_$(^e`xJaHeiJF~Fpk!beeK)WK-S zlu|xUb%E3RIK`JbgdRsnj+heNZJazWQ@c09_Si~1KPD0jQm7E~k=mWPA9YvHQ%lI; z6D7CUvm9By|N2CT1`!g*(Xea}8$8o!LRGlRu~_)}M32WTBOPtpu`#B0$vY1|1g|D z*2;?}JMXN;Xfv`GCvOt?FJ>s=ek>J3u~l&CSyU6nHQ`c@X*Agiu6U#-LIYo!@I0c+tYD%6G`C(3{%$f)1!-O#bRCYbuiYEG3dz9cIJpWypccuW_Z z4^w^Jubr^q?y5aTKS|Qe%vRzWFoV^ony+-l{SjygO`sQ-pa#bb^q5o8UP_}sKZd=t zVvGYu#c&zV5vVOiN6+l_q{>>Xd#kdJdv`nQxdvpH$i%pn|CW}NcOH;0iJ>)6jI;V(m3p|&S9c_(`SncWU^S?4X=dzfFBCNKFFRa>k_3bZ+Qj{HzKOyqma;z@fl zB17E5lGVfb&ggkSBEbaC8jtyh(LWa|g9a?gd=-kN+T$2TywiU>`5~Cu{vf<52fm^_ z-@MxL`rK&Oaqu4Ur{8v$M3lvER8yJ8H;--dS{q%KbjuxsZIsn^rRbZN-nsH%d z@nwr&Ki$x{_fOw$7XSxG%=l%fb?I${0|%s>{YQ5pq33f4CRUlJj|>?FPL)uTXKK{f zN7kqh2*fKd#_4@L4*;Ng5@Q|F^ZENnK$7hdS>)TwUpt-XpL7Te)9EyI#mwT%A4y$b z>BCwWl%BdrBDV1=Yn;Z;Z!AOs!LL(4k2nZ`Tei!a6Qp*-R9#DL(w|mo-^+U-dXb@> zzT|4UrJix?y{qVL6>o|e&X4D-$5{otXcoq1>W-}~unaxYvU6_sV(Pag+3R>Y{k%Y= z%D1NGS{Bg@(N9Y|#bFpL=lJeBs`n6{rT$hHEyppu!(LLkB2FMExsi}3UVsH1ZYG?1 zu^?jM)%5R8B%9_$xNXjheX3V<)heR9tyU^8&T3WYK!3>-DjS(G`e5@^)?~{tyWQqw zOHD)D8Qw=|~q< z#>XF~9jZQ%RHAgM)-of7FUAyVONBt?l*FAy?Xe>1M~}Z>ckaVsi>|5B+Vgr=iS+Q* z|94d)|MOF$wLdg9TKhkp8g1e~F5l%~y&lgX`s2m3E?%-Py&RO4wfN;~#ZM=1RbPJM zk3V8QHvFj1SV`Rk1Ec28#hQDoLzj$|oW2#=?+#C~Eb48Pd8zIlsj#6$)lQiopO@>e zpv=dpl@=Z9npJCs!O^YFLYDPnmlV^MFmHfOE^JI~*RpG3a zLcOFjH2Iyfg`|V;Y~f+6RNE}9yDBMZOmk{DhOIlY-j5ozI8@RnyVZff?>%k{XgoyG@z+#YrD6t z*H&9;t3ay)t+r}KzyW7MuD8XZiWCP>5vZ+EP!SYF#^hG3v{0qmYAPsEY{dysKtLIy zQbmmz5ETI-B0@lB0)&w1TWg=>BmuPc?frh-KN1GdK6~#qJnLC&?}Z(u-4ZuJQpCyZ zO>QE2Q2MIUu|UmkVDTW7I&&aA9(?nc>03Ν3CA{LsuYV&+f7_r)GaozQn0`Vf+D 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

RJIYj3#Gu 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�T}JDYLg9!||JhO7hhjbs6|Fl|2@LKU6{Ga8 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

vcf+wlAlw%Ho&@zi}YMd7}-{wTigd( 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

m_;=|n(h-vk$D$jI^%$=Q`ao&Sk=WUtc zt-545?1kMdEG)FNPf*nQ$FvwtvHGZT$vkmc5QS$Hq|SLNjNDOBo?(8+eXe%k7sTt1 z96r3VvO+u{`OjMM-+ip54J-@^_WVx!>l?NBhWi9#78sU~d;FPvj_3+1z#I@mX?gkX38_1s&`cC#KjI)BQQTi833n7<06uo z$3Dz12=IYkTOnG}esT~+Ij-c5f}BGR2TRPX#E`@&5V@ynm=#NmyJpb;iQY8J(`>X` z2VZ(q*oTFN@|o3#BM!c}^XQvEO8(s4yu#b7;9}@fl_S!b*ndU7X^R7K-qeNx54b1w z#kumx_SPXVa$sY!LDj?DT!DpKRFt8Cg+lP;_p!3l;{vU-|Btq}jEbs#`-bsCML|GC zKpG_^rDJFbk?t-L1{hMh2T?-0yFt2Z=#XxtySuyVIq|yhXZ_dx;aTtdtoNNS%FLeG z=iX->$1jczo(KH~CGuULJF6IO#R|9?@1k=vKp=sif||M3;U00ph$0V`OfYysKi*f3{~M2E zB-aaJgbNcb3tYYceut6K(L^A52l zgz(c{zMr22$clh?9>ilWadExD(t*#iERY@E@807X4yI142mZf@*Aar^Kq7TBp!x;a zw^oaIh%xV52Ko2ke~mQBsSAq&-;Cd&h^Izc0kvgEk#2KVF;KoOgZ3V40Ei0d-^0Pi z3?@){Sqbg8?;k+h&6Wk6a)GcJ2D(qHI(!x#jCmwvi0pj_4n?A&p@IE72doWGCsqZV zZa|0+z(G1XxSⅈxO?S>Ri&07kgSGiy?%dsPgG_5r$kpKPu3i)Q}w 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

V-PDMUw4}I)4kl2i11eXn2q=uXTOLXD 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

k#HxeHcLnm6{T#Xr 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 literal 0 HcmV?d00001 From 0ffdf8d9a7c729dd831e1da15d7e344c14dce692 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 25 Jun 2024 00:22:10 +0200 Subject: [PATCH 076/190] Fix merge errors --- src/ImageSharp/Configuration.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index b2d1cb04b4..228e42bfe4 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -227,7 +227,7 @@ public void Configure(IImageFormatConfigurationModule configuration) new TiffConfigurationModule(), new WebpConfigurationModule(), new QoiConfigurationModule(), - new HeifConfigurationModule()); + new HeifConfigurationModule(), new IcoConfigurationModule(), new CurConfigurationModule()); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 99789ecd6b..9fc7ffe32d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1138,6 +1138,7 @@ public static class Heif public const string IrvineAvif = "Heif/Irvine_CA.avif"; public const string XnConvert = "Heif/jpeg444_xnconvert.avif"; + } public static class Ico { From a67454604480c247b0e042b3e438596ae4c1c5d4 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 25 Jun 2024 00:30:41 +0200 Subject: [PATCH 077/190] Fix code style violations --- .../Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs | 4 ++++ tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs | 4 +++- tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs index 12e5197138..ff510c8aed 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -6,8 +6,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopFilterParameters { public int[] FilterLevel { get; internal set; } = new int[2]; + public int FilterLevelU { get; internal set; } + public int FilterLevelV { get; internal set; } + public int SharpnessLevel { get; internal set; } + public bool ReferenceDeltaModeEnabled { get; internal set; } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index c2f9071990..8c1c12b0c3 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class Av1BitsStreamTests +public class Av1BitStreamTests { [Theory] [InlineData(42, new bool[] { false, false, true, false, true, false, true, false })] @@ -169,6 +169,7 @@ public void ReadWriteAsNonSymmetricArray(uint numberOfSymbols, uint val1, uint v { ulong actual = reader.ReadNonSymmetric(numberOfSymbols); actuals[i] = (uint)actual; + // Assert.NotEqual(0UL, actual); } @@ -230,6 +231,7 @@ public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int { int actual = reader.ReadSignedFromUnsigned(bitCount); actuals[i] = actual; + // Assert.NotEqual(0, actual); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 943eced4ce..4a1fa85801 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -195,7 +195,7 @@ public void RoundTripPartitionType() Av1SymbolEncoder encoder = new(configuration, 100 / 8); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, - Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None ]; + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None]; Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; // Act From 215ca668fbfd2d3a53f3ca535a0c74479d7174ea Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 25 Jun 2024 18:43:23 +0200 Subject: [PATCH 078/190] Fix bug in ObuFrameHeader parsing --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 13 ++++---- .../Formats/Heif/Av1/Av1Constants.cs | 5 ++++ .../ObuLoopFilterParameters.cs | 12 ++++++++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 30 +++++++++++++++---- 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index 4dbbfe56e0..99ba383ce8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,14 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader { - private const int WordSize = 32; + private const int WordSize = 1 << WordSizeLog2; + private const int WordSizeLog2 = 5; + private const int WordSizeInBytesLog2 = WordSizeLog2 - Log2Of8; + private const int Log2Of8 = 3; private readonly Span data; private uint currentWord; @@ -175,11 +177,12 @@ public void AdvanceToNextWord() public Span GetSymbolReader(int tileDataSize) { - DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Symbol reading needs to start on byte boundary."); + DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); // TODO: Pass exact byte iso Word start. - Span span = this.data.Slice(this.bitOffset >> WordSize, tileDataSize); - this.bitOffset += tileDataSize << 8; + int spanLength = tileDataSize >> WordSizeInBytesLog2; + Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); + this.bitOffset += tileDataSize << Log2Of8; return MemoryMarshal.Cast(span); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index d83389d9dc..36e3d19c42 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -118,4 +118,9 @@ internal static class Av1Constants /// Number of segments allowed in segmentation map. ///

Gu)9$%3YO$eXaIZeIu0n01D+dQq(Kj|LvtW-}` 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

fyQ);_ecXiDny1ah|ie0ckM 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

wgr!--<`Ym!+fiHS|5rib4`t) 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%0Ds1{jyI$qC2S>Y?{GzcK;UN1)_dlP4)ZvVc6 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%0DPe*;z&C)!RGm<-vIRT^~S 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�uS+9gUK(|Gg+kE$hzvLFxejVZAK9i?5H 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≥$~ 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ȥ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

xK;1^?)zCf+I7DLCl0?Y;CelL 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�#*X|D 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%0A>3r*EQtc&GDIfXqd;_uitqpm zB8W_X?H~%nVK%}bbvk&f@uV-}J#Va;!KT~RH7|}M7Y8wLzZ-y`l?y?{UC!g5S zd(i{0OlK!h{Q~6zevvjP8Xdl0P7MA=PZ4#e;FXdRNO5wFg`otk(~4+oixfCy5QDXa zg4hehd3izTpezm*OOiR1h*ydl0;9Y`7Z1$vsLRP{ z{}oa3$UK?>X`akGvKHT&2uZFQ2ki3log9KUUEu~qbxDa1H9xtIBq_BwAi?3??$-U} z_m(Nh`6$ZbZEnHs6C~M68F8OQUJB`vk0V+%zIm`?2I$^2q7+Fg84j_MR(6Wq)TRVs z1h(J;j*6dvv(!*8!Qe0DvPLn<_Ep$34}5b6Pa5wN`DTU>$)dYe{wQQ6`xJ%QwD;gfQ`0K+V zx~@)5-y8aQ0clJFqXBqVtO4yQKQofr61SrxVLSS<+8If_x1|~h83i3 z$br2IX)%Q0=G9yd$Xnaol*lLr6Anl;->ykx#GbR;|1+86X8)zhE0pae#o$%--76fr z{F+0UR^Q(jZ~hj$sVl(C7(t-n`pV}SZ}c$pho3pG$VuFhor}^o^Cg{+)g{yKR7Jc%Em2f1m2u~7tg_<(+e?VH`)!SLGMl@3E1Y+nWbq(^q0eFV(ms(2= z*TL-f!ux>fP!<&k`sZsUHpM9QV*)CUO~{CEWKhI6`U2QWFO@K=zXY)DPZx2FBspTk z&p4#FHpyU@7odKt-Fzh z#G3c=8#6<7iuu>S34wCpiOUKLJlQSIp0Z?xNhEWo=QfTF;%bs95kjU`4U;m`HBn^_ ztPkXWP6+I_WvUH574qN~i6nop)^R#ki0P!K-iD5~FBRXqePBgjS`5uOc1{jIwtT$#T2L$Bitj}~Pj;H%s*xELwgeqOlm{y|aNwW{z zR(pQRLfMr=oCWM%MzlUYE6DOwr!=*IvDYpPe}0Pf&)jE zbE|f12S#LeM*>Al_U?HTYJ2y#)?56m2@y_J+lznn1tG1_O8;#jq;?Eni#okE#YL|Bn!{R9#7784MQ%ty`d)0aOW1lXq+r-``Uxl!5I?LXt|kyB>aPsCCbLLPrWd zd6%yxoDej#OB_pj+LNk{3H9yZI*j79+{s|cO4m$|W*Zo!4rd)cUF=`=>D@0%aPFES z^dE`GeG01H`*Bl_iBFlkk+t3C$<#DmKQ|ba&>i$;X0!D81yIx89kelUt8v8tEI1Ep zZ&Sp}PlD4bA-)^rkrCXWWI45O!&0Fz415f#!Na8t!96}$zmc1QFP0f0j**n`x}4K? zQU!K*uS<9t+(;VfK{GHGM7%a-{qnt+L~*ZgKK<^P;xe%u#Tezf%+6tJjep9tFCu9^ zOT;H}@pj#|wZDmNa;a+aD(8ZYSY%T?2KTR_F!ovQ{^6Ec83{t256uW0s>yy^<3Gg{v+&pqZoT6 zLRLqB0%r(Hi_`kL-qF0)Dmn_QKX4sc{$77q0m9r$&8EfHlBk?pw7FR{1?$KDzNR6* zZkykXRv5E#5_MQNdM6y4Jn0z%s<^OnO!t+H`P+;)NYI#ra_cWE(sR_HROfb`d^#8_ zRlr6s&Aims)?f@uW_0HXGdV*Tu%NKsD*E4txf$oe#7s05A~z-0O}Yn+Yy<7AY4$hr z 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

GX={MFOtm122E1nfG-a_d->pB=uxJ+g-i;!l*VV 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

m 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

EjNVHL)XXZYPTd`g(DJ<3{q-r`phUjL0c~>D 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

Vn=}yWyEtE(1 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 z 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

g#J=v<3?wmHj)aJi@{I8d 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%0DJ7~cT1ZlX6c$28B{!%6ltfB+!j|r= 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%0Aqho?L^SRYTJx+GsD{+3zD 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

public const int MaxSegments = 8; + + /// + /// Number of reference frame types (including intra type). + /// + public const int TotalReferencesPerFrame = 8; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs index ff510c8aed..ad32d0c01d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopFilterParameters.cs @@ -5,6 +5,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopFilterParameters { + public ObuLoopFilterParameters() + { + this.ReferenceDeltas = [1, 0, 0, 0, 0, -1, -1, -1]; + this.ModeDeltas = [0, 0]; + } + public int[] FilterLevel { get; internal set; } = new int[2]; public int FilterLevelU { get; internal set; } @@ -14,4 +20,10 @@ internal class ObuLoopFilterParameters public int SharpnessLevel { get; internal set; } public bool ReferenceDeltaModeEnabled { get; internal set; } + + public bool ReferenceDeltaModeUpdate { get; internal set; } + + public int[] ReferenceDeltas { get; } + + public int[] ModeDeltas { get; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index aa35df2135..4823712f18 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -714,7 +714,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade } frameInfo.DisableCdfUpdate = reader.ReadBoolean(); - frameInfo.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 1; + frameInfo.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2; if (frameInfo.AllowScreenContentTools) { frameInfo.AllowScreenContentTools = reader.ReadBoolean(); @@ -936,7 +936,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade } frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; - ReadLoopFilterParameters(ref reader, sequenceHeader, frameInfo, planesCount); + this.ReadLoopFilterParameters(ref reader, planesCount); ReadCdefParameters(ref reader, sequenceHeader, frameInfo, planesCount); ReadLoopRestorationParameters(ref reader, sequenceHeader, frameInfo, planesCount); ReadTransformMode(ref reader, frameInfo); @@ -1024,7 +1024,7 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decode int tileGroupEnd = tileCount - 1; if (tileCount != 1 && tileStartAndEndPresentFlag) { - int tileBits = Av1Math.Log2(tileInfo.TileColumnCount) + Av1Math.Log2(tileInfo.TileRowCount); + int tileBits = tileInfo.TileColumnCountLog2 + tileInfo.TileRowCountLog2; tileGroupStart = (int)reader.ReadLiteral(tileBits); tileGroupEnd = (int)reader.ReadLiteral(tileBits); } @@ -1196,8 +1196,9 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob /// /// 5.9.11. Loop filter params syntax /// - private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, int planesCount) { + ObuFrameHeader frameInfo = this.FrameHeader!; frameInfo.LoopFilterParameters.FilterLevel = new int[2]; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) { @@ -1220,8 +1221,25 @@ private static void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuS frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled = reader.ReadBoolean(); if (frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled) { - // TODO: Implement. - throw new NotImplementedException(); + frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate = reader.ReadBoolean(); + if (frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate) + { + for (int i = 0; i < Av1Constants.TotalReferencesPerFrame; i++) + { + if (reader.ReadBoolean()) + { + frameInfo.LoopFilterParameters.ReferenceDeltas[i] = reader.ReadSignedFromUnsigned(7); + } + } + + for (int i = 0; i < 2; i++) + { + if (reader.ReadBoolean()) + { + frameInfo.LoopFilterParameters.ModeDeltas[i] = reader.ReadSignedFromUnsigned(7); + } + } + } } } From 5d793a2ab76d0805a8f490d5816b6c26cf43dabf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 25 Jun 2024 20:16:33 +0200 Subject: [PATCH 079/190] Implement ReadSegmentationParameters --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 111 +++++++++++++++++- .../ObuSegmentationParameters.cs | 19 +++ 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 4823712f18..dec0602523 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -11,6 +12,34 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; ///
internal class ObuReader { + /// + /// Maximum value used for loop filtering. + /// + private const int MaxLoopFilter = 63; + + /// + /// Number of segments allowed in segmentation map. + /// + private const int MaxSegments = 0; + + /// + /// Number of segment features. + /// + private const int SegLvlMax = 8; + + /// + /// Index for reference frame segment feature. + /// + private const int SegLvlRefFrame = 5; + + private const int PrimaryRefNone = 7; + + private static readonly int[] SegmentationFeatureBits = [8, 6, 6, 6, 6, 3, 0, 0]; + + private static readonly int[] SegmentationFeatureSigned = [1, 1, 1, 1, 1, 0, 0, 0]; + + private static readonly int[] SegmentationFeatureMax = [255, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0]; + public ObuSequenceHeader? SequenceHeader { get; set; } public ObuFrameHeader? FrameHeader { get; set; } @@ -1188,9 +1217,84 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); - Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet."); - // TODO: Parse more stuff. + if (frameInfo.SegmentationParameters.Enabled) + { + if (frameInfo.PrimaryReferenceFrame == PrimaryRefNone) + { + frameInfo.SegmentationParameters.SegmentationUpdateMap = 1; + frameInfo.SegmentationParameters.SegmentationTemporalUpdate = 0; + frameInfo.SegmentationParameters.SegmentationUpdateData = 1; + } + else + { + frameInfo.SegmentationParameters.SegmentationUpdateMap = reader.ReadBoolean() ? 1 : 0; + if (frameInfo.SegmentationParameters.SegmentationUpdateMap == 1) + { + frameInfo.SegmentationParameters.SegmentationTemporalUpdate = reader.ReadBoolean() ? 1 : 0; + } + + frameInfo.SegmentationParameters.SegmentationUpdateData = reader.ReadBoolean() ? 1 : 0; + } + + if (frameInfo.SegmentationParameters.SegmentationUpdateData == 1) + { + for (int i = 0; i < MaxSegments; i++) + { + for (int j = 0; j < SegLvlMax; j++) + { + int featureValue = 0; + bool featureEnabled = reader.ReadBoolean(); + frameInfo.SegmentationParameters.FeatureEnabled[i, j] = featureEnabled; + int clippedValue = 0; + if (featureEnabled) + { + int bitsToRead = SegmentationFeatureBits[j]; + int limit = SegmentationFeatureMax[j]; + if (SegmentationFeatureSigned[j] == 1) + { + featureValue = reader.ReadSignedFromUnsigned(1 + bitsToRead); + clippedValue = Av1Math.Clip3(-limit, limit, featureValue); + } + else + { + featureValue = (int)reader.ReadLiteral(bitsToRead); + } + } + + frameInfo.SegmentationParameters.FeatureData[i, j] = clippedValue; + } + } + } + } + else + { + for (int i = 0; i < MaxSegments; i++) + { + for (int j = 0; j < SegLvlMax; j++) + { + frameInfo.SegmentationParameters.FeatureEnabled[i, j] = false; + frameInfo.SegmentationParameters.FeatureData[i, j] = 0; + } + } + } + + frameInfo.SegmentationParameters.SegmentIdPrecedesSkip = false; + frameInfo.SegmentationParameters.LastActiveSegmentId = 0; + for (int i = 0; i < MaxSegments; i++) + { + for (int j = 0; j < SegLvlMax; j++) + { + if (frameInfo.SegmentationParameters.FeatureEnabled[i, j]) + { + frameInfo.SegmentationParameters.LastActiveSegmentId = i; + if (j >= SegLvlRefFrame) + { + frameInfo.SegmentationParameters.SegmentIdPrecedesSkip = true; + } + } + } + } } /// @@ -1426,6 +1530,9 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt private static bool IsValidSequenceLevel(int sequenceLevelIndex) => sequenceLevelIndex is < 24 or 31; + /// + /// Returns the smallest value for k such that blockSize << k is greater than or equal to target. + /// public static int TileLog2(int blockSize, int target) { int k; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs index 07d4a6cd8d..a31235322c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSegmentationParameters.cs @@ -17,6 +17,25 @@ internal class ObuSegmentationParameters public int LastActiveSegmentId { get; internal set; } + /// + /// Gets or sets the SegmentationUpdateMap. A value of 1 indicates that the segmentation map are updated during the decoding of this + /// frame. SegmentationUpdateMap equal to 0 means that the segmentation map from the previous frame is used. + /// + public int SegmentationUpdateMap { get; internal set; } + + /// + /// Gets or sets the SegmentationTemporalUpdate. A value of 1 indicates that the updates to the segmentation map are coded relative to the + /// existing segmentation map. SegmentationTemporalUpdate equal to 0 indicates that the new segmentation map is coded + /// without reference to the existing segmentation map. + /// + public int SegmentationTemporalUpdate { get; internal set; } + + /// + /// Gets or sets SegmentationUpdateData. A value of 1 indicates that new parameters are about to be specified for each segment. + /// SegmentationUpdateData equal to 0 indicates that the segmentation parameters should keep their existing values. + /// + public int SegmentationUpdateData { get; internal set; } + internal bool IsFeatureActive(int segmentId, ObuSegmentationLevelFeature feature) => this.FeatureEnabled[segmentId, (int)feature]; } From dd9f3832f74404bffc03090d144c14bf5b857228 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 25 Jun 2024 20:27:21 +0200 Subject: [PATCH 080/190] Fix escape < sign in XML comment --- src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index dec0602523..fc41dd5b24 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1531,7 +1531,7 @@ private static bool IsValidSequenceLevel(int sequenceLevelIndex) => sequenceLevelIndex is < 24 or 31; /// - /// Returns the smallest value for k such that blockSize << k is greater than or equal to target. + /// Returns the smallest value for k such that blockSize << k is greater than or equal to target. /// public static int TileLog2(int blockSize, int target) { From 1d288d382b58a6a17fb65203819663dd1ceb9b9a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Jun 2024 20:03:53 +0200 Subject: [PATCH 081/190] Implement ReadFilmGrainFilterParameters() --- .../ObuFilmGrainParameters.cs | 167 ++++++++++++++++++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 129 +++++++++++++- 2 files changed, 292 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs index 67b8f352ed..f4c0888dce 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs @@ -1,9 +1,176 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Diagnostics.Metrics; +using System.Runtime.Intrinsics.X86; +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuFilmGrainParameters { + /// + /// Gets or sets ApplyGrain. A value equal to 1 specifies that film grain should be added to this frame. A value equal to 0 specifies that film + /// grain should not be added. + /// public bool ApplyGrain { get; set; } + + /// + /// Gets or sets GrainSeed. This value specifies the starting value for the pseudo-random numbers used during film grain synthesis. + /// + public uint GrainSeed { get; set; } + + /// + /// Gets or sets UpdateGrain. A value equal to 1 means that a new set of parameters should be sent. A value equal to 0 means that the + /// previous set of parameters should be used. + /// + public bool UpdateGrain { get; set; } + + /// + /// Gets or sets FilmGrainParamsRefIdx. Indicates which reference frame contains the film grain parameters to be used for this frame. + /// It is a requirement of bitstream conformance that FilmGrainParamsRefIdx is equal to ref_frame_idx[ j ] for some value + /// of j in the range 0 to REFS_PER_FRAME - 1. + /// + public uint FilmGrainParamsRefidx { get; set; } + + /// + /// Gets or sets NumYPoints. Specifies the number of points for the piece-wise linear scaling function of the luma component. + /// It is a requirement of bitstream conformance that NumYPoints is less than or equal to 14. + /// + public uint NumYPoints { get; set; } + + /// + /// Gets or sets PointYValue. Represents the x (luma value) coordinate for the i-th point of the piecewise linear scaling function for + /// luma component.The values are signaled on the scale of 0..255. (In case of 10 bit video, these values correspond to + /// luma values divided by 4. In case of 12 bit video, these values correspond to luma values divided by 16.) + /// + /// If i is greater than 0, it is a requirement of bitstream conformance that point_y_value[ i ] is greater than point_y_value[ i - 1] (this ensures the x coordinates are specified in increasing order). + /// + public uint[]? PointYValue { get; set; } + + /// + /// Gets or sets PointYScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for luma component. + /// + public uint[]? PointYScaling { get; set; } + + /// + /// Gets or sets ChromaScalingFromLuma. Specifies that the chroma scaling is inferred from the luma scaling. + /// + public bool ChromaScalingFromLuma { get; set; } + + /// + /// Gets or sets NumCbPoints. Specifies the number of points for the piece-wise linear scaling function of the cb component. + /// It is a requirement of bitstream conformance that NumCbPoints is less than or equal to 10. + /// + public uint NumCbPoints { get; set; } + + /// + /// Gets or sets NumCrPoints. Specifies represents the number of points for the piece-wise linear scaling function of the cr component. + /// It is a requirement of bitstream conformance that NumCrPoints is less than or equal to 10. + /// + public uint NumCrPoints { get; set; } + + /// + /// Gets or sets PointCbValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cb + /// component.The values are signaled on the scale of 0..255. + /// If i is greater than 0, it is a requirement of bitstream conformance that point_cb_value[ i ] is greater than point_cb_value[ i - 1 ]. + /// + public uint[]? PointCbValue { get; set; } + + /// + /// Gets or sets PointCbScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cb component. + /// + public uint[]? PointCbScaling { get; set; } + + /// + /// Gets or sets PointCrValue. Represents the x coordinate for the i-th point of the piece-wise linear scaling function for cr component. + /// The values are signaled on the scale of 0..255. + /// If i is greater than 0, it is a requirement of bitstream conformance that point_cr_value[ i ] is greater than point_cr_value[ i - 1 ]. + /// + public uint[]? PointCrValue { get; set; } + + /// + /// Gets or sets PointCrScaling. Represents the scaling (output) value for the i-th point of the piecewise linear scaling function for cr component. + /// + public uint[]? PointCrScaling { get; set; } + + /// + /// Gets or sets GrainScalingMinus8. represents the shift – 8 applied to the values of the chroma component. The + /// grain_scaling_minus_8 can take values of 0..3 and determines the range and quantization step of the standard deviation of film grain. + /// + public uint GrainScalingMinus8 { get; set; } + + /// + /// Gets or sets ArCoeffLag. Specifies the number of auto-regressive coefficients for luma and chroma. + /// + public uint ArCoeffLag { get; set; } + + /// + /// Gets or sets ArCoeffsYPlus128. Specifies auto-regressive coefficients used for the Y plane. + /// + public uint[]? ArCoeffsYPlus128 { get; set; } + + /// + /// Gets or sets ArCoeffsCbPlus128. Specifies auto-regressive coefficients used for the U plane. + /// + public uint[]? ArCoeffsCbPlus128 { get; set; } + + /// + /// Gets or sets ArCoeffsCrPlus128. Specifies auto-regressive coefficients used for the V plane. + /// + public uint[]? ArCoeffsCrPlus128 { get; set; } + + /// + /// Gets or sets ArCoeffShiftMinus6. Specifies the range of the auto-regressive coefficients. Values of 0, 1, 2, and 3 correspond to the + /// ranges for auto-regressive coefficients of[-2, 2), [-1, 1), [-0.5, 0.5) and [-0.25, 0.25) respectively. + /// + public uint ArCoeffShiftMinus6 { get; set; } + + /// + /// Gets or sets GrainScaleShift. Specifies how much the Gaussian random numbers should be scaled down during the grain synthesis process. + /// + public uint GrainScaleShift { get; set; } + + /// + /// Gets or sets CbMult. Represents a multiplier for the cb component used in derivation of the input index to the cb component scaling function. + /// + public uint CbMult { get; set; } + + /// + /// Gets or sets CbLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cb component scaling function. + /// + public uint CbLumaMult { get; set; } + + /// + /// Gets or sets CbOffset. Represents an offset used in derivation of the input index to the cb component scaling function. + /// + public uint CbOffset { get; set; } + + /// + /// Gets or sets CrMult. Represents a multiplier for the cr component used in derivation of the input index to the cr component scaling function. + /// + public uint CrMult { get; set; } + + /// + /// Gets or sets CrLumaMult. Represents a multiplier for the average luma component used in derivation of the input index to the cr component scaling function. + /// + public uint CrLumaMult { get; set; } + + /// + /// Gets or sets CrOffset. Represents an offset used in derivation of the input index to the cr component scaling function. + /// + public uint CrOffset { get; set; } + + /// + /// Gets or sets OverlapFlag. Equal to true indicates that the overlap between film grain blocks shall be applied. overlap_flag equal to false + /// indicates that the overlap between film grain blocks shall not be applied. + /// + public bool OverlapFlag { get; set; } + + /// + /// Gets or sets ClipToRestrictedRange. equal to true indicates that clipping to the restricted (studio) range shall be applied to the sample + /// values after adding the film grain(see the semantics for color_range for an explanation of studio swing). + /// clip_to_restricted_range equal to false indicates that clipping to the full range shall be applied to the sample values after adding the film grain. + /// + public bool ClipToRestrictedRange { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index fc41dd5b24..83feaddb69 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections.Generic; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -1523,8 +1521,131 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt return grainParams; } - // TODO: Implement parsing. - throw new NotImplementedException(); + grainParams.GrainSeed = reader.ReadLiteral(16); + + if (frameInfo.FrameType == ObuFrameType.InterFrame) + { + grainParams.UpdateGrain = reader.ReadBoolean(); + } + else + { + grainParams.UpdateGrain = false; + } + + if (!grainParams.UpdateGrain) + { + grainParams.FilmGrainParamsRefidx = reader.ReadLiteral(3); + uint tempGrainSeed = grainParams.GrainSeed; + + // TODO: implement load_grain_params + // load_grain_params(film_grain_params_ref_idx) + grainParams.GrainSeed = tempGrainSeed; + return grainParams; + } + + grainParams.NumYPoints = reader.ReadLiteral(4); + grainParams.PointYValue = new uint[grainParams.NumYPoints]; + grainParams.PointYScaling = new uint[grainParams.NumYPoints]; + for (int i = 0; i < grainParams.NumYPoints; i++) + { + grainParams.PointYValue[i] = reader.ReadLiteral(8); + grainParams.PointYScaling[i] = reader.ReadLiteral(8); + } + + if (sequenceHeader.ColorConfig.IsMonochrome) + { + grainParams.ChromaScalingFromLuma = false; + } + else + { + grainParams.ChromaScalingFromLuma = reader.ReadBoolean(); + } + + if (sequenceHeader.ColorConfig.IsMonochrome || + grainParams.ChromaScalingFromLuma || + (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && grainParams.NumYPoints == 0)) + { + grainParams.NumCbPoints = 0; + grainParams.NumCrPoints = 0; + } + else + { + grainParams.NumCbPoints = reader.ReadLiteral(4); + grainParams.PointCbValue = new uint[grainParams.NumCbPoints]; + grainParams.PointCbScaling = new uint[grainParams.NumCbPoints]; + for (int i = 0; i < grainParams.NumCbPoints; i++) + { + grainParams.PointCbValue[i] = reader.ReadLiteral(8); + grainParams.PointCbScaling[i] = reader.ReadLiteral(8); + } + + grainParams.NumCrPoints = reader.ReadLiteral(4); + grainParams.PointCrValue = new uint[grainParams.NumCrPoints]; + grainParams.PointCrScaling = new uint[grainParams.NumCrPoints]; + for (int i = 0; i < grainParams.NumCbPoints; i++) + { + grainParams.PointCrValue[i] = reader.ReadLiteral(8); + grainParams.PointCrScaling[i] = reader.ReadLiteral(8); + } + } + + grainParams.GrainScalingMinus8 = reader.ReadLiteral(2); + grainParams.ArCoeffLag = reader.ReadLiteral(2); + uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1); + + uint numPosChroma = 0; + if (grainParams.NumYPoints != 0) + { + numPosChroma = numPosLuma + 1; + grainParams.ArCoeffsYPlus128 = new uint[numPosLuma]; + for (int i = 0; i < numPosLuma; i++) + { + grainParams.ArCoeffsYPlus128[i] = reader.ReadLiteral(8); + } + } + else + { + numPosChroma = numPosLuma; + } + + if (grainParams.ChromaScalingFromLuma || grainParams.NumCbPoints != 0) + { + grainParams.ArCoeffsCbPlus128 = new uint[numPosChroma]; + for (int i = 0; i < numPosChroma; i++) + { + grainParams.ArCoeffsCbPlus128[i] = reader.ReadLiteral(8); + } + } + + if (grainParams.ChromaScalingFromLuma || grainParams.NumCrPoints != 0) + { + grainParams.ArCoeffsCrPlus128 = new uint[numPosChroma]; + for (int i = 0; i < numPosChroma; i++) + { + grainParams.ArCoeffsCrPlus128[i] = reader.ReadLiteral(8); + } + } + + grainParams.ArCoeffShiftMinus6 = reader.ReadLiteral(2); + grainParams.GrainScaleShift = reader.ReadLiteral(2); + if (grainParams.NumCbPoints != 0) + { + grainParams.CbMult = reader.ReadLiteral(8); + grainParams.CbLumaMult = reader.ReadLiteral(8); + grainParams.CbOffset = reader.ReadLiteral(8); + } + + if (grainParams.NumCrPoints != 0) + { + grainParams.CrMult = reader.ReadLiteral(8); + grainParams.CrLumaMult = reader.ReadLiteral(8); + grainParams.CrOffset = reader.ReadLiteral(8); + } + + grainParams.OverlapFlag = reader.ReadBoolean(); + grainParams.ClipToRestrictedRange = reader.ReadBoolean(); + + return grainParams; } private static bool IsValidSequenceLevel(int sequenceLevelIndex) From 4d79d59bf14b2f51aa03b929adcb966976e4313e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 26 Jun 2024 20:22:10 +0200 Subject: [PATCH 082/190] Fix stylecop warnings --- .../OpenBitstreamUnit/ObuFilmGrainParameters.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs index f4c0888dce..42a3b6578d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFilmGrainParameters.cs @@ -1,16 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Diagnostics.Metrics; -using System.Runtime.Intrinsics.X86; -using System; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuFilmGrainParameters { /// - /// Gets or sets ApplyGrain. A value equal to 1 specifies that film grain should be added to this frame. A value equal to 0 specifies that film + /// Gets or sets a value indicating whether film grain should be added to this frame. A value equal to false specifies that film /// grain should not be added. /// public bool ApplyGrain { get; set; } @@ -21,7 +17,7 @@ internal class ObuFilmGrainParameters public uint GrainSeed { get; set; } /// - /// Gets or sets UpdateGrain. A value equal to 1 means that a new set of parameters should be sent. A value equal to 0 means that the + /// Gets or sets a value indicating whether a new set of parameters should be sent. A value equal to false means that the /// previous set of parameters should be used. /// public bool UpdateGrain { get; set; } @@ -54,7 +50,7 @@ internal class ObuFilmGrainParameters public uint[]? PointYScaling { get; set; } /// - /// Gets or sets ChromaScalingFromLuma. Specifies that the chroma scaling is inferred from the luma scaling. + /// Gets or sets a value indicating whether the chroma scaling is inferred from the luma scaling. /// public bool ChromaScalingFromLuma { get; set; } @@ -162,15 +158,15 @@ internal class ObuFilmGrainParameters public uint CrOffset { get; set; } /// - /// Gets or sets OverlapFlag. Equal to true indicates that the overlap between film grain blocks shall be applied. overlap_flag equal to false + /// Gets or sets a value indicating whether the overlap between film grain blocks shall be applied. OverlapFlag equal to false /// indicates that the overlap between film grain blocks shall not be applied. /// public bool OverlapFlag { get; set; } /// - /// Gets or sets ClipToRestrictedRange. equal to true indicates that clipping to the restricted (studio) range shall be applied to the sample + /// Gets or sets a value indicating whether clipping to the restricted (studio) range shall be applied to the sample /// values after adding the film grain(see the semantics for color_range for an explanation of studio swing). - /// clip_to_restricted_range equal to false indicates that clipping to the full range shall be applied to the sample values after adding the film grain. + /// ClipToRestrictedRange equal to false indicates that clipping to the full range shall be applied to the sample values after adding the film grain. /// public bool ClipToRestrictedRange { get; set; } } From 6aa609fa3453735834b5abc0ee9955c5ff509869 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 27 Jun 2024 00:55:41 +0200 Subject: [PATCH 083/190] Strength test criteria --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 4 +- .../Heif/Av1/Av1BlockSizeExtensions.cs | 4 +- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 51 ++++++++++--------- 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index 99ba383ce8..c5bd837fdb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader { - private const int WordSize = 1 << WordSizeLog2; + public const int WordSize = 1 << WordSizeLog2; private const int WordSizeLog2 = 5; private const int WordSizeInBytesLog2 = WordSizeLog2 - Log2Of8; private const int Log2Of8 = 3; @@ -182,7 +182,7 @@ public Span GetSymbolReader(int tileDataSize) // TODO: Pass exact byte iso Word start. int spanLength = tileDataSize >> WordSizeInBytesLog2; Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); - this.bitOffset += tileDataSize << Log2Of8; + this.Skip(tileDataSize << Log2Of8); return MemoryMarshal.Cast(span); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 9c16b63def..a269c01071 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -68,13 +68,13 @@ public static int GetHeight(this Av1BlockSize blockSize) /// Returns base 2 logarithm of the width of the block in units of 4 samples. /// public static int Get4x4WidthLog2(this Av1BlockSize blockSize) - => Get4x4WideCount(blockSize) << 2; + => Av1Math.Log2(Get4x4WideCount(blockSize)); /// /// Returns base 2 logarithm of the height of the block in units of 4 samples. /// public static int Get4x4HeightLog2(this Av1BlockSize blockSize) - => Get4x4HighCount(blockSize) << 2; + => Av1Math.Log2(Get4x4HighCount(blockSize)); /// /// Returns the block size of a sub sampled block. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 2340fe427b..a56daacc6e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -32,31 +32,36 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); Assert.NotNull(obuReader.FrameHeader.TilesInfo); + Assert.Equal(reader.Length * Av1BitStreamReader.WordSize, reader.BitPosition); + Assert.Equal(reader.Length * 4, blockSize); } - /* [Theory] - // [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] - // public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) - // { - // // Assign - // string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); - // byte[] content = File.ReadAllBytes(filePath); - // Span span = content.AsSpan(fileOffset, blockSize); - // IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); - // Av1BitStreamReader reader = new(span); - - // // Act 1 - // ObuReader.Read(ref reader, blockSize, tileDecoder); - - // // Assign 2 - // MemoryStream encoded = new(); - - // // Act 2 - // ObuWriter.Write(encoded, tileDecoder); - - // // Assert - // Assert.Equal(span, encoded.ToArray()); - //} + /* + [Theory] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) + { + // Assign + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + byte[] content = File.ReadAllBytes(filePath); + Span span = content.AsSpan(fileOffset, blockSize); + Av1TileDecoderStub tileDecoder = new(); + Av1BitStreamReader reader = new(span); + ObuReader obuReader = new(); + + // Act 1 + obuReader.Read(ref reader, blockSize, tileDecoder); + + // Assign 2 + MemoryStream encoded = new(); + + // Act 2 + ObuWriter obuWriter = new(); + ObuWriter.Write(encoded, obuReader.SequenceHeader, obuReader.FrameHeader); + + // Assert + Assert.Equal(span, encoded.ToArray()); + } */ [Theory] From 136110fe5eecb7ebba30fa3f915e0da552df5c03 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 28 Jun 2024 20:26:08 +0200 Subject: [PATCH 084/190] Adjustments in ReadSequenceHeader() for case when image is not ReducedStillPictureHeader (still incomplete) --- .../OpenBitstreamUnit/ObuOperatingPoint.cs | 12 +++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 84 +++++++++++++++---- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 2 + 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs index a1401b1aa8..be3ad61f53 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs @@ -14,4 +14,16 @@ internal class ObuOperatingPoint internal bool IsDecoderModelPresent { get; set; } internal bool IsInitialDisplayDelayPresent { get; set; } + + internal uint InitialDisplayDelay { get; set; } + + /// + /// Gets or sets of sets the Idc bitmask. The bitmask that indicates which spatial and temporal layers should be decoded for + /// operating point i.Bit k is equal to 1 if temporal layer k should be decoded(for k between 0 and 7). Bit j+8 is equal to 1 if + /// spatial layer j should be decoded(for j between 0 and 3). + /// However, if operating_point_idc[i] is equal to 0 then the coded video sequence has no scalability information in OBU + /// extension headers and the operating point applies to the entire coded video sequence.This means that all OBUs must be decoded. + /// It is a requirement of bitstream conformance that operating_point_idc[i] is not equal to operating_point_idc[j] for j = 0..(i- 1). + /// + internal uint Idc { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 83feaddb69..9dada5c37a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -265,27 +265,77 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.IsStillPicture = reader.ReadBoolean(); sequenceHeader.IsReducedStillPictureHeader = reader.ReadBoolean(); - if (!sequenceHeader.IsStillPicture || !sequenceHeader.IsReducedStillPictureHeader) + if (sequenceHeader.IsReducedStillPictureHeader) { - throw new ImageFormatException("Not a picture header, is this a movie file ??"); - } + sequenceHeader.TimingInfo = null; + sequenceHeader.DecoderModelInfoPresentFlag = false; + sequenceHeader.InitialDisplayDelayPresentFlag = false; + sequenceHeader.OperatingPoint = new ObuOperatingPoint[1]; + ObuOperatingPoint operatingPoint = new(); + sequenceHeader.OperatingPoint[0] = operatingPoint; + operatingPoint.OperatorIndex = 0; + operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(Av1Constants.LevelBits); + if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) + { + throw new ImageFormatException("Invalid sequence level."); + } - sequenceHeader.TimingInfo = null; - sequenceHeader.DecoderModelInfoPresentFlag = false; - sequenceHeader.InitialDisplayDelayPresentFlag = false; - sequenceHeader.OperatingPoint = new ObuOperatingPoint[1]; - ObuOperatingPoint operatingPoint = new(); - sequenceHeader.OperatingPoint[0] = operatingPoint; - operatingPoint.OperatorIndex = 0; - operatingPoint.SequenceLevelIndex = (int)reader.ReadLiteral(Av1Constants.LevelBits); - if (!IsValidSequenceLevel(sequenceHeader.OperatingPoint[0].SequenceLevelIndex)) - { - throw new ImageFormatException("Invalid sequence level."); + operatingPoint.SequenceTier = 0; + operatingPoint.IsDecoderModelPresent = false; + operatingPoint.IsInitialDisplayDelayPresent = false; } + else + { + sequenceHeader.TimingInfoPresentFlag = reader.ReadBoolean(); + if (sequenceHeader.TimingInfoPresentFlag) + { + sequenceHeader.DecoderModelInfoPresentFlag = reader.ReadBoolean(); + if (sequenceHeader.DecoderModelInfoPresentFlag) + { + // TODO: read decoder_model_info( ) + } + } + + sequenceHeader.InitialDisplayDelayPresentFlag = reader.ReadBoolean(); + uint operatingPointsCnt = reader.ReadLiteral(5) + 1; + sequenceHeader.OperatingPoint = new ObuOperatingPoint[operatingPointsCnt]; + for (int i = 0; i < operatingPointsCnt; i++) + { + sequenceHeader.OperatingPoint[i] = new ObuOperatingPoint(); + sequenceHeader.OperatingPoint[i].Idc = reader.ReadLiteral(12); + sequenceHeader.OperatingPoint[i].SequenceLevelIndex = (int)reader.ReadLiteral(5); + if (sequenceHeader.OperatingPoint[i].SequenceLevelIndex > 7) + { + sequenceHeader.OperatingPoint[i].SequenceTier = (int)reader.ReadLiteral(1); + } + else + { + sequenceHeader.OperatingPoint[i].SequenceTier = 0; + } + + if (sequenceHeader.DecoderModelInfoPresentFlag) + { + sequenceHeader.OperatingPoint[i].IsDecoderModelPresent = reader.ReadBoolean(); + if (sequenceHeader.OperatingPoint[i].IsDecoderModelPresent) + { + // TODO: operating_parameters_info( i ) + } + } + else + { + sequenceHeader.OperatingPoint[i].IsDecoderModelPresent = false; + } - operatingPoint.SequenceTier = 0; - operatingPoint.IsDecoderModelPresent = false; - operatingPoint.IsInitialDisplayDelayPresent = false; + if (sequenceHeader.InitialDisplayDelayPresentFlag) + { + sequenceHeader.OperatingPoint[i].IsInitialDisplayDelayPresent = reader.ReadBoolean(); + if (sequenceHeader.OperatingPoint[i].IsInitialDisplayDelayPresent) + { + sequenceHeader.OperatingPoint[i].InitialDisplayDelay = reader.ReadLiteral(4) + 1; + } + } + } + } // Video related flags removed diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 0b970091af..40d84c4819 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -21,6 +21,8 @@ internal class ObuSequenceHeader public bool DecoderModelInfoPresentFlag { get; set; } + public bool TimingInfoPresentFlag { get; set; } + public object? TimingInfo { get; set; } public bool IsFrameIdNumbersPresent { get; set; } From 2accf9513cfefaee9d09fd626bc72fceaa05e895 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 29 Jun 2024 16:04:27 +0200 Subject: [PATCH 085/190] Implement 5.5.4. read Decoder model info --- .../OpenBitstreamUnit/ObuDecoderModelInfo.cs | 29 +++++++++++++++++++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 13 ++++++++- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 2 ++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs new file mode 100644 index 0000000000..bc7f96d9ef --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDecoderModelInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal class ObuDecoderModelInfo +{ + /// + /// Gets or sets BufferDelayLength. Specifies the length of the decoder_buffer_delay and the encoder_buffer_delay + /// syntax elements, in bits. + /// + internal uint BufferDelayLength { get; set; } + + /// + /// Gets or sets NumUnitsInDecodingTick. This is the number of time units of a decoding clock operating at the frequency time_scale Hz + /// that corresponds to one increment of a clock tick counter. + /// + internal uint NumUnitsInDecodingTick { get; set; } + + /// + /// Gets or sets BufferRemovalTimeLength. Specifies the length of the buffer_removal_time syntax element, in bits. + /// + internal uint BufferRemovalTimeLength { get; set; } + + /// + /// Gets or sets the FramePresentationTimeLength. Specifies the length of the frame_presentation_time syntax element, in bits. + /// + internal uint FramePresentationTimeLength { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 9dada5c37a..efd1bc224f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -292,7 +292,7 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.DecoderModelInfoPresentFlag = reader.ReadBoolean(); if (sequenceHeader.DecoderModelInfoPresentFlag) { - // TODO: read decoder_model_info( ) + ReadDecoderModelInfo(ref reader, sequenceHeader); } } @@ -461,6 +461,17 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu return colorConfig; } + /// + /// 5.5.4. Decoder model info syntax. + /// + private static void ReadDecoderModelInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) => sequenceHeader.DecoderModelInfo = new ObuDecoderModelInfo + { + BufferDelayLength = reader.ReadLiteral(5) + 1, + NumUnitsInDecodingTick = reader.ReadLiteral(16), + BufferRemovalTimeLength = reader.ReadLiteral(5) + 1, + FramePresentationTimeLength = reader.ReadLiteral(5) + 1 + }; + private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) { bool hasHighBitDepth = reader.ReadBoolean(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 40d84c4819..4f0dbf83de 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -17,6 +17,8 @@ internal class ObuSequenceHeader public ObuOperatingPoint[] OperatingPoint { get; set; } = new ObuOperatingPoint[1]; + public ObuDecoderModelInfo? DecoderModelInfo { get; set; } + public bool InitialDisplayDelayPresentFlag { get; set; } public bool DecoderModelInfoPresentFlag { get; set; } From 41b980cae012b03460a4c383498259dac6857b49 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 29 Jun 2024 17:14:55 +0200 Subject: [PATCH 086/190] Implement missing parts in ReadSequenceHeader for case when ReducedStillPictureHeader is false --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 89 ++++++++++++++++--- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 4 + 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index efd1bc224f..b501584981 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -345,7 +345,20 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.FrameHeightBits = (int)reader.ReadLiteral(4) + 1; sequenceHeader.MaxFrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; sequenceHeader.MaxFrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; - sequenceHeader.IsFrameIdNumbersPresent = false; + if (sequenceHeader.IsReducedStillPictureHeader) + { + sequenceHeader.IsFrameIdNumbersPresent = false; + } + else + { + sequenceHeader.IsFrameIdNumbersPresent = reader.ReadBoolean(); + } + + if (sequenceHeader.IsFrameIdNumbersPresent) + { + sequenceHeader.DeltaFrameIdLength = (int)reader.ReadLiteral(4) + 2; + sequenceHeader.AdditionalFrameIdLength = reader.ReadLiteral(3) + 1; + } // Video related flags removed sequenceHeader.Use128x128SuperBlock = reader.ReadBoolean(); @@ -354,15 +367,71 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6; sequenceHeader.EnableFilterIntra = reader.ReadBoolean(); sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); - sequenceHeader.EnableInterIntraCompound = false; - sequenceHeader.EnableMaskedCompound = false; - sequenceHeader.EnableWarpedMotion = false; - sequenceHeader.EnableDualFilter = false; - sequenceHeader.OrderHintInfo.EnableJointCompound = false; - sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; - sequenceHeader.ForceScreenContentTools = 2; - sequenceHeader.ForceIntegerMotionVector = 2; - sequenceHeader.OrderHintInfo.OrderHintBits = 0; + + if (sequenceHeader.IsReducedStillPictureHeader) + { + sequenceHeader.EnableInterIntraCompound = false; + sequenceHeader.EnableMaskedCompound = false; + sequenceHeader.EnableWarpedMotion = false; + sequenceHeader.EnableDualFilter = false; + sequenceHeader.OrderHintInfo.EnableJointCompound = false; + sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; + sequenceHeader.ForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS + sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV + sequenceHeader.OrderHintInfo.OrderHintBits = 0; + } + else + { + sequenceHeader.EnableInterIntraCompound = reader.ReadBoolean(); + sequenceHeader.EnableMaskedCompound = reader.ReadBoolean(); + sequenceHeader.EnableWarpedMotion = reader.ReadBoolean(); + sequenceHeader.EnableDualFilter |= reader.ReadBoolean(); + sequenceHeader.EnableOrderHint = reader.ReadBoolean(); + if (sequenceHeader.EnableOrderHint) + { + sequenceHeader.OrderHintInfo.EnableJointCompound = reader.ReadBoolean(); + sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = reader.ReadBoolean(); + } + else + { + sequenceHeader.OrderHintInfo.EnableJointCompound = false; + sequenceHeader.OrderHintInfo.EnableReferenceFrameMotionVectors = false; + } + + bool seqChooseScreenContentTools = reader.ReadBoolean(); + if (seqChooseScreenContentTools) + { + sequenceHeader.ForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS + } + else + { + sequenceHeader.ForceScreenContentTools = (int)reader.ReadLiteral(1); + } + + if (sequenceHeader.ForceScreenContentTools > 0) + { + bool seqChooseIntegerMv = reader.ReadBoolean(); + if (seqChooseIntegerMv) + { + sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV + } + else + { + sequenceHeader.ForceIntegerMotionVector = (int)reader.ReadLiteral(1); + } + } + else + { + if (sequenceHeader.EnableOrderHint) + { + sequenceHeader.OrderHintInfo.OrderHintBits = (int)reader.ReadLiteral(3) + 1; + } + else + { + sequenceHeader.OrderHintInfo.OrderHintBits = 0; + } + } + } // Video related flags removed sequenceHeader.EnableSuperResolution = reader.ReadBoolean(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 4f0dbf83de..e7ce1b4e5c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -51,6 +51,8 @@ internal class ObuSequenceHeader public ObuOrderHintInfo OrderHintInfo { get; set; } = new ObuOrderHintInfo(); + public bool EnableOrderHint { get; set; } + public bool EnableInterIntraCompound { get; set; } public bool EnableMaskedCompound { get; set; } @@ -76,4 +78,6 @@ internal class ObuSequenceHeader public int FrameIdLength { get; set; } public int DeltaFrameIdLength { get; set; } + + public uint AdditionalFrameIdLength { get; set; } } From c4d34c5ea552bb0d44217472c5342b998e89b807 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 29 Jun 2024 18:44:28 +0200 Subject: [PATCH 087/190] Fix issue in ReadSequenceHeader() not reading OrderHintBits --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index b501584981..2c2075f5df 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -422,14 +422,16 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc } else { - if (sequenceHeader.EnableOrderHint) - { - sequenceHeader.OrderHintInfo.OrderHintBits = (int)reader.ReadLiteral(3) + 1; - } - else - { - sequenceHeader.OrderHintInfo.OrderHintBits = 0; - } + sequenceHeader.ForceIntegerMotionVector = 2; // SELECT_INTEGER_MV + } + + if (sequenceHeader.EnableOrderHint) + { + sequenceHeader.OrderHintInfo.OrderHintBits = (int)reader.ReadLiteral(3) + 1; + } + else + { + sequenceHeader.OrderHintInfo.OrderHintBits = 0; } } From a0fdf0239a3ceed0e13732cde380b2d2cb5de182 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 29 Jun 2024 21:26:28 +0200 Subject: [PATCH 088/190] Implement partition context logic --- .../Formats/Heif/Av1/Av1Constants.cs | 10 ++ .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 75 ++++++++ .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 76 +++++++++ .../Heif/Av1/Tiling/Av1PartitionContext.cs | 23 +++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 160 +++++++++++++----- .../Heif/Av1/Tiling/Av1TileLocation.cs | 45 +++++ 6 files changed, 349 insertions(+), 40 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 36e3d19c42..92810728a3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -119,8 +119,18 @@ internal static class Av1Constants /// public const int MaxSegments = 8; + /// + /// Maximum number of color planes. + /// + public const int MaxPlanes = 3; + /// /// Number of reference frame types (including intra type). /// public const int TotalReferencesPerFrame = 8; + + /// + /// Number of values for palette_size. + /// + public const int PaletteMaxSize = 8; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs new file mode 100644 index 0000000000..bc1f669a83 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseAboveNeighbor4x4Context +{ + /* Buffer holding the transform sizes of the previous 4x4 block row. */ + private readonly int[] aboveTransformWidth; + + /* Buffer holding the partition context of the previous 4x4 block row. */ + private int[] abovePartitionWidth; + + /* Buffer holding the sign of the DC coefficients and the + cumulative sum of the coefficient levels of the above 4x4 + blocks corresponding to the current super block row. */ + private int[][] aboveContext = new int[Av1Constants.MaxPlanes][]; + + /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ + private int[] aboveSegmentIdPredictionContext; + + /* Value of base colors for Y, U, and V */ + private int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][]; + + private int[] aboveCompGroupIndex; + + public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) + { + int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount(); + this.aboveTransformWidth = new int[modeInfoColumnCount]; + this.abovePartitionWidth = new int[modeInfoColumnCount]; + for (int i = 0; i < planesCount; i++) + { + this.aboveContext[i] = new int[modeInfoColumnCount]; + this.abovePaletteColors[i] = new int[wide64x64Count * Av1Constants.PaletteMaxSize]; + } + + this.aboveSegmentIdPredictionContext = new int[modeInfoColumnCount]; + this.aboveCompGroupIndex = new int[modeInfoColumnCount]; + } + + public int[] AbovePartitionWidth => this.abovePartitionWidth; + + public void Clear(ObuSequenceHeader sequenceHeader) + { + int planeCount = sequenceHeader.ColorConfig.ChannelCount; + Array.Fill(this.aboveTransformWidth, Av1TransformSize.Size64x64.GetWidth()); + Array.Fill(this.abovePartitionWidth, 0); + for (int i = 0; i < planeCount; i++) + { + Array.Fill(this.aboveContext[i], 0); + Array.Fill(this.abovePaletteColors[i], 0); + } + + Array.Fill(this.aboveSegmentIdPredictionContext, 0); + Array.Fill(this.aboveCompGroupIndex, 0); + } + + public void UpdatePartition(Point modeInfoLocation, Av1TileLocation tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize) + { + int startIndex = modeInfoLocation.X - tileLoc.ModeInfoColumnStart; + int bw = blockSize.Get4x4WideCount(); + int value = Av1PartitionContext.GetAboveContext(subSize); + for (int i = 0; i < bw; i++) + { + this.abovePartitionWidth[startIndex + i] = value; + } + } + + internal void ClearContext(int plane, int offset, int length) + => Array.Fill(this.aboveContext[plane], 0, offset, length); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs new file mode 100644 index 0000000000..9030ea6012 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -0,0 +1,76 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1ParseLeftNeighbor4x4Context +{ + /* Buffer holding the transform sizes of the left 4x4 blocks corresponding + to the current super block row. */ + private readonly int[] leftTransformHeight; + + /* Buffer holding the partition context of the left 4x4 blocks corresponding + to the current super block row. */ + private int[] leftPartitionHeight; + + /* Buffer holding the sign of the DC coefficients and the + cumulative sum of the coefficient levels of the left 4x4 + blocks corresponding to the current super block row. */ + private int[][] leftContext = new int[Av1Constants.MaxPlanes][]; + + /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ + private int[] leftSegmentIdPredictionContext; + + /* Value of base colors for Y, U, and V */ + private int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][]; + + private int[] leftCompGroupIndex; + + public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize) + { + this.leftTransformHeight = new int[superblockModeInfoSize]; + this.leftPartitionHeight = new int[superblockModeInfoSize]; + for (int i = 0; i < planesCount; i++) + { + this.leftContext[i] = new int[superblockModeInfoSize]; + this.leftPaletteColors[i] = new int[superblockModeInfoSize * Av1Constants.PaletteMaxSize]; + } + + this.leftSegmentIdPredictionContext = new int[superblockModeInfoSize]; + this.leftCompGroupIndex = new int[superblockModeInfoSize]; + } + + public int[] LeftPartitionHeight => this.leftPartitionHeight; + + public void Clear(ObuSequenceHeader sequenceHeader) + { + int planeCount = sequenceHeader.ColorConfig.ChannelCount; + Array.Fill(this.leftTransformHeight, Av1TransformSize.Size64x64.GetHeight()); + Array.Fill(this.leftPartitionHeight, 0); + for (int i = 0; i < planeCount; i++) + { + Array.Fill(this.leftContext[i], 0); + Array.Fill(this.leftPaletteColors[i], 0); + } + + Array.Fill(this.leftSegmentIdPredictionContext, 0); + Array.Fill(this.leftCompGroupIndex, 0); + } + + public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize) + { + int startIndex = (modeInfoLocation.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask; + int bh = blockSize.Get4x4HighCount(); + int value = Av1PartitionContext.GetLeftContext(subSize); + for (int i = 0; i < bh; i++) + { + this.leftPartitionHeight[startIndex + i] = value; + } + } + + internal void ClearContext(int plane, int offset, int length) + => Array.Fill(this.leftContext[plane], 0, offset, length); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs new file mode 100644 index 0000000000..6d5feafad5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +// Generates 5 bit field in which each bit set to 1 represents +// a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16 +// and 8x8. 10000 means we just split the 128x128 to 64x64 +internal class Av1PartitionContext +{ + private static readonly int[] AboveLookup = + [31, 31, 30, 30, 30, 28, 28, 28, 24, 24, 24, 16, 16, 16, 0, 0, 31, 28, 30, 24, 28, 16]; + + private static readonly int[] LeftLookup = + [31, 30, 31, 30, 28, 30, 28, 24, 28, 24, 16, 24, 16, 0, 16, 0, 28, 31, 24, 30, 16, 28]; + + // Mask to extract ModeInfo offset within max ModeInfoBlock + public const int Mask = (1 << (7 - 2)) - 1; + + public static int GetAboveContext(Av1BlockSize blockSize) => AboveLookup[(int)blockSize]; + + public static int GetLeftContext(Av1BlockSize blockSize) => LeftLookup[(int)blockSize]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index a1edf35570..6797f433f4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -19,6 +19,8 @@ internal class Av1TileDecoder : IAv1TileDecoder private int[][][] referenceLrWiener = []; private Av1ParseAboveContext aboveContext = new(); private Av1ParseLeftContext leftContext = new(); + private Av1ParseAboveNeighbor4x4Context aboveNeighborContext; + private Av1ParseLeftNeighbor4x4Context leftNeighborContext; private int currentQuantizerIndex; private int[][] aboveLevelContext = []; private int[][] aboveDcContext = []; @@ -37,13 +39,22 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo // init_main_frame_ctxt this.FrameBuffer = new(this.SequenceHeader); - } - - public bool SequenceHeaderDone { get; set; } - - public bool ShowExistingFrame { get; set; } + this.segmentIds = new int[this.FrameInfo.ModeInfoRowCount][]; + for (int y = 0; y < this.FrameInfo.ModeInfoRowCount; y++) + { + this.segmentIds[y] = new int[this.FrameInfo.ModeInfoColumnCount]; + } - public bool SeenFrameHeader { get; set; } + // reallocate_parse_context_memory + // Hard code number of threads to 1 for now. + int planesCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; + int superblockColumnCount = + AlignPowerOfTwo(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperBlockSizeLog2) >> sequenceHeader.SuperBlockSizeLog2; + int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.ModeInfoSize; + modeInfoWideColumnCount = AlignPowerOfTwo(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); + this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); + this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.ModeInfoSize); + } public ObuFrameHeader FrameInfo { get; } @@ -57,7 +68,7 @@ public void DecodeTile(Span tileData, int tileNum) int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; - this.aboveContext.Clear(this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex], this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex - 1]); + this.aboveContext.Clear(this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex], this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; this.referenceSgrXqd = new int[planesCount][]; @@ -74,6 +85,7 @@ public void DecodeTile(Span tileData, int tileNum) } } + // TODO: Initialize this.blockDecoded and this.segmentIds Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; int superBlock4x4Size = superBlockSize.Get4x4WideCount(); for (int row = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; row < this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) @@ -83,38 +95,43 @@ public void DecodeTile(Span tileData, int tileNum) for (int column = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) { int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; - bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + // bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + // bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; bool readDeltas = this.FrameInfo.DeltaQParameters.IsPresent; + Av1TileLocation tileLoc = new(row, column, this.FrameInfo); - this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - - Point superblockPosition = new Point(superBlockColumn, superBlockRow); + // this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); + Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = new(this.FrameBuffer, superblockPosition); // Nothing to do for CDEF // this.ClearCdef(row, column); // this.ReadLoopRestoration(row, column, superBlockSize); - this.ParsePartition(ref reader, row, column, superBlockSize, superblockInfo); + this.ParsePartition(ref reader, new Point(column, row), superBlockSize, superblockInfo, tileLoc); } } } + private static int AlignPowerOfTwo(int value, int n) => (value + ((1 << n) - 1)) & ~((1 << n) - 1); + private void ClearLoopFilterDelta() => this.FrameBuffer.ClearDeltaLoopFilter(); private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + this.blockDecoded = new bool[planesCount][][]; for (int plane = 0; plane < planesCount; plane++) { int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX; int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY; + this.blockDecoded[plane] = new bool[superBlock4x4Size >> subY][]; for (int y = -1; y <= superBlock4x4Size >> subY; y++) { + this.blockDecoded[plane][y] = new bool[superBlock4x4Size >> subX]; for (int x = -1; x <= superBlock4x4Size >> subX; x++) { if (y < 0 && x < superBlock4x4Width) @@ -160,10 +177,11 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } - private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo) + private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileLocation tileLoc) { - if (rowIndex >= this.FrameInfo.TilesInfo.TileRowStartModeInfo[rowIndex] || - columnIndex >= this.FrameInfo.TilesInfo.TileColumnStartModeInfo[columnIndex]) + int columnIndex = modeInfoLocation.X; + int rowIndex = modeInfoLocation.Y; + if (modeInfoLocation.Y >= this.FrameInfo.ModeInfoRowCount || modeInfoLocation.X >= this.FrameInfo.ModeInfoColumnCount) { return; } @@ -173,8 +191,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 2; - bool hasRows = (rowIndex + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; - bool hasColumns = (columnIndex + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; + bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; + bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; Av1PartitionType partitionType = Av1PartitionType.Split; if (blockSize < Av1BlockSize.Block8x8) { @@ -182,18 +200,18 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum } else if (hasRows && hasColumns) { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); partitionType = reader.ReadPartitionType(ctx); } else if (hasColumns) { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } else if (hasRows) { - int ctx = this.GetPartitionContext(rowIndex, columnIndex, blockSize); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; } @@ -203,10 +221,13 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum switch (partitionType) { case Av1PartitionType.Split: - this.ParsePartition(ref reader, rowIndex, columnIndex, subSize, superblockInfo); - this.ParsePartition(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize, superblockInfo); - this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize, superblockInfo); - this.ParsePartition(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo); + Point loc1 = new Point(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); + Point loc2 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); + Point loc3 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size); + this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileLoc); + this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileLoc); + this.ParsePartition(ref reader, loc2, subSize, superblockInfo, tileLoc); + this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileLoc); break; case Av1PartitionType.None: this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.None); @@ -276,6 +297,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, int rowIndex, int colum default: throw new NotImplementedException($"Partition type: {partitionType} is not supported."); } + + this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileLoc, superblockInfo, subSize, blockSize, partitionType); } private void ParseBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) @@ -338,10 +361,8 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) int txsHigh = planeBlockSize.GetHeight() >> 2; int aboveOffset = (partitionInfo.ColumnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); int leftOffset = (partitionInfo.RowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); - int[] aboveContext = this.aboveContext.AboveContext[i + aboveOffset]; - int[] leftContext = this.leftContext.LeftContext[i + leftOffset]; - Array.Fill(aboveContext, 0); - Array.Fill(leftContext, 0); + this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide); + this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh); } } @@ -697,16 +718,17 @@ private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partit this.ReadSegmentId(ref reader, partitionInfo); } - int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); - int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); - int x_mis = Math.Min(this.FrameInfo.ModeInfoColumnCount - partitionInfo.ColumnIndex, bw4); - int y_mis = Math.Min(this.FrameInfo.ModeInfoRowCount - partitionInfo.RowIndex, bh4); - - for (int y = 0; y < y_mis; y++) + int blockWidth4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); + int blockHeight4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); + int modeInfoCountX = Math.Min(this.FrameInfo.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4); + int modeInfoCountY = Math.Min(this.FrameInfo.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4); + int segmentId = partitionInfo.ModeInfo.SegmentId; + for (int y = 0; y < modeInfoCountY; y++) { - for (int x = 0; x < x_mis; x++) + int[] segmentRow = this.segmentIds[partitionInfo.RowIndex + y]; + for (int x = 0; x < modeInfoCountX; x++) { - this.segmentIds[partitionInfo.RowIndex + y][partitionInfo.ColumnIndex + x] = partitionInfo.ModeInfo.SegmentId; + segmentRow[partitionInfo.ColumnIndex + x] = segmentId; } } } @@ -954,14 +976,72 @@ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blo return xPos && yPos; }*/ - private int GetPartitionContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) + private int GetPartitionContext(Point location, Av1BlockSize blockSize, Av1TileLocation tileLoc, int superblockModeInfoRowCount) { // Maximum partition point is 8x8. Offset the log value occordingly. + int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileLoc.ModeInfoColumnStart]; + int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockModeInfoRowCount) & Av1PartitionContext.Mask]; int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); - int aboveCtx = this.aboveContext.PartitionWidth + columnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[columnIndex]; - int leftCtx = this.leftContext.PartitionHeight + rowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[rowIndex]; int above = (aboveCtx >> blockSizeLog) & 0x1; int left = (leftCtx >> blockSizeLog) & 0x1; return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet); } + + private void UpdatePartitionContext(Point modeInfoLocation, Av1TileLocation tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) + { + if (blockSize >= Av1BlockSize.Block8x8) + { + int hbs = blockSize.Get4x4WideCount() / 2; + Av1BlockSize blockSize2 = Av1PartitionType.Split.GetBlockSubSize(blockSize); + switch (partition) + { + case Av1PartitionType.Split: + if (blockSize != Av1BlockSize.Block8x8) + { + break; + } + + goto PARTITIONS; + case Av1PartitionType.None: + case Av1PartitionType.Horizontal: + case Av1PartitionType.Vertical: + case Av1PartitionType.Horizontal4: + case Av1PartitionType.Vertical4: + PARTITIONS: + this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, blockSize); + this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, blockSize); + break; + case Av1PartitionType.HorizontalA: + this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, blockSize2, subSize); + this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, blockSize2, subSize); + Point locHorizontalA = new(modeInfoLocation.X, modeInfoLocation.Y + hbs); + this.aboveNeighborContext.UpdatePartition(locHorizontalA, tileLoc, subSize, subSize); + this.leftNeighborContext.UpdatePartition(locHorizontalA, superblockInfo, subSize, subSize); + break; + case Av1PartitionType.HorizontalB: + this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, subSize); + this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, subSize); + Point locHorizontalB = new(modeInfoLocation.X, modeInfoLocation.Y + hbs); + this.aboveNeighborContext.UpdatePartition(locHorizontalB, tileLoc, blockSize2, subSize); + this.leftNeighborContext.UpdatePartition(locHorizontalB, superblockInfo, blockSize2, subSize); + break; + case Av1PartitionType.VerticalA: + this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, blockSize2, subSize); + this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, blockSize2, subSize); + Point locVerticalA = new(modeInfoLocation.X + hbs, modeInfoLocation.Y); + this.aboveNeighborContext.UpdatePartition(locVerticalA, tileLoc, subSize, subSize); + this.leftNeighborContext.UpdatePartition(locVerticalA, superblockInfo, subSize, subSize); + break; + case Av1PartitionType.VerticalB: + this.aboveNeighborContext.UpdatePartition(modeInfoLocation, tileLoc, subSize, subSize); + this.leftNeighborContext.UpdatePartition(modeInfoLocation, superblockInfo, subSize, subSize); + Point locVerticalB = new(modeInfoLocation.X, modeInfoLocation.Y + hbs); + this.aboveNeighborContext.UpdatePartition(locVerticalB, tileLoc, blockSize2, subSize); + this.leftNeighborContext.UpdatePartition(locVerticalB, superblockInfo, blockSize2, subSize); + break; + default: + throw new InvalidImageContentException($"Unknown partition type: {partition}"); + } + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs new file mode 100644 index 0000000000..f0821c8c8a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs @@ -0,0 +1,45 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1TileLocation +{ + public Av1TileLocation(int row, int column, ObuFrameHeader frameInfo) + { + this.SetTileRow(frameInfo.TilesInfo, frameInfo.ModeInfoRowCount, row); + this.SetTileColumn(frameInfo.TilesInfo, frameInfo.ModeInfoColumnCount, column); + } + + public int ModeInfoRowStart { get; private set; } + + public int ModeInfoRowEnd { get; private set; } + + public int ModeInfoColumnStart { get; private set; } + + public int ModeInfoColumnEnd { get; private set; } + + public Point TileIndex { get; private set; } + + public int TileIndexInRasterOrder { get; } + + public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row) + { + this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row]; + this.ModeInfoRowEnd = Math.Min(tileGroupHeader.TileRowStartModeInfo[row + 1], modeInfoRowCount); + Point loc = this.TileIndex; + loc.Y = row; + this.TileIndex = loc; + } + + public void SetTileColumn(ObuTileGroupHeader tileGroupHeader, int modeInfoColumnCount, int column) + { + this.ModeInfoColumnStart = tileGroupHeader.TileColumnStartModeInfo[column]; + this.ModeInfoColumnEnd = Math.Min(tileGroupHeader.TileColumnStartModeInfo[column + 1], modeInfoColumnCount); + Point loc = this.TileIndex; + loc.X = column; + this.TileIndex = loc; + } +} From 355692131fe0de2eed480b5ec216cb54f4a72615 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 30 Jun 2024 17:13:19 +0200 Subject: [PATCH 089/190] Implement ReadTimingInfo, it is needed in ReadUncompressedFrameHeader() when IsReducedStillPictureHeader is false --- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 + .../OpenBitstreamUnit/ObuOperatingPoint.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 41 +++++++++++++++++-- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuTimingInfo.cs | 33 +++++++++++++++ 5 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index ffe384e688..b145a7a846 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -71,6 +71,8 @@ internal class ObuFrameHeader internal bool ShowableFrame { get; set; } + internal uint FrameToShowMapIdx { get; set; } + internal bool ErrorResilientMode { get; set; } internal bool AllowScreenContentTools { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs index be3ad61f53..a398124e39 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuOperatingPoint.cs @@ -11,7 +11,7 @@ internal class ObuOperatingPoint internal int SequenceTier { get; set; } - internal bool IsDecoderModelPresent { get; set; } + internal bool IsDecoderModelInfoPresent { get; set; } internal bool IsInitialDisplayDelayPresent { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 2c2075f5df..9c2d791060 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -281,7 +281,7 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc } operatingPoint.SequenceTier = 0; - operatingPoint.IsDecoderModelPresent = false; + operatingPoint.IsDecoderModelInfoPresent = false; operatingPoint.IsInitialDisplayDelayPresent = false; } else @@ -289,11 +289,16 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc sequenceHeader.TimingInfoPresentFlag = reader.ReadBoolean(); if (sequenceHeader.TimingInfoPresentFlag) { + ReadTimingInfo(ref reader, sequenceHeader); sequenceHeader.DecoderModelInfoPresentFlag = reader.ReadBoolean(); if (sequenceHeader.DecoderModelInfoPresentFlag) { ReadDecoderModelInfo(ref reader, sequenceHeader); } + else + { + sequenceHeader.DecoderModelInfoPresentFlag = false; + } } sequenceHeader.InitialDisplayDelayPresentFlag = reader.ReadBoolean(); @@ -315,15 +320,15 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc if (sequenceHeader.DecoderModelInfoPresentFlag) { - sequenceHeader.OperatingPoint[i].IsDecoderModelPresent = reader.ReadBoolean(); - if (sequenceHeader.OperatingPoint[i].IsDecoderModelPresent) + sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent = reader.ReadBoolean(); + if (sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent) { // TODO: operating_parameters_info( i ) } } else { - sequenceHeader.OperatingPoint[i].IsDecoderModelPresent = false; + sequenceHeader.OperatingPoint[i].IsDecoderModelInfoPresent = false; } if (sequenceHeader.InitialDisplayDelayPresentFlag) @@ -543,6 +548,24 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu FramePresentationTimeLength = reader.ReadLiteral(5) + 1 }; + /// + /// 5.5.3. Timing info syntax. + /// + private static void ReadTimingInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) + { + sequenceHeader.TimingInfo = new ObuTimingInfo + { + NumUnitsInDisplayTick = reader.ReadLiteral(32), + TimeScale = reader.ReadLiteral(32), + EqualPictureInterval = reader.ReadBoolean() + }; + + if (sequenceHeader.TimingInfo.EqualPictureInterval) + { + sequenceHeader.TimingInfo.NumTicksPerPicture = reader.ReadUnsignedVariableLength() + 1; + } + } + private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) { bool hasHighBitDepth = reader.ReadBoolean(); @@ -863,6 +886,16 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameInfo.ShowableFrame = false; frameInfo.ErrorResilientMode = true; } + else + { + frameInfo.ShowExistingFrame = reader.ReadBoolean(); + if (frameInfo.ShowExistingFrame) + { + frameInfo.FrameToShowMapIdx = reader.ReadLiteral(3); + } + + // TODO: There is still some parts not implemented here + } if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) { diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index e7ce1b4e5c..b1afb7c7e3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -25,7 +25,7 @@ internal class ObuSequenceHeader public bool TimingInfoPresentFlag { get; set; } - public object? TimingInfo { get; set; } + public ObuTimingInfo? TimingInfo { get; set; } public bool IsFrameIdNumbersPresent { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs new file mode 100644 index 0000000000..d366157e78 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTimingInfo.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuTimingInfo +{ + /// + /// Gets or sets NumUnitsInDisplayTick. NumUnitsInDisplayTick is the number of time units of a clock operating at the frequency TimeScale Hz that + /// corresponds to one increment of a clock tick counter. A display clock tick, in seconds, is equal to + /// NumUnitsInDisplayTick divided by TimeScale. + /// + public uint NumUnitsInDisplayTick { get; set; } + + /// + /// Gets or sets TimeScale. TimeScale is the number of time units that pass in one second. + /// It is a requirement of bitstream conformance that TimeScale is greater than 0. + /// + public uint TimeScale { get; set; } + + /// + /// Gets or sets a value indicating whether that pictures should be displayed according to their output order with the + /// number of ticks between two consecutive pictures (without dropping frames) specified by NumTicksPerPicture. + /// EqualPictureInterval equal to false indicates that the interval between two consecutive pictures is not specified. + /// + public bool EqualPictureInterval { get; set; } + + /// + /// Gets or sets NumTicksPerPicture. NumTicksPerPicture specifies the number of clock ticks corresponding to output time between two + /// consecutive pictures in the output order. + /// + public uint NumTicksPerPicture { get; set; } +} From 0cf272626fa98c4f78579bc51ce7a937d29b8207 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 30 Jun 2024 22:19:43 +0200 Subject: [PATCH 090/190] Tiling initialization fixes --- .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 5 +- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 50 ++++++- .../Heif/Av1/Tiling/Av1ParseAboveContext.cs | 17 --- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 2 +- .../Heif/Av1/Tiling/Av1ParseLeftContext.cs | 13 -- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 122 ++++++++++-------- .../{Av1TileLocation.cs => Av1TileInfo.cs} | 4 +- 7 files changed, 120 insertions(+), 93 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs rename src/ImageSharp/Formats/Heif/Av1/Tiling/{Av1TileLocation.cs => Av1TileInfo.cs} (93%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index 27ba271ab3..b8b8f8bb47 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -9,9 +9,10 @@ internal class Av1BlockModeInfo { private int[] paletteSize; - public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize) + public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) { this.BlockSize = blockSize; + this.PositionInSuperblock = position; this.AngleDelta = new int[numPlanes]; this.paletteSize = new int[numPlanes - 1]; this.FilterIntraModeInfo = new(); @@ -39,7 +40,7 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize) public int[] AngleDelta { get; set; } - public Size IndexInSuperblock { get; set; } + public Point PositionInSuperblock { get; set; } public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 8ae1eec8e1..db8b6ba18f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -39,17 +39,43 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) int superblockCount = this.superblockColumnCount * this.superblockRowCount; this.modeInfoSizePerSuperblock = 1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2); this.modeInfoCountPerSuperblock = this.modeInfoSizePerSuperblock * this.modeInfoSizePerSuperblock; + int numPlanes = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; + + // Allocate the arrays. this.superblockInfos = new Av1SuperblockInfo[superblockCount]; this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock]; - this.coefficientsY = new int[superblockCount * this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo]; + + // Initialize the arrays. + int i = 0; + int j = 0; + for (int y = 0; y < this.superblockRowCount; y++) + { + for (int x = 0; x < this.superblockColumnCount; x++) + { + Point point = new(x, y); + this.superblockInfos[i] = new(this, point); + for (int u = 0; u < this.modeInfoSizePerSuperblock; u++) + { + for (int v = 0; v < this.modeInfoSizePerSuperblock; v++) + { + this.modeInfos[j] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); + j++; + } + } + + i++; + } + } + bool subX = sequenceHeader.ColorConfig.SubSamplingX; bool subY = sequenceHeader.ColorConfig.SubSamplingY; // Factor: 444 => 0, 422 => 1, 420 => 2. this.subsamplingFactor = (subX && subY) ? 2 : (subX && !subY) ? 1 : (!subX && !subY) ? 0 : -1; Guard.IsFalse(this.subsamplingFactor == -1, nameof(this.subsamplingFactor), "Invalid combination of subsampling."); + this.coefficientsY = new int[superblockCount * this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo]; this.coefficientsU = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor]; this.coefficientsV = new int[(this.modeInfoCountPerSuperblock * CoefficientCountPerModeInfo) >> this.subsamplingFactor]; this.deltaQ = new int[superblockCount]; @@ -68,12 +94,19 @@ public Av1SuperblockInfo GetSuperblock(Point index) return span[i]; } + public Av1BlockModeInfo GetModeInfo(Point superblockIndex) + { + Span span = this.modeInfos; + int superblock = (superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X; + return span[superblock * this.modeInfoCountPerSuperblock]; + } + public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) { Span span = this.modeInfos; - int baseIndex = ((superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X) * this.modeInfoCountPerSuperblock; - int offset = (modeInfoIndex.Y * this.modeInfoSizePerSuperblock) + modeInfoIndex.X; - return span[baseIndex + offset]; + int superblock = (superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X; + int modeInfo = (modeInfoIndex.Y * this.modeInfoSizePerSuperblock) + modeInfoIndex.X; + return span[(superblock * this.modeInfoCountPerSuperblock) + modeInfo]; } public Av1TransformInfo GetTransformY(Point index) @@ -126,6 +159,15 @@ public Span GetCdefStrength(Point index) return span.Slice(i, 1 << this.cdefStrengthFactorLog2); } + internal void ClearCdef(Point index) + { + Span cdefs = this.GetCdefStrength(index); + for (int i = 0; i < cdefs.Length; i++) + { + cdefs[i] = -1; + } + } + public Span GetDeltaLoopFilter(Point index) { Span span = this.deltaLoopFilter; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs deleted file mode 100644 index e213005dff..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; - -internal class Av1ParseAboveContext -{ - public int PartitionWidth { get; set; } - - public int[][] AboveContext { get; set; } = []; - - internal void Clear(int startColumnIndex, int endColumnIndex) - { - this.PartitionWidth = -1; - this.AboveContext = []; - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index bc1f669a83..c883d618bf 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -59,7 +59,7 @@ public void Clear(ObuSequenceHeader sequenceHeader) Array.Fill(this.aboveCompGroupIndex, 0); } - public void UpdatePartition(Point modeInfoLocation, Av1TileLocation tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize) + public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize) { int startIndex = modeInfoLocation.X - tileLoc.ModeInfoColumnStart; int bw = blockSize.Get4x4WideCount(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs deleted file mode 100644 index 01219dcfcb..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftContext.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; - -internal class Av1ParseLeftContext -{ - public int PartitionHeight { get; set; } - - public int[][] LeftContext { get; set; } = []; - - internal void Clear() => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 6797f433f4..685d1d6b8d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Data.Common; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using static SixLabors.ImageSharp.PixelFormats.Utils.Vector4Converters; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -17,8 +19,6 @@ internal class Av1TileDecoder : IAv1TileDecoder private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; - private Av1ParseAboveContext aboveContext = new(); - private Av1ParseLeftContext leftContext = new(); private Av1ParseAboveNeighbor4x4Context aboveNeighborContext; private Av1ParseLeftNeighbor4x4Context leftNeighborContext; private int currentQuantizerIndex; @@ -30,12 +30,13 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaWidth; private int maxLumaHeight; private int deltaLoopFilterResolution = -1; - private int deltaQuantizerResolution = -1; + private bool readDeltas; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; + this.readDeltas = frameInfo.DeltaQParameters.IsPresent; // init_main_frame_ctxt this.FrameBuffer = new(this.SequenceHeader); @@ -68,9 +69,11 @@ public void DecodeTile(Span tileData, int tileNum) int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; - this.aboveContext.Clear(this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex], this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]); + this.aboveNeighborContext.Clear(this.SequenceHeader); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + + // Default initialization of Wiener and SGR Filter. this.referenceSgrXqd = new int[planesCount][]; this.referenceLrWiener = new int[planesCount][][]; for (int plane = 0; plane < planesCount; plane++) @@ -85,30 +88,25 @@ public void DecodeTile(Span tileData, int tileNum) } } - // TODO: Initialize this.blockDecoded and this.segmentIds + // TODO: Initialize this.blockDecoded + Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameInfo); Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; int superBlock4x4Size = superBlockSize.Get4x4WideCount(); for (int row = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; row < this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) { int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; - this.leftContext.Clear(); + this.leftNeighborContext.Clear(this.SequenceHeader); for (int column = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) { int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; - - // bool subSamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - // bool subSamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - bool readDeltas = this.FrameInfo.DeltaQParameters.IsPresent; - Av1TileLocation tileLoc = new(row, column, this.FrameInfo); - - // this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); Point superblockPosition = new(superBlockColumn, superBlockRow); - Av1SuperblockInfo superblockInfo = new(this.FrameBuffer, superblockPosition); + Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); - // Nothing to do for CDEF - // this.ClearCdef(row, column); - // this.ReadLoopRestoration(row, column, superBlockSize); - this.ParsePartition(ref reader, new Point(column, row), superBlockSize, superblockInfo, tileLoc); + // this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); + this.FrameBuffer.ClearCdef(superblockPosition); + Point modeInfoLocation = new(column, row); + this.ReadLoopRestoration(modeInfoLocation, superBlockSize); + this.ParsePartition(ref reader, modeInfoLocation, superBlockSize, superblockInfo, tileInfo); } } } @@ -154,7 +152,7 @@ private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) } } - private void ReadLoopRestoration(int row, int column, Av1BlockSize superBlockSize) + private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) { int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; for (int plane = 0; plane < planesCount; plane++) @@ -177,7 +175,7 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } - private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileLocation tileLoc) + private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { int columnIndex = modeInfoLocation.X; int rowIndex = modeInfoLocation.Y; @@ -200,18 +198,18 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } else if (hasRows && hasColumns) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); partitionType = reader.ReadPartitionType(ctx); } else if (hasColumns) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } else if (hasRows) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileLoc, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; } @@ -224,49 +222,59 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Point loc1 = new Point(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); Point loc2 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); Point loc3 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size); - this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileLoc); - this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileLoc); - this.ParsePartition(ref reader, loc2, subSize, superblockInfo, tileLoc); - this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileLoc); + this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo); + this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileInfo); + this.ParsePartition(ref reader, loc2, subSize, superblockInfo, tileInfo); + this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileInfo); break; case Av1PartitionType.None: - this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.None); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.None); break; case Av1PartitionType.Horizontal: - this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); if (hasRows) { - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal); + Point halfLocation = new(columnIndex, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); } break; case Av1PartitionType.Vertical: - this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.Vertical); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Vertical); if (hasRows) { - this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.Vertical); + Point halfLocation = new(columnIndex + halfBlock4x4Size, rowIndex); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Vertical); } break; case Av1PartitionType.HorizontalA: - this.ParseBlock(ref reader, rowIndex, columnIndex, splitSize, superblockInfo, Av1PartitionType.HorizontalA); - this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.HorizontalA); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + Point locHorA1 = new(columnIndex + halfBlock4x4Size, rowIndex); + this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + Point locHorA2 = new(columnIndex, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, Av1PartitionType.HorizontalA); break; case Av1PartitionType.HorizontalB: - this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.HorizontalB); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, splitSize, superblockInfo, Av1PartitionType.HorizontalB); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.HorizontalB); + Point locHorB1 = new(columnIndex + halfBlock4x4Size, rowIndex); + this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + Point locHorB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, Av1PartitionType.HorizontalB); break; case Av1PartitionType.VerticalA: - this.ParseBlock(ref reader, rowIndex, columnIndex, splitSize, superblockInfo, Av1PartitionType.VerticalA); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex, splitSize, superblockInfo, Av1PartitionType.VerticalA); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, subSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.VerticalA); + Point locVertA1 = new(columnIndex, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, Av1PartitionType.VerticalA); + Point locVertA2 = new(columnIndex + halfBlock4x4Size, rowIndex); + this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, Av1PartitionType.VerticalA); break; case Av1PartitionType.VerticalB: - this.ParseBlock(ref reader, rowIndex, columnIndex, subSize, superblockInfo, Av1PartitionType.VerticalB); - this.ParseBlock(ref reader, rowIndex, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.VerticalB); - this.ParseBlock(ref reader, rowIndex + halfBlock4x4Size, columnIndex + halfBlock4x4Size, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.VerticalB); + Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, Av1PartitionType.VerticalB); + Point locVertB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); + this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, Av1PartitionType.VerticalB); break; case Av1PartitionType.Horizontal4: for (int i = 0; i < 4; i++) @@ -277,7 +285,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, break; } - this.ParseBlock(ref reader, currentBlockRow, columnIndex, subSize, superblockInfo, Av1PartitionType.Horizontal4); + Point currentLocation = new(modeInfoLocation.X, currentBlockRow); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Horizontal4); } break; @@ -290,7 +299,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, break; } - this.ParseBlock(ref reader, rowIndex, currentBlockColumn, subSize, superblockInfo, Av1PartitionType.Vertical4); + Point currentLocation = new(currentBlockColumn, modeInfoLocation.Y); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Vertical4); } break; @@ -298,15 +308,17 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, throw new NotImplementedException($"Partition type: {partitionType} is not supported."); } - this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileLoc, superblockInfo, subSize, blockSize, partitionType); + this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType); } - private void ParseBlock(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) + private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) { + int rowIndex = modeInfoLocation.Y; + int columnIndex = modeInfoLocation.X; int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize); + Av1BlockModeInfo blockModeInfo = superblockInfo.GetModeInfo(modeInfoLocation); bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); partitionInfo.ColumnIndex = columnIndex; @@ -383,7 +395,8 @@ private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) int columnChunk = columnIndex + (chunkX << 4); int subBlockRow = rowChunk & superBlockMask; int subBlockColumn = columnChunk & superBlockMask; - for (int plane = 0; plane < 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); plane++) + int endPlane = 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); + for (int plane = 0; plane < endPlane; plane++) { Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, -1); int stepX = transformSize.GetWidth() >> 2; @@ -426,7 +439,7 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid), new(this.FrameBuffer, default), false, Av1PartitionType.None); + Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid, new Point(0, 0)), new(this.FrameBuffer, default), false, Av1PartitionType.None); int startX = (baseX + 4) * x; int startY = (baseY + 4) * y; bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -954,7 +967,8 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn { bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; - this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << this.deltaQuantizerResolution)); + int deltaQuantizerResolution = this.FrameInfo.DeltaQParameters.Resolution; + this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << deltaQuantizerResolution)); partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex; } } @@ -976,7 +990,7 @@ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blo return xPos && yPos; }*/ - private int GetPartitionContext(Point location, Av1BlockSize blockSize, Av1TileLocation tileLoc, int superblockModeInfoRowCount) + private int GetPartitionContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileLoc, int superblockModeInfoRowCount) { // Maximum partition point is 8x8. Offset the log value occordingly. int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileLoc.ModeInfoColumnStart]; @@ -987,7 +1001,7 @@ private int GetPartitionContext(Point location, Av1BlockSize blockSize, Av1TileL return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet); } - private void UpdatePartitionContext(Point modeInfoLocation, Av1TileLocation tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) + private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) { if (blockSize >= Av1BlockSize.Block8x8) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs similarity index 93% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index f0821c8c8a..56d5801333 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileLocation.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs @@ -5,9 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1TileLocation +internal class Av1TileInfo { - public Av1TileLocation(int row, int column, ObuFrameHeader frameInfo) + public Av1TileInfo(int row, int column, ObuFrameHeader frameInfo) { this.SetTileRow(frameInfo.TilesInfo, frameInfo.ModeInfoRowCount, row); this.SetTileColumn(frameInfo.TilesInfo, frameInfo.ModeInfoColumnCount, column); From 5792d05063faa19109c88c1010fec18f3ecce58e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Jul 2024 19:37:22 +0200 Subject: [PATCH 091/190] Annotate Av1TileDecoder methods with spec sections --- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 5 ++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 15 +++++- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 51 +++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index b145a7a846..a107562637 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -73,6 +73,8 @@ internal class ObuFrameHeader internal uint FrameToShowMapIdx { get; set; } + internal uint DisplayFrameId { get; set; } + internal bool ErrorResilientMode { get; set; } internal bool AllowScreenContentTools { get; set; } @@ -88,4 +90,7 @@ internal class ObuFrameHeader internal uint PrimaryReferenceFrame { get; set; } = Av1Constants.PrimaryReferenceFrameNone; internal uint RefreshFrameFlags { get; set; } + + // 5.9.31. Temporal point info syntax + internal uint FramePresentationTime { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 9c2d791060..4736f92342 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -894,7 +894,20 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameInfo.FrameToShowMapIdx = reader.ReadLiteral(3); } - // TODO: There is still some parts not implemented here + if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) + { + // 5.9.31. Temporal point info syntax. + frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); + } + + int refreshFrameFlags = 0; + if (sequenceHeader.IsFrameIdNumbersPresent) + { + frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); + } + + // TODO: This is incomplete here, not sure how we can display an already decoded frame here. + throw new NotImplementedException("ShowExistingFrame is not yet implemented"); } if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 685d1d6b8d..ea0af6ce9b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -116,6 +116,9 @@ public void DecodeTile(Span tileData, int tileNum) private void ClearLoopFilterDelta() => this.FrameBuffer.ClearDeltaLoopFilter(); + /// + /// 5.11.3. Clear block decoded flags function. + /// private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; @@ -175,6 +178,9 @@ public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) // TODO: Implement } + /// + /// 5.11.4. Decode partition syntax. + /// private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { int columnIndex = modeInfoLocation.X; @@ -378,6 +384,9 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) } } + /// + /// 5.11.34. Residual syntax. + /// private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) { bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; @@ -437,6 +446,9 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException(); + /// + /// 5.11.35. Transform block syntax. + /// private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) { Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid, new Point(0, 0)), new(this.FrameBuffer, default), false, Av1PartitionType.None); @@ -522,10 +534,16 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); + /// + /// 7.11.2. Intra prediction process. + /// private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException(); private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); + /// + /// Page 65, below 5.11.5. Decode block syntax. + /// private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) { int block4x4Width = blockSize.Get4x4WideCount(); @@ -552,6 +570,9 @@ private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize block } } + /// + /// 5.11.16. Block TX size syntax. + /// private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) { int block4x4Width = blockSize.Get4x4WideCount(); @@ -568,6 +589,9 @@ private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowI }*/ } + /// + /// 5.11.49. Palette tokens syntax. + /// private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { reader.ReadLiteral(-1); @@ -584,12 +608,18 @@ private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionI } } + /// + /// 5.11.6. Mode info syntax. + /// private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { DebugGuard.IsTrue(this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); this.ReadIntraFrameModeInfo(ref reader, partitionInfo); } + /// + /// 5.11.7. Intra frame mode info syntax. + /// private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) @@ -631,6 +661,8 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf { // this.IsInter = false; partitionInfo.ModeInfo.YMode = reader.ReadYMode(partitionInfo.AboveModeInfo, partitionInfo.LeftModeInfo); + + // 5.11.42.Intra angle info luma syntax. partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Y] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.YMode, partitionInfo.ModeInfo.BlockSize); if (partitionInfo.IsChroma && !this.SequenceHeader.ColorConfig.IsMonochrome) { @@ -640,6 +672,7 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf this.ReadChromaFromLumaAlphas(ref reader, partitionInfo); } + // 5.11.43.Intra angle info chroma syntax. partitionInfo.ModeInfo.AngleDelta[(int)Av1PlaneType.Uv] = IntraAngleInfo(ref reader, partitionInfo.ModeInfo.UvMode, partitionInfo.ModeInfo.BlockSize); } else @@ -699,16 +732,25 @@ private void FilterIntraModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo p } } + /// + /// 5.11.46. Palette mode info syntax. + /// private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); + /// + /// 5.11.45. Read CFL alphas syntax. + /// private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => // TODO: Implement. throw new NotImplementedException(); + /// + /// 5.11.42. and 5.11.43. + /// private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode mode, Av1BlockSize blockSize) { int angleDelta = 0; @@ -724,6 +766,9 @@ private static int IntraAngleInfo(ref Av1SymbolDecoder reader, Av1PredictionMode private static bool IsDirectionalMode(Av1PredictionMode mode) => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; + /// + /// 5.11.8. Intra segment ID syntax. + /// private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { if (this.FrameInfo.SegmentationParameters.Enabled) @@ -746,6 +791,9 @@ private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partit } } + /// + /// 5.11.9. Read segment ID syntax. + /// private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { int predictor; @@ -861,6 +909,9 @@ private static int NegativeDeinterleave(int diff, int reference, int max) } } + /// + /// 5.11.56. Read CDEF syntax. + /// private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { if (partitionInfo.ModeInfo.Skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) From 3382518793344ec64b902743c990b4fa453171ec Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 2 Jul 2024 23:50:31 +0200 Subject: [PATCH 092/190] Fix build --- src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 4736f92342..01df2728e8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -900,7 +900,6 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); } - int refreshFrameFlags = 0; if (sequenceHeader.IsFrameIdNumbersPresent) { frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); From aaac78a07324edd2165255e76fa0434fd0eb23c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 3 Jul 2024 18:12:15 +0200 Subject: [PATCH 093/190] When TemporalDelimiter header is encountered, seenFrameHeader flag will be set to false --- .../Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 01df2728e8..9c6a50fb96 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -128,6 +128,9 @@ public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder de break; case ObuType.TemporalDelimiter: + // 5.6. Temporal delimiter obu syntax. + seenFrameHeader = false; + break; default: // Ignore unknown OBU types. // throw new InvalidImageContentException($"Unknown OBU header found: {header.Type.ToString()}"); @@ -900,6 +903,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); } + // int refreshFrameFlags = 0; if (sequenceHeader.IsFrameIdNumbersPresent) { frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); From e60b39fb256573499c0852926735e4e34852efe8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 3 Jul 2024 18:43:57 +0200 Subject: [PATCH 094/190] Implement Transform parsing --- .../Heif/Av1/Av1BlockSizeExtensions.cs | 19 ++ .../Formats/Heif/Av1/Av1Constants.cs | 5 + .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 10 +- .../Av1/Tiling/Av1DefaultDistributions.cs | 8 + .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 43 ++- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 20 +- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 20 +- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 46 ++++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 27 ++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 246 ++++++++++++++---- .../Heif/Av1/Tiling/Av1TransformInfo.cs | 18 ++ .../Heif/Av1/Transform/Av1TransformSize.cs | 2 +- .../Transform/Av1TransformSizeExtensions.cs | 38 ++- 13 files changed, 440 insertions(+), 62 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index a269c01071..34fe80da3b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -89,6 +90,24 @@ public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, return SubSampled[(int)blockSize][subX ? 1 : 0][subY ? 1 : 0]; } + public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY) + { + Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + Av1TransformSize uvTransformSize = Av1TransformSize.Invalid; + if (planeBlockSize < Av1BlockSize.SizeS) + { + uvTransformSize = planeBlockSize.GetMaximumTransformSize(); + } + + return uvTransformSize switch + { + Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32, + Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16, + Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32, + _ => uvTransformSize, + }; + } + /// /// Returns the largest transform size that can be used for blocks of given size. /// The can be either a square or rectangular block. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 92810728a3..6fd6c536ac 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -133,4 +133,9 @@ internal static class Av1Constants /// Number of values for palette_size. /// public const int PaletteMaxSize = 8; + + /// + /// Maximum transform size categories. + /// + public const int MaxTransformCategories = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index b8b8f8bb47..a78df4428e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -13,9 +13,11 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) { this.BlockSize = blockSize; this.PositionInSuperblock = position; - this.AngleDelta = new int[numPlanes]; + this.AngleDelta = new int[numPlanes - 1]; this.paletteSize = new int[numPlanes - 1]; this.FilterIntraModeInfo = new(); + this.FirstTransformLocation = new int[numPlanes - 1]; + this.TusCount = new int[numPlanes - 1]; } public Av1BlockSize BlockSize { get; } @@ -24,7 +26,7 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) public bool Skip { get; set; } - public Av1PartitionType PartitionType { get; } + public Av1PartitionType PartitionType { get; set; } public bool SkipMode { get; set; } @@ -44,6 +46,10 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } + public int[] FirstTransformLocation { get; } + + public int[] TusCount { get; internal set; } + public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index cb65bfafe4..fbd0d99856 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -171,4 +171,12 @@ internal static class Av1DefaultDistributions new(16384), new(16384), new(16384), new(16384), new(12770), new(10368), new(20229), new(18101), new(16384), new(16384) ]; + + public static Av1Distribution[][] TransformSize => + [ + [new(19968), new(19968), new(24320)], + [new(12272, 30172), new(12272, 30172), new(18677, 30848)], + [new(12986, 15180), new(12986, 15180), new(24302, 25602)], + [new(5782, 11475), new(5782, 11475), new(16803, 22759)], + ]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index db8b6ba18f..03e7100606 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -50,6 +51,7 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) // Initialize the arrays. int i = 0; int j = 0; + int k = 0; for (int y = 0; y < this.superblockRowCount; y++) { for (int x = 0; x < this.superblockColumnCount; x++) @@ -58,11 +60,16 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) this.superblockInfos[i] = new(this, point); for (int u = 0; u < this.modeInfoSizePerSuperblock; u++) { + this.transformInfosY[j] = new Av1TransformInfo(); + this.transformInfosY[j << 1] = new Av1TransformInfo(); + this.transformInfosY[(j << 1) + 1] = new Av1TransformInfo(); for (int v = 0; v < this.modeInfoSizePerSuperblock; v++) { - this.modeInfos[j] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); - j++; + this.modeInfos[k] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); + k++; } + + j++; } i++; @@ -87,6 +94,8 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) this.deltaLoopFilter = new int[superblockCount << this.deltaLoopFactorLog2]; } + public int ModeInfoCount => this.modeInfos.Length; + public Av1SuperblockInfo GetSuperblock(Point index) { Span span = this.superblockInfos; @@ -109,19 +118,35 @@ public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) return span[(superblock * this.modeInfoCountPerSuperblock) + modeInfo]; } - public Av1TransformInfo GetTransformY(Point index) + public ref Av1TransformInfo GetTransformY(int index) + { + Span span = this.transformInfosY; + return ref span[index]; + } + + public void SetTransformY(int index, Av1TransformInfo transformInfo) + { + Span span = this.transformInfosY; + span[index] = transformInfo; + } + + public ref Av1TransformInfo GetTransformY(Point index) { Span span = this.transformInfosY; int i = (index.Y * this.superblockColumnCount) + index.X; - return span[i * this.modeInfoCountPerSuperblock]; + return ref span[i * this.modeInfoCountPerSuperblock]; + } + + public ref Av1TransformInfo GetTransformUv(int index) + { + Span span = this.transformInfosUv; + return ref span[index]; } - public void GetTransformUv(Point index, out Av1TransformInfo transformU, out Av1TransformInfo transformV) + public void SetTransformUv(int index, Av1TransformInfo transformInfo) { Span span = this.transformInfosUv; - int i = 2 * ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock; - transformU = span[i]; - transformV = span[i + 1]; + span[index] = transformInfo; } public Span GetCoefficientsY(Point index) @@ -175,5 +200,5 @@ public Span GetDeltaLoopFilter(Point index) return span.Slice(i, 1 << this.deltaLoopFactorLog2); } - internal void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); + public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index c883d618bf..2efbde6e4c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -44,6 +44,8 @@ public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) public int[] AbovePartitionWidth => this.abovePartitionWidth; + public int[] AboveTransformWidth => this.aboveTransformWidth; + public void Clear(ObuSequenceHeader sequenceHeader) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; @@ -70,6 +72,22 @@ public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1Bloc } } + public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) + { + int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart; + int transformWidth = transformSize.GetWidth(); + int n4w = blockSize.Get4x4WideCount(); + if (skip) + { + transformWidth = n4w * (1 << Av1Constants.ModeInfoSizeLog2); + } + + for (int i = 0; i < n4w; i++) + { + this.aboveTransformWidth[startIndex + i] = transformWidth; + } + } + internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.aboveContext[plane], 0, offset, length); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 9030ea6012..18ea5b5f7f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -2,7 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -45,6 +45,8 @@ public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSiz public int[] LeftPartitionHeight => this.leftPartitionHeight; + public int[] LeftTransformHeight => this.leftTransformHeight; + public void Clear(ObuSequenceHeader sequenceHeader) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; @@ -71,6 +73,22 @@ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblock } } + public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) + { + int startIndex = modeInfoLocation.Y - superblockInfo.Position.Y; + int transformHeight = transformSize.GetHeight(); + int n4h = blockSize.Get4x4HighCount(); + if (skip) + { + transformHeight = n4h * (1 << Av1Constants.ModeInfoSizeLog2); + } + + for (int i = 0; i < n4h; i++) + { + this.leftTransformHeight[startIndex + i] = transformHeight; + } + } + internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.leftContext[plane], 0, offset, length); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index d6dc9ffa65..cfacff6d10 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -1,10 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1PartitionInfo { + private int modeBlockToLeftEdge; + private int modeBlockToRightEdge; + private int modeBlockToTopEdge; + private int modeBlockToBottomEdge; + public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) { this.ModeInfo = modeInfo; @@ -42,4 +49,43 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI public int[][] CdefStrength { get; set; } public int[] ReferenceFrame { get; set; } + + public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInfo) + { + Av1BlockSize blockSize = this.ModeInfo.BlockSize; + int bw4 = blockSize.Get4x4WideCount(); + int bh4 = blockSize.Get4x4HighCount(); + this.AvailableUp = this.RowIndex > tileInfo.ModeInfoRowStart; + this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart; + this.AvailableUpForChroma = this.AvailableUp; + this.AvailableLeftForChroma = this.AvailableLeft; + this.modeBlockToLeftEdge = -(this.ColumnIndex << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToRightEdge = ((frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToTopEdge = -(this.RowIndex << Av1Constants.ModeInfoSizeLog2) << 3; + this.modeBlockToBottomEdge = ((frameInfo.ModeInfoRowCount - bh4 - this.RowIndex) << Av1Constants.ModeInfoSizeLog2) << 3; + } + + public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) + { + int maxBlockWide = blockSize.GetWidth(); + if (this.modeBlockToRightEdge < 0) + { + int shift = subX ? 4 : 3; + maxBlockWide += this.modeBlockToRightEdge >> shift; + } + + return maxBlockWide >> 2; + } + + public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY) + { + int maxBlockHigh = blockSize.GetHeight(); + if (this.modeBlockToBottomEdge < 0) + { + int shift = subY ? 4 : 3; + maxBlockHigh += this.modeBlockToBottomEdge >> shift; + } + + return maxBlockHigh >> 2; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 3d76994391..7d6eb4998f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Drawing; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -20,6 +22,7 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[] angleDelta = Av1DefaultDistributions.AngleDelta; private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; + private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); @@ -149,6 +152,30 @@ public Av1FilterIntraMode ReadFilterUltraMode() return (Av1FilterIntraMode)r.ReadSymbol(this.filterIntraMode); } + public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) + { + ref Av1SymbolReader r = ref this.reader; + Av1TransformSize maxTransformSize = blockSize.GetMaximumTransformSize(); + int depth = 0; + while (maxTransformSize != Av1TransformSize.Size4x4) + { + depth++; + maxTransformSize = maxTransformSize.GetSubSize(); + DebugGuard.MustBeLessThan(depth, 10, nameof(depth)); + } + + DebugGuard.MustBeLessThanOrEqualTo(depth, Av1Constants.MaxTransformCategories, nameof(depth)); + int category = depth - 1; + int value = r.ReadSymbol(this.transformSize[category][context]); + Av1TransformSize transformSize = blockSize.GetMaximumTransformSize(); + for (int d = 0; d < value; ++d) + { + transformSize = transformSize.GetSubSize(); + } + + return transformSize; + } + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index ea0af6ce9b..c286b34b6a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -2,10 +2,13 @@ // Licensed under the Six Labors Split License. using System.Data.Common; +using System.Drawing; +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using static SixLabors.ImageSharp.PixelFormats.Utils.Vector4Converters; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -31,6 +34,8 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private bool readDeltas; + private int[][] tusCount = []; + private int[] firstTransformOffset = new int[2]; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { @@ -55,6 +60,10 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo modeInfoWideColumnCount = AlignPowerOfTwo(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.ModeInfoSize); + this.tusCount = new int[Av1Constants.MaxPlanes][]; + this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; + this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; + this.tusCount[2] = new int[this.FrameBuffer.ModeInfoCount]; } public ObuFrameHeader FrameInfo { get; } @@ -71,7 +80,7 @@ public void DecodeTile(Span tileData, int tileNum) this.aboveNeighborContext.Clear(this.SequenceHeader); this.ClearLoopFilterDelta(); - int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; // Default initialization of Wiener and SGR Filter. this.referenceSgrXqd = new int[planesCount][]; @@ -102,9 +111,9 @@ public void DecodeTile(Span tileData, int tileNum) Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); - // this.ClearBlockDecodedFlags(row, column, superBlock4x4Size); - this.FrameBuffer.ClearCdef(superblockPosition); + // this.ClearBlockDecodedFlags(modeInfoLocation, superBlock4x4Size); Point modeInfoLocation = new(column, row); + this.FrameBuffer.ClearCdef(superblockPosition); this.ReadLoopRestoration(modeInfoLocation, superBlockSize); this.ParsePartition(ref reader, modeInfoLocation, superBlockSize, superblockInfo, tileInfo); } @@ -119,7 +128,7 @@ private void ClearLoopFilterDelta() /// /// 5.11.3. Clear block decoded flags function. /// - private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) + private void ClearBlockDecodedFlags(Point modeInfoLocation, int superBlock4x4Size) { int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; this.blockDecoded = new bool[planesCount][][]; @@ -127,12 +136,12 @@ private void ClearBlockDecodedFlags(int row, int column, int superBlock4x4Size) { int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; - int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - column) >> subX; - int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - row) >> subY; - this.blockDecoded[plane] = new bool[superBlock4x4Size >> subY][]; + int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - modeInfoLocation.X) >> subX; + int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - modeInfoLocation.Y) >> subY; + this.blockDecoded[plane] = new bool[(superBlock4x4Size >> subY) + 3][]; for (int y = -1; y <= superBlock4x4Size >> subY; y++) { - this.blockDecoded[plane][y] = new bool[superBlock4x4Size >> subX]; + this.blockDecoded[plane][y] = new bool[(superBlock4x4Size >> subX) + 3]; for (int x = -1; x <= superBlock4x4Size >> subX; x++) { if (y < 0 && x < superBlock4x4Width) @@ -194,7 +203,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, bool availableLeft = this.IsInside(rowIndex, columnIndex - 1); int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; - int quarterBlock4x4Size = halfBlock4x4Size >> 2; + int quarterBlock4x4Size = halfBlock4x4Size >> 1; bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; Av1PartitionType partitionType = Av1PartitionType.Split; @@ -234,53 +243,53 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, this.ParsePartition(ref reader, loc3, subSize, superblockInfo, tileInfo); break; case Av1PartitionType.None: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.None); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.None); break; case Av1PartitionType.Horizontal: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal); if (hasRows) { Point halfLocation = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Horizontal); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal); } break; case Av1PartitionType.Vertical: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.Vertical); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); if (hasRows) { Point halfLocation = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, Av1PartitionType.Vertical); + this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); } break; case Av1PartitionType.HorizontalA: - this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); Point locHorA1 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, locHorA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); Point locHorA2 = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, Av1PartitionType.HorizontalA); + this.ParseBlock(ref reader, locHorA2, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalA); break; case Av1PartitionType.HorizontalB: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); Point locHorB1 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); Point locHorB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, Av1PartitionType.HorizontalB); + this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); break; case Av1PartitionType.VerticalA: - this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, modeInfoLocation, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); Point locVertA1 = new(columnIndex, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, locVertA1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); Point locVertA2 = new(columnIndex + halfBlock4x4Size, rowIndex); - this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, Av1PartitionType.VerticalA); + this.ParseBlock(ref reader, locVertA2, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalA); break; case Av1PartitionType.VerticalB: - this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); Point locVertB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); - this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, Av1PartitionType.VerticalB); + this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); break; case Av1PartitionType.Horizontal4: for (int i = 0; i < 4; i++) @@ -292,7 +301,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } Point currentLocation = new(modeInfoLocation.X, currentBlockRow); - this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Horizontal4); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Horizontal4); } break; @@ -306,7 +315,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } Point currentLocation = new(currentBlockColumn, modeInfoLocation.Y); - this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, Av1PartitionType.Vertical4); + this.ParseBlock(ref reader, currentLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical4); } break; @@ -317,7 +326,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, this.UpdatePartitionContext(new Point(columnIndex, rowIndex), tileInfo, superblockInfo, subSize, blockSize, partitionType); } - private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1PartitionType partitionType) + private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1PartitionType partitionType) { int rowIndex = modeInfoLocation.Y; int columnIndex = modeInfoLocation.X; @@ -325,14 +334,12 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; Av1BlockModeInfo blockModeInfo = superblockInfo.GetModeInfo(modeInfoLocation); + blockModeInfo.PartitionType = partitionType; bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); partitionInfo.ColumnIndex = columnIndex; partitionInfo.RowIndex = rowIndex; - partitionInfo.AvailableUp = this.IsInside(rowIndex - 1, columnIndex); - partitionInfo.AvailableLeft = this.IsInside(rowIndex, columnIndex - 1); - partitionInfo.AvailableUpForChroma = partitionInfo.AvailableUp; - partitionInfo.AvailableLeftForChroma = partitionInfo.AvailableLeft; + partitionInfo.ComputeBoundaryOffsets(this.FrameInfo, tileInfo); if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) @@ -358,7 +365,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.ReadModeInfo(ref reader, partitionInfo); ReadPaletteTokens(ref reader, partitionInfo); - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); + this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo); if (partitionInfo.ModeInfo.Skip) { this.ResetSkipContext(partitionInfo); @@ -571,22 +578,171 @@ private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize block } /// - /// 5.11.16. Block TX size syntax. + /// 5.11.15. TX size syntax. /// - private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowIndex, int columnIndex, Av1BlockSize blockSize) + private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect) + { + Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; + if (this.FrameInfo.LosslessArray[modeInfo.SegmentId]) + { + return Av1TransformSize.Size4x4; + } + + if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameInfo.TransformMode == Transform.Av1TransformMode.Select) + { + return this.ReadSelectedTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo); + } + + return modeInfo.BlockSize.GetMaximumTransformSize(); + } + + private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { + int context = 0; + Av1TransformSize maxTransformSize = partitionInfo.ModeInfo.BlockSize.GetMaximumTransformSize(); + int aboveWidth = this.aboveNeighborContext.AboveTransformWidth[partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart]; + int above = (aboveWidth >= maxTransformSize.GetWidth()) ? 1 : 0; + int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.Position.Y]; + int left = (leftHeight >= maxTransformSize.GetHeight()) ? 1 : 0; + bool hasAbove = partitionInfo.AvailableUp; + bool hasLeft = partitionInfo.AvailableLeft; + + if (hasAbove && hasLeft) + { + context = above + left; + } + else if (hasAbove) + { + context = above; + } + else if (hasLeft) + { + context = left; + } + else + { + context = 0; + } + + return reader.ReadTransformSize(partitionInfo.ModeInfo.BlockSize, context); + } + + /// + /// Section 5.11.16. Block TX size syntax. + /// + private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) + { + Av1BlockSize blockSize = partitionInfo.ModeInfo.BlockSize; int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); // First condition in spec is for INTER frames, implemented only the INTRA condition. - ReadBlockTransformSize(ref reader, rowIndex, columnIndex, blockSize); - /*for (int row = rowIndex; row < rowIndex + block4x4Height; row++) + bool allowSelect = !partitionInfo.ModeInfo.Skip || true; + Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, allowSelect); + this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false); + this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false); + this.UpdateTransformInfo(partitionInfo, blockSize, transformSize); + } + + private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) + { + int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; + int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; + Av1TransformInfo lumaTransformInfo = this.FrameBuffer.GetTransformY(transformInfoYIndex); + Av1TransformInfo chromaTransformInfo = this.FrameBuffer.GetTransformUv(transformInfoUvIndex); + int totalLumaTusCount = 0; + int totalChromaTusCount = 0; + int forceSplitCount = 0; + bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + int maxBlockWide = partitionInfo.GetMaxBlockWide(blockSize, false); + int maxBlockHigh = partitionInfo.GetMaxBlockHigh(blockSize, false); + int width = 64 >> 2; + int height = 64 >> 2; + width = Math.Min(width, maxBlockWide); + height = Math.Min(height, maxBlockHigh); + + bool isLossLess = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; + Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY); + + for (int idy = 0; idy < maxBlockHigh; idy += height) { - for (int column = columnIndex; column < columnIndex + block4x4Width; column++) + for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++) { - this.InterTransformSizes[row][column] = this.TransformSize; + int lumaTusCount = 0; + int chromaTusCount = 0; + + // Update Luminance Transform Info. + int stepColumn = transformSize.Get4x4WideCount(); + int stepRow = transformSize.Get4x4HighCount(); + + int unitHeight = Av1Math.Round2(Math.Min(height + idy, maxBlockHigh), 0); + int unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), 0); + for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) + { + for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) + { + this.FrameBuffer.SetTransformY(transformInfoYIndex, new Av1TransformInfo + { + TransformSize = transformSize, + OffsetX = blockColumn, + OffsetY = blockRow + }); + transformInfoYIndex++; + lumaTusCount++; + totalLumaTusCount++; + } + } + + this.tusCount[(int)Av1Plane.Y][forceSplitCount] = lumaTusCount; + + if (this.SequenceHeader.ColorConfig.IsMonochrome || !partitionInfo.IsChroma) + { + continue; + } + + // Update Chroma Transform Info. + stepColumn = transformSizeUv.Get4x4WideCount(); + stepRow = transformSizeUv.Get4x4HighCount(); + + unitHeight = Av1Math.Round2(Math.Min(height + idx, maxBlockHigh), subY ? 1 : 0); + unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), subX ? 1 : 0); + for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) + { + for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) + { + this.FrameBuffer.SetTransformUv(transformInfoUvIndex, new Av1TransformInfo + { + TransformSize = transformSizeUv, + OffsetX = blockColumn, + OffsetY = blockRow + }); + transformInfoUvIndex++; + chromaTusCount++; + totalChromaTusCount++; + } + } + + this.tusCount[(int)Av1Plane.U][forceSplitCount] = lumaTusCount; + this.tusCount[(int)Av1Plane.V][forceSplitCount] = lumaTusCount; } - }*/ + } + + // Cr Transform Info Update from Cb. + if (totalChromaTusCount != 0) + { + int originalIndex = transformInfoUvIndex - totalChromaTusCount; + for (int i = 0; i < totalChromaTusCount; i++) + { + this.FrameBuffer.SetTransformUv(transformInfoUvIndex + i, this.FrameBuffer.GetTransformUv(originalIndex + i)); + } + } + + partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Y] = totalLumaTusCount; + partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; + + this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTusCount; + this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount; } /// @@ -594,16 +750,15 @@ private static void ReadBlockTransformSize(ref Av1SymbolDecoder reader, int rowI /// private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - reader.ReadLiteral(-1); if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) != 0) { - // Todo: Implement. + // TODO: Implement. throw new NotImplementedException(); } if (partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) != 0) { - // Todo: Implement. + // TODO: Implement. throw new NotImplementedException(); } } @@ -636,8 +791,7 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf this.ReadCdef(ref reader, partitionInfo); - bool readDeltas = false; - if (readDeltas) + if (this.readDeltas) { this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); this.ReadDeltaLoopFilter(ref reader, partitionInfo); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index 910309f873..ce91a4916f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs @@ -1,8 +1,26 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1TransformInfo { + public Av1TransformInfo() + { + } + + public Av1TransformInfo(Av1TransformInfo originalInfo) + { + this.TransformSize = originalInfo.TransformSize; + this.OffsetX = originalInfo.OffsetX; + this.OffsetY = originalInfo.OffsetY; + } + + public Av1TransformSize TransformSize { get; internal set; } + + public int OffsetX { get; internal set; } + + public int OffsetY { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs index 267c55266d..e812778335 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSize.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal enum Av1TransformSize : byte { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 8366a8e887..1fc36fd100 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -1,12 +1,40 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1TransformSizeExtensions { private static readonly int[] Size2d = [ - 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024]; + 16, 64, 256, 1024, 4096, 32, 32, 128, 128, 512, 512, 2048, 2048, 64, 64, 256, 256, 1024, 1024]; + + private static readonly Av1TransformSize[] SubTransformSize = [ + Av1TransformSize.Size4x4, // TX_4X4 + Av1TransformSize.Size4x4, // TX_8X8 + Av1TransformSize.Size8x8, // TX_16X16 + Av1TransformSize.Size16x16, // TX_32X32 + Av1TransformSize.Size32x32, // TX_64X64 + Av1TransformSize.Size4x4, // TX_4X8 + Av1TransformSize.Size4x4, // TX_8X4 + Av1TransformSize.Size8x8, // TX_8X16 + Av1TransformSize.Size8x8, // TX_16X8 + Av1TransformSize.Size16x16, // TX_16X32 + Av1TransformSize.Size16x16, // TX_32X16 + Av1TransformSize.Size32x32, // TX_32X64 + Av1TransformSize.Size32x32, // TX_64X32 + Av1TransformSize.Size4x8, // TX_4X16 + Av1TransformSize.Size8x4, // TX_16X4 + Av1TransformSize.Size8x16, // TX_8X32 + Av1TransformSize.Size16x8, // TX_32X8 + Av1TransformSize.Size16x32, // TX_16X64 + Av1TransformSize.Size32x16, // TX_64X16 + ]; + + // Transform block width in units. + private static readonly int[] WideUnit = [1, 2, 4, 8, 16, 1, 2, 2, 4, 4, 8, 8, 16, 1, 4, 2, 8, 4, 16]; + + // Transform block height in unit + private static readonly int[] HighUnit = [1, 2, 4, 8, 16, 2, 1, 4, 2, 8, 4, 16, 8, 4, 1, 8, 2, 16, 4]; public static int GetScale(this Av1TransformSize size) { @@ -21,4 +49,10 @@ public static int GetScale(this Av1TransformSize size) public static int GetWidthLog2(this Av1TransformSize size) => (int)size; public static int GetHeightLog2(this Av1TransformSize size) => (int)size; + + public static int Get4x4WideCount(this Av1TransformSize size) => WideUnit[(int)size]; + + public static int Get4x4HighCount(this Av1TransformSize size) => HighUnit[(int)size]; + + public static Av1TransformSize GetSubSize(this Av1TransformSize size) => SubTransformSize[(int)size]; } From 9fb58262e2fbc1a46788a7363a510c8de42be814 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 3 Jul 2024 18:44:08 +0200 Subject: [PATCH 095/190] Smoke test for tiling --- .../Formats/Heif/Av1/Av1TilingTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs new file mode 100644 index 0000000000..c0ce99c3ae --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1TilingTests +{ + // [Theory] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 0x03CC - 18)] + public void ReadFirstTile(string filename, int headerOffset, int headerSize, int tileOffset, int tileSize) + { + // Assign + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + byte[] content = File.ReadAllBytes(filePath); + Span headerSpan = content.AsSpan(headerOffset, headerSize); + Span tileSpan = content.AsSpan(tileOffset, tileSize); + Av1BitStreamReader reader = new(headerSpan); + IAv1TileDecoder stub = new Av1TileDecoderStub(); + ObuReader obuReader = new(); + obuReader.Read(ref reader, headerSize, stub); + Av1TileDecoder decoder = new(obuReader.SequenceHeader, obuReader.FrameHeader); + + // Act + decoder.DecodeTile(tileSpan, 0); + + // Assert + } +} From 13173a3906ec8f45d65963593ffdb50b708b8a4f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Jul 2024 19:29:04 +0200 Subject: [PATCH 096/190] Implement GetPlaneResidualSize() --- .../Formats/Heif/Av1/Av1BlockSize.cs | 2 +- .../Formats/Heif/Av1/Av1LookupTables.cs | 34 +++++++++++++++++++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 14 +++++++- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index 5a524c261d..92a651d60f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal enum Av1BlockSize +internal enum Av1BlockSize : byte { // See sction 6.10.4 of the Av1 Specification. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs b/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs new file mode 100644 index 0000000000..a7e9ab7998 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1LookupTables +{ + // The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function). + public static Av1BlockSize[,,] SubSampledSize = + { + { { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 }, { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 } }, + { { Av1BlockSize.Block4x8, Av1BlockSize.Block4x4 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x4 } }, + { { Av1BlockSize.Block8x4, Av1BlockSize.Invalid }, { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 } }, + { { Av1BlockSize.Block8x8, Av1BlockSize.Block8x4 }, { Av1BlockSize.Block4x8, Av1BlockSize.Block4x4 } }, + { { Av1BlockSize.Block8x16, Av1BlockSize.Block8x8 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x8 } }, + { { Av1BlockSize.Block16x8, Av1BlockSize.Invalid }, { Av1BlockSize.Block8x8, Av1BlockSize.Block8x4 } }, + { { Av1BlockSize.Block16x16, Av1BlockSize.Block16x8 }, { Av1BlockSize.Block8x16, Av1BlockSize.Block8x8 } }, + { { Av1BlockSize.Block16x32, Av1BlockSize.Block16x16 }, { Av1BlockSize.Invalid, Av1BlockSize.Block8x16 } }, + { { Av1BlockSize.Block32x16, Av1BlockSize.Invalid }, { Av1BlockSize.Block16x16, Av1BlockSize.Block16x8 } }, + { { Av1BlockSize.Block32x32, Av1BlockSize.Block32x16 }, { Av1BlockSize.Block16x32, Av1BlockSize.Block16x16 } }, + { { Av1BlockSize.Block32x64, Av1BlockSize.Block32x32 }, { Av1BlockSize.Invalid, Av1BlockSize.Block16x32 } }, + { { Av1BlockSize.Block64x32, Av1BlockSize.Invalid }, { Av1BlockSize.Block32x32, Av1BlockSize.Block32x16 } }, + { { Av1BlockSize.Block64x64, Av1BlockSize.Block64x32 }, { Av1BlockSize.Block32x64, Av1BlockSize.Block32x32 } }, + { { Av1BlockSize.Block64x128, Av1BlockSize.Block64x64 }, { Av1BlockSize.Invalid, Av1BlockSize.Block32x64 } }, + { { Av1BlockSize.Block128x64, Av1BlockSize.Invalid }, { Av1BlockSize.Block64x64, Av1BlockSize.Block64x32 } }, + { { Av1BlockSize.Block128x128, Av1BlockSize.Block128x64 }, { Av1BlockSize.Block64x128, Av1BlockSize.Block64x64 } }, + { { Av1BlockSize.Block4x16, Av1BlockSize.Block4x8 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x8 } }, + { { Av1BlockSize.Block16x4, Av1BlockSize.Invalid }, { Av1BlockSize.Block8x4, Av1BlockSize.Block8x4 } }, + { { Av1BlockSize.Block8x32, Av1BlockSize.Block8x16 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x16 } }, + { { Av1BlockSize.Block32x8, Av1BlockSize.Invalid }, { Av1BlockSize.Block16x8, Av1BlockSize.Block16x4 } }, + { { Av1BlockSize.Block16x64, Av1BlockSize.Block16x32 }, { Av1BlockSize.Invalid, Av1BlockSize.Block8x32 } }, + { { Av1BlockSize.Block64x16, Av1BlockSize.Invalid }, { Av1BlockSize.Block32x16, Av1BlockSize.Block32x8 } }, + }; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index c286b34b6a..4308ff3326 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -451,7 +451,19 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); - private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) => throw new NotImplementedException(); + /// + /// 5.11.38. Get plane residual size function. + /// The GetPlaneResidualSize returns the size of a residual block for the specified plane. (The residual block will always + /// have width and height at least equal to 4.) + /// + private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) + { + int subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; + int subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; + int subX = plane > 0 ? subsamplingX : 0; + int subY = plane > 0 ? subsamplingY : 0; + return Av1LookupTables.SubSampledSize[(byte)sizeChunk, subX, subY]; + } /// /// 5.11.35. Transform block syntax. From 3d84a51970afd8ac2d8aa8c618487bff96619e52 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 4 Jul 2024 19:35:08 +0200 Subject: [PATCH 097/190] Suppress warning about SA1500: Braces for multi-line statements --- src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs b/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs index a7e9ab7998..2b9c911041 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs @@ -6,7 +6,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1LookupTables { // The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function). - public static Av1BlockSize[,,] SubSampledSize = + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line", Justification = "alignment should match the spec file definition")] + public static readonly Av1BlockSize[,,] SubSampledSize = { { { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 }, { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 } }, { { Av1BlockSize.Block4x8, Av1BlockSize.Block4x4 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x4 } }, From 6c4e213a75df607cecec9155aa7b5303b1090380 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 4 Jul 2024 20:53:43 +0200 Subject: [PATCH 098/190] Implement Residual --- .../Heif/Av1/Av1BlockSizeExtensions.cs | 1 + .../Formats/Heif/Av1/Av1LookupTables.cs | 35 ---- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 2 + .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 9 + .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 6 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 164 ++++++++++++------ .../Heif/Av1/Tiling/Av1TransformInfo.cs | 6 + 7 files changed, 132 insertions(+), 91 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 34fe80da3b..29003b3410 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -11,6 +11,7 @@ internal static class Av1BlockSizeExtensions private static readonly int[] SizeWide = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]; private static readonly int[] SizeHigh = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]; + // The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function). private static readonly Av1BlockSize[][][] SubSampled = [ diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs b/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs deleted file mode 100644 index 2b9c911041..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1LookupTables.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1; - -internal static class Av1LookupTables -{ - // The Subsampled_Size table in the spec (Section 5.11.38. Get plane residual size function). - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line", Justification = "alignment should match the spec file definition")] - public static readonly Av1BlockSize[,,] SubSampledSize = - { - { { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 }, { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 } }, - { { Av1BlockSize.Block4x8, Av1BlockSize.Block4x4 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x4 } }, - { { Av1BlockSize.Block8x4, Av1BlockSize.Invalid }, { Av1BlockSize.Block4x4, Av1BlockSize.Block4x4 } }, - { { Av1BlockSize.Block8x8, Av1BlockSize.Block8x4 }, { Av1BlockSize.Block4x8, Av1BlockSize.Block4x4 } }, - { { Av1BlockSize.Block8x16, Av1BlockSize.Block8x8 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x8 } }, - { { Av1BlockSize.Block16x8, Av1BlockSize.Invalid }, { Av1BlockSize.Block8x8, Av1BlockSize.Block8x4 } }, - { { Av1BlockSize.Block16x16, Av1BlockSize.Block16x8 }, { Av1BlockSize.Block8x16, Av1BlockSize.Block8x8 } }, - { { Av1BlockSize.Block16x32, Av1BlockSize.Block16x16 }, { Av1BlockSize.Invalid, Av1BlockSize.Block8x16 } }, - { { Av1BlockSize.Block32x16, Av1BlockSize.Invalid }, { Av1BlockSize.Block16x16, Av1BlockSize.Block16x8 } }, - { { Av1BlockSize.Block32x32, Av1BlockSize.Block32x16 }, { Av1BlockSize.Block16x32, Av1BlockSize.Block16x16 } }, - { { Av1BlockSize.Block32x64, Av1BlockSize.Block32x32 }, { Av1BlockSize.Invalid, Av1BlockSize.Block16x32 } }, - { { Av1BlockSize.Block64x32, Av1BlockSize.Invalid }, { Av1BlockSize.Block32x32, Av1BlockSize.Block32x16 } }, - { { Av1BlockSize.Block64x64, Av1BlockSize.Block64x32 }, { Av1BlockSize.Block32x64, Av1BlockSize.Block32x32 } }, - { { Av1BlockSize.Block64x128, Av1BlockSize.Block64x64 }, { Av1BlockSize.Invalid, Av1BlockSize.Block32x64 } }, - { { Av1BlockSize.Block128x64, Av1BlockSize.Invalid }, { Av1BlockSize.Block64x64, Av1BlockSize.Block64x32 } }, - { { Av1BlockSize.Block128x128, Av1BlockSize.Block128x64 }, { Av1BlockSize.Block64x128, Av1BlockSize.Block64x64 } }, - { { Av1BlockSize.Block4x16, Av1BlockSize.Block4x8 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x8 } }, - { { Av1BlockSize.Block16x4, Av1BlockSize.Invalid }, { Av1BlockSize.Block8x4, Av1BlockSize.Block8x4 } }, - { { Av1BlockSize.Block8x32, Av1BlockSize.Block8x16 }, { Av1BlockSize.Invalid, Av1BlockSize.Block4x16 } }, - { { Av1BlockSize.Block32x8, Av1BlockSize.Invalid }, { Av1BlockSize.Block16x8, Av1BlockSize.Block16x4 } }, - { { Av1BlockSize.Block16x64, Av1BlockSize.Block16x32 }, { Av1BlockSize.Invalid, Av1BlockSize.Block8x32 } }, - { { Av1BlockSize.Block64x16, Av1BlockSize.Invalid }, { Av1BlockSize.Block32x16, Av1BlockSize.Block32x8 } }, - }; -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index ce5dd66269..2848dac8ad 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -137,6 +137,8 @@ internal static int AlignPowerOf2(int value, int n) return (value + mask) & ~mask; } + internal static int RoundPowerOf2(int value, int n) => (value + ((1 << n) >> 1)) >> n; + internal static int Clamp(int value, int low, int high) => value < low ? low : (value > high ? high : value); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 03e7100606..a0748a2e7f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Reflection.Metadata.Ecma335; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -201,4 +202,12 @@ public Span GetDeltaLoopFilter(Point index) } public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); + + public Av1TransformInfo? GetTransform(int plane, int transformInfoIndex) => + plane switch + { + 0 => this.GetTransformY(transformInfoIndex), + 1 or 2 => this.GetTransformUv(transformInfoIndex), + _ => null, + }; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 0ee2d80497..5b6b9c79df 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -25,11 +25,13 @@ public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) public Span CoefficientsV => this.frameBuffer.GetCoefficientsV(this.Position); - public Av1TransformInfo SuperblockTransformInfo => this.frameBuffer.GetTransformY(this.Position); - public Span CdefStrength => this.frameBuffer.GetCdefStrength(this.Position); public Span SuperblockDeltaLoopFilter => this.frameBuffer.GetDeltaLoopFilter(this.Position); + public int TransformInfoIndexY { get; internal set; } + + public int TransformInfoIndexUv { get; internal set; } + public Av1BlockModeInfo GetModeInfo(Point index) => this.frameBuffer.GetModeInfo(this.Position, index); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 4308ff3326..172b69e880 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Data.Common; -using System.Drawing; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -using static SixLabors.ImageSharp.PixelFormats.Utils.Vector4Converters; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; @@ -34,8 +29,10 @@ internal class Av1TileDecoder : IAv1TileDecoder private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private bool readDeltas; - private int[][] tusCount = []; + private int[][] tusCount; private int[] firstTransformOffset = new int[2]; + private int[][] coefficients = []; + private int[] coefficientIndex = []; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { @@ -64,6 +61,8 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; this.tusCount[2] = new int[this.FrameBuffer.ModeInfoCount]; + this.coefficients = new int[3][]; + this.coefficientIndex = new int[3]; } public ObuFrameHeader FrameInfo { get; } @@ -371,7 +370,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.ResetSkipContext(partitionInfo); } - this.Residual(rowIndex, columnIndex, blockSize); + this.Residual(partitionInfo, superblockInfo, tileInfo, blockSize); } private void ResetSkipContext(Av1PartitionInfo partitionInfo) @@ -394,46 +393,97 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) /// /// 5.11.34. Residual syntax. /// - private void Residual(int rowIndex, int columnIndex, Av1BlockSize blockSize) + private void Residual(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize) { - bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; - int widthChunks = Math.Max(1, blockSize.Get4x4WideCount() >> 6); - int heightChunks = Math.Max(1, blockSize.Get4x4HighCount() >> 6); - Av1BlockSize sizeChunk = widthChunks > 1 || heightChunks > 1 ? Av1BlockSize.Block64x64 : blockSize; + int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false); + int maxBlocksHigh = partitionInfo.GetMaxBlockHigh(blockSize, false); + Av1BlockSize maxUnitSize = Av1BlockSize.Block64x64; + int modeUnitBlocksWide = maxUnitSize.GetWidth() >> 2; + int modeUnitBlocksHigh = maxUnitSize.GetHeight() >> 2; + modeUnitBlocksWide = Math.Min(maxBlocksWide, modeUnitBlocksWide); + modeUnitBlocksHigh = Math.Min(maxBlocksHigh, modeUnitBlocksHigh); + int planeCount = this.SequenceHeader.ColorConfig.ChannelCount; + bool isLossless = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; + bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); + int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); + int chromaTusCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv]; + int[] transformInfoIndices = new int[3]; + transformInfoIndices[0] = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; + transformInfoIndices[1] = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; + transformInfoIndices[2] = transformInfoIndices[1] + chromaTusCount; + int forceSplitCount = 0; - for (int chunkY = 0; chunkY < heightChunks; chunkY++) + for (int row = 0; row < maxBlocksHigh; row += modeUnitBlocksHigh) { - for (int chunkX = 0; chunkX < widthChunks; chunkX++) + for (int column = 0; column < maxBlocksWide; column += modeUnitBlocksWide) { - int rowChunk = rowIndex + (chunkY << 4); - int columnChunk = columnIndex + (chunkX << 4); - int subBlockRow = rowChunk & superBlockMask; - int subBlockColumn = columnChunk & superBlockMask; - int endPlane = 1 + (this.HasChroma(rowIndex, columnIndex, blockSize) ? 2 : 0); - for (int plane = 0; plane < endPlane; plane++) + for (int plane = 0; plane < planeCount; ++plane) { - Av1TransformSize transformSize = this.FrameInfo.CodedLossless ? Av1TransformSize.Size4x4 : this.GetSize(plane, -1); - int stepX = transformSize.GetWidth() >> 2; - int stepY = transformSize.GetHeight() >> 2; - Av1BlockSize planeSize = this.GetPlaneResidualSize(sizeChunk, plane); - int num4x4Width = planeSize.Get4x4WideCount(); - int num4x4Height = planeSize.Get4x4HighCount(); - int subX = plane > 0 && subsamplingX ? 1 : 0; - int subY = plane > 0 && subsamplingY ? 1 : 0; - int baseX = (columnChunk >> subX) * (1 << Av1Constants.ModeInfoSizeLog2); - int baseY = (rowChunk >> subY) * (1 << Av1Constants.ModeInfoSizeLog2); - int baseXBlock = (columnIndex >> subX) * (1 << Av1Constants.ModeInfoSizeLog2); - int baseYBlock = (rowIndex >> subY) * (1 << Av1Constants.ModeInfoSizeLog2); - for (int y = 0; y < num4x4Height; y += stepY) + int totalTusCount; + int tusCount; + int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; + int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; + + if (plane != 0 && !partitionInfo.IsChroma) + { + continue; + } + + if (isLosslessBlock) + { + // TODO: Implement. + int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0); + int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0); + totalTusCount = 0; + tusCount = ((unitWidth - column) * (unitHeight - row)) >> (subX + subY); + } + else + { + totalTusCount = partitionInfo.ModeInfo.TusCount[Math.Min(1, plane)]; + tusCount = this.tusCount[plane][forceSplitCount]; + + DebugGuard.IsFalse(totalTusCount == 0, nameof(totalTusCount), string.Empty); + DebugGuard.IsTrue(totalTusCount == this.tusCount[plane][0] + this.tusCount[plane][1] + this.tusCount[plane][2] + this.tusCount[plane][3], nameof(totalTusCount), string.Empty); + } + + DebugGuard.IsFalse(tusCount == 0, nameof(tusCount), string.Empty); + + for (int tu = 0; tu < tusCount; tu++) { - for (int x = 0; x < num4x4Width; x += stepX) + int coefficientIndex = this.coefficientIndex[plane]; + int endOfBlock = 0; + Av1TransformInfo transformInfo = this.FrameBuffer.GetTransform(plane, transformInfoIndices[plane])!; + int blockColumn = transformInfo.TransformOffsetX; + int blockRow = transformInfo.TransformOffsetY; + int startX = (partitionInfo.ColumnIndex >> subX) + blockColumn; + int startY = (partitionInfo.RowIndex >> subY) + blockRow; + + if (startX >= (this.FrameInfo.ModeInfoColumnCount >> subX) || + startY >= (this.FrameInfo.ModeInfoRowCount >> subY)) { - this.TransformBlock(plane, baseXBlock, baseYBlock, transformSize, x + (chunkX << 4 >> subX), y + (chunkY << 4 >> subY)); + return; } + + if (!partitionInfo.ModeInfo.Skip) + { + endOfBlock = this.TransformBlock(partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.TransformSize, subX, subY); + } + + if (endOfBlock != 0) + { + this.coefficientIndex[plane] += 2; + transformInfo.CodeBlockFlag = true; + } + else + { + transformInfo.CodeBlockFlag = false; + } + + transformInfoIndices[plane]++; } } + + forceSplitCount++; } } } @@ -458,25 +508,29 @@ private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) /// private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) { - int subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; - int subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; - int subX = plane > 0 ? subsamplingX : 0; - int subY = plane > 0 ? subsamplingY : 0; - return Av1LookupTables.SubSampledSize[(byte)sizeChunk, subX, subY]; + bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; + bool subX = plane > 0 && subsamplingX; + bool subY = plane > 0 && subsamplingY; + return sizeChunk.GetSubsampled(subX, subY); } /// /// 5.11.35. Transform block syntax. /// - private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize transformSize, int x, int y) + private int TransformBlock( + Av1PartitionInfo partitionInfo, + int coefficientIndex, + Av1TransformInfo transformInfo, + int plane, + int blockColumn, + int blockRow, + int startX, + int startY, + Av1TransformSize transformSize, + int subX, + int subY) { - Av1PartitionInfo partitionInfo = new(new(1, Av1BlockSize.Invalid, new Point(0, 0)), new(this.FrameBuffer, default), false, Av1PartitionType.None); - int startX = (baseX + 4) * x; - int startY = (baseY + 4) * y; - bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - int subX = plane > 0 && subsamplingX ? 1 : 0; - int subY = plane > 0 && subsamplingY ? 1 : 0; int columnIndex = startX << subX >> Av1Constants.ModeInfoSizeLog2; int rowIndex = startY << subY >> Av1Constants.ModeInfoSizeLog2; int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; @@ -488,13 +542,13 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr int maxY = (this.SequenceHeader.ModeInfoSize * (1 << Av1Constants.ModeInfoSizeLog2)) >> subY; if (startX >= maxX || startY >= maxY) { - return; + return 0; } if ((plane == 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) > 0) || (plane != 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) > 0)) { - this.PredictPalette(plane, startX, startY, x, y, transformSize); + this.PredictPalette(plane, startX, startY, blockColumn, blockRow, transformSize); } else { @@ -511,8 +565,8 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr int log2Width = transformSize.GetWidthLog2(); int log2Height = transformSize.GetHeightLog2(); - bool leftAvailable = x > 0 || plane == 0 ? partitionInfo.AvailableLeft : partitionInfo.AvailableLeftForChroma; - bool upAvailable = y > 0 || plane == 0 ? partitionInfo.AvailableUp : partitionInfo.AvailableUpForChroma; + bool leftAvailable = blockColumn > 0 || plane == 0 ? partitionInfo.AvailableLeft : partitionInfo.AvailableLeftForChroma; + bool upAvailable = blockRow > 0 || plane == 0 ? partitionInfo.AvailableUp : partitionInfo.AvailableUpForChroma; bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); @@ -545,6 +599,8 @@ private void TransformBlock(int plane, int baseX, int baseY, Av1TransformSize tr this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true; } } + + return -1; } private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index ce91a4916f..252c969a30 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs @@ -23,4 +23,10 @@ public Av1TransformInfo(Av1TransformInfo originalInfo) public int OffsetX { get; internal set; } public int OffsetY { get; internal set; } + + public bool CodeBlockFlag { get; internal set; } + + public int TransformOffsetX { get; internal set; } + + public int TransformOffsetY { get; internal set; } } From 1ff01503ac026f2107bf3ff0f4ca2e7f0a9b6fad Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 4 Jul 2024 22:40:50 +0200 Subject: [PATCH 099/190] Implement TransformBlock --- .../Heif/Av1/Av1BlockSizeExtensions.cs | 14 +- .../Formats/Heif/Av1/Av1Constants.cs | 2 + .../Formats/Heif/Av1/Av1MainParseContext.cs | 8 - .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 1 - .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 9 + .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 2 + .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 2 + .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 4 + .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 272 ++++++++++++++---- .../Av1/Tiling/Av1TransformBlockContext.cs | 11 + .../Transform/Av1TransformSizeExtensions.cs | 25 ++ 11 files changed, 280 insertions(+), 70 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 29003b3410..6f1490975e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -50,6 +50,9 @@ internal static class Av1BlockSizeExtensions Av1TransformSize.Size16x64, Av1TransformSize.Size64x16 ]; + private static readonly int[] PelsLog2Count = + [4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13, 13, 14, 6, 6, 8, 8, 10, 10]; + public static int Get4x4WideCount(this Av1BlockSize blockSize) => SizeWide[(int)blockSize]; public static int Get4x4HighCount(this Av1BlockSize blockSize) => SizeHigh[(int)blockSize]; @@ -82,13 +85,19 @@ public static int Get4x4HeightLog2(this Av1BlockSize blockSize) /// Returns the block size of a sub sampled block. ///
public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, bool subX, bool subY) + => GetSubsampled(blockSize, subX ? 1 : 0, subY ? 1 : 0); + + /// + /// Returns the block size of a sub sampled block. + /// + public static Av1BlockSize GetSubsampled(this Av1BlockSize blockSize, int subX, int subY) { if (blockSize == Av1BlockSize.Invalid) { return Av1BlockSize.Invalid; } - return SubSampled[(int)blockSize][subX ? 1 : 0][subY ? 1 : 0]; + return SubSampled[(int)blockSize][subX][subY]; } public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize, bool subX, bool subY) @@ -115,4 +124,7 @@ public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize ///
public static Av1TransformSize GetMaximumTransformSize(this Av1BlockSize blockSize) => MaxTransformSize[(int)blockSize]; + + public static int GetPelsLog2Count(this Av1BlockSize blockSize) + => PelsLog2Count[(int)blockSize]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 6fd6c536ac..05656a6d18 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -138,4 +138,6 @@ internal static class Av1Constants /// Maximum transform size categories. ///
public const int MaxTransformCategories = 4; + + public const int CoefficientContextCount = 6; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs b/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs deleted file mode 100644 index 4b13a86cd7..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1MainParseContext.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1; - -internal class Av1MainParseContext -{ -} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 9c6a50fb96..db0131604a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1095,7 +1095,6 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade ReadFrameDeltaLoopFilterParameters(ref reader, frameInfo); // SetupSegmentationDequantization(); - Av1MainParseContext mainParseContext = new(); if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index a0748a2e7f..8ca3d67fe3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -150,6 +150,15 @@ public void SetTransformUv(int index, Av1TransformInfo transformInfo) span[index] = transformInfo; } + public Span GetCoefficients(int plane) => + plane switch + { + 0 => (Span)this.coefficientsY, + 2 => (Span)this.coefficientsY, + 3 => (Span)this.coefficientsY, + _ => null, + }; + public Span GetCoefficientsY(Point index) { Span span = this.coefficientsY; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 2efbde6e4c..1810bf6fc4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -46,6 +46,8 @@ public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) public int[] AboveTransformWidth => this.aboveTransformWidth; + public int[] GetContext(int plane) => this.aboveContext[plane]; + public void Clear(ObuSequenceHeader sequenceHeader) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 18ea5b5f7f..8743ee329e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -91,4 +91,6 @@ public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo super internal void ClearContext(int plane, int offset, int length) => Array.Fill(this.leftContext[plane], 0, offset, length); + + internal int[] GetContext(int plane) => this.leftContext[plane]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index cfacff6d10..9ae390d117 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -50,6 +50,10 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI public int[] ReferenceFrame { get; set; } + public int ModeBlockToRightEdge => this.modeBlockToRightEdge; + + public int ModeBlockToBottomEdge => this.modeBlockToBottomEdge; + public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInfo) { Av1BlockSize blockSize = this.ModeInfo.BlockSize; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 172b69e880..a329a4ba6a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -13,6 +13,13 @@ internal class Av1TileDecoder : IAv1TileDecoder private static readonly int[] SgrprojXqdMid = [-32, 31]; private static readonly int[] WienerTapsMid = [3, -7, 15]; private const int PartitionProbabilitySet = 4; + private static readonly int[] Signs = [0, -1, 1]; + private static readonly int[] DcSignContexts = [ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]; + + private static readonly int[][] SkipContexts = [ + [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]]; private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; @@ -25,13 +32,10 @@ internal class Av1TileDecoder : IAv1TileDecoder private int[][] leftLevelContext = []; private int[][] leftDcContext = []; private int[][] segmentIds = []; - private int maxLumaWidth; - private int maxLumaHeight; private int deltaLoopFilterResolution = -1; private bool readDeltas; private int[][] tusCount; private int[] firstTransformOffset = new int[2]; - private int[][] coefficients = []; private int[] coefficientIndex = []; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) @@ -61,8 +65,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; this.tusCount[2] = new int[this.FrameBuffer.ModeInfoCount]; - this.coefficients = new int[3][]; - this.coefficientIndex = new int[3]; + this.coefficientIndex = new int[Av1Constants.MaxPlanes]; } public ObuFrameHeader FrameInfo { get; } @@ -370,7 +373,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.ResetSkipContext(partitionInfo); } - this.Residual(partitionInfo, superblockInfo, tileInfo, blockSize); + this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize); } private void ResetSkipContext(Av1PartitionInfo partitionInfo) @@ -393,7 +396,7 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) /// /// 5.11.34. Residual syntax. /// - private void Residual(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize) + private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, Av1BlockSize blockSize) { int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false); int maxBlocksHigh = partitionInfo.GetMaxBlockHigh(blockSize, false); @@ -466,7 +469,7 @@ private void Residual(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblo if (!partitionInfo.ModeInfo.Skip) { - endOfBlock = this.TransformBlock(partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.TransformSize, subX, subY); + endOfBlock = this.TransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.TransformSize, subX != 0, subY != 0); } if (endOfBlock != 0) @@ -519,6 +522,7 @@ private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) /// 5.11.35. Transform block syntax. ///
private int TransformBlock( + ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int coefficientIndex, Av1TransformInfo transformInfo, @@ -528,79 +532,227 @@ private int TransformBlock( int startX, int startY, Av1TransformSize transformSize, - int subX, - int subY) + bool subX, + bool subY) { - int columnIndex = startX << subX >> Av1Constants.ModeInfoSizeLog2; - int rowIndex = startY << subY >> Av1Constants.ModeInfoSizeLog2; - int superBlockMask = this.SequenceHeader.Use128x128SuperBlock ? 31 : 15; - int subBlockColumn = columnIndex & superBlockMask; - int subBlockRow = rowIndex & superBlockMask; - int stepX = transformSize.GetWidth() >> Av1Constants.ModeInfoSizeLog2; - int stepY = transformSize.GetHeight() >> Av1Constants.ModeInfoSizeLog2; - int maxX = (this.SequenceHeader.ModeInfoSize * (1 << Av1Constants.ModeInfoSizeLog2)) >> subX; - int maxY = (this.SequenceHeader.ModeInfoSize * (1 << Av1Constants.ModeInfoSizeLog2)) >> subY; - if (startX >= maxX || startY >= maxY) + int endOfBlock = 0; + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int transformBlockUnitWideCount = transformSize.Get4x4WideCount(); + int transformBlockUnitHighCount = transformSize.Get4x4HighCount(); + + if (partitionInfo.ModeBlockToRightEdge < 0) { - return 0; + int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); + transformBlockUnitWideCount = Math.Min(transformBlockUnitWideCount, blocksWide - blockColumn); } - if ((plane == 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Y) > 0) || - (plane != 0 && partitionInfo.ModeInfo.GetPaletteSize(Av1PlaneType.Uv) > 0)) + if (partitionInfo.ModeBlockToBottomEdge < 0) { - this.PredictPalette(plane, startX, startY, blockColumn, blockRow, transformSize); + int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); + transformBlockUnitHighCount = Math.Min(transformBlockUnitHighCount, blocksHigh - blockRow); } - else + + Av1TransformBlockContext transformBlockContext = this.GetTransformBlockContext(transformSize, plane, planeBlockSize, transformBlockUnitHighCount, transformBlockUnitWideCount, startY, startX); + endOfBlock = this.ParseCoefficients(ref reader, partitionInfo, startY, startX, blockRow, blockColumn, plane, transformBlockContext, transformSize, coefficientIndex, transformInfo); + + return endOfBlock; + } + + /// + /// 5.11.39. Coefficients syntax. + /// + private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int startY, int startX, int blockRow, int blockColumn, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, int coefficientIndex, Av1TransformInfo transformInfo) => throw new NotImplementedException(); + + private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX) + { + Av1TransformBlockContext transformBlockContext = new(); + int[] aboveContext = this.aboveNeighborContext.GetContext(plane); + int[] leftContext = this.leftNeighborContext.GetContext(plane); + int dcSign = 0; + int k = 0; + int mask = (1 << Av1Constants.CoefficientContextCount) - 1; + + do { - bool isChromaFromLuma = plane > 0 && partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma; - Av1PredictionMode mode; - if (plane == 0) + int sign = aboveContext[k] >> Av1Constants.CoefficientContextCount; + DebugGuard.MustBeLessThanOrEqualTo(sign, 2, nameof(sign)); + dcSign += Signs[sign]; + } + while (++k < transformBlockUnitHighCount); + transformBlockContext.DcSignContext = dcSign; + + if (plane == 0) + { + if (planeBlockSize == transformSize.GetBlockSize()) { - mode = partitionInfo.ModeInfo.YMode; + transformBlockContext.SkipContext = 0; } else { - mode = isChromaFromLuma ? Av1PredictionMode.DC : partitionInfo.ModeInfo.UvMode; - } + int top = 0; + int left = 0; - int log2Width = transformSize.GetWidthLog2(); - int log2Height = transformSize.GetHeightLog2(); - bool leftAvailable = blockColumn > 0 || plane == 0 ? partitionInfo.AvailableLeft : partitionInfo.AvailableLeftForChroma; - bool upAvailable = blockRow > 0 || plane == 0 ? partitionInfo.AvailableUp : partitionInfo.AvailableUpForChroma; - bool haveAboveRight = this.blockDecoded[plane][(subBlockRow >> subY) - 1][(subBlockColumn >> subX) + stepX]; - bool haveBelowLeft = this.blockDecoded[plane][(subBlockRow >> subY) + stepY][(subBlockColumn >> subX) - 1]; - this.PredictIntra(plane, startX, startY, leftAvailable, upAvailable, haveAboveRight, haveBelowLeft, mode, log2Width, log2Height); - if (isChromaFromLuma) - { - this.PredictChromaFromLuma(plane, startX, startY, transformSize); + k = 0; + do + { + top |= aboveContext[k]; + } + while (++k < transformBlockUnitWideCount); + top &= mask; + + k = 0; + do + { + left |= leftContext[k]; + } + while (++k < transformBlockUnitHighCount); + left &= mask; + + int max = Math.Min(top | left, 4); + int min = Math.Min(Math.Min(top, left), 4); + + transformBlockContext.SkipContext = SkipContexts[min][max]; } } - - if (plane == 0) + else { - this.maxLumaWidth = startX + (stepX * 4); - this.maxLumaHeight = startY + (stepY * 4); + int contextBase = GetEntropyContext(transformSize, aboveContext, leftContext); + int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.GetBlockSize().GetPelsLog2Count() ? 10 : 7; + transformBlockContext.SkipContext = contextBase + contextOffset; } - if (!partitionInfo.ModeInfo.Skip) - { - int eob = this.Coefficients(plane, startX, startY, transformSize); - if (eob > 0) - { - this.Reconstruct(plane, startX, startY, transformSize); - } - } + return transformBlockContext; + } - for (int i = 0; i < stepY; i++) + private static int GetEntropyContext(Av1TransformSize transformSize, int[] above, int[] left) + { + bool aboveEntropyContext = false; + bool leftEntropyContext = false; + + switch (transformSize) { - for (int j = 0; j < stepX; j++) - { - // Ignore loop filter. - this.blockDecoded[plane][(subBlockRow >> subY) + i][(subBlockColumn >> subX) + j] = true; - } + case Av1TransformSize.Size4x4: + aboveEntropyContext = above[0] != 0; + leftEntropyContext = left[0] != 0; + break; + case Av1TransformSize.Size4x8: + aboveEntropyContext = above[0] != 0; + leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left; + break; + case Av1TransformSize.Size8x4: + aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above; + leftEntropyContext = left[0] != 0; + break; + case Av1TransformSize.Size8x16: + aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left; + break; + case Av1TransformSize.Size16x8: + aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left; + break; + case Av1TransformSize.Size16x32: + aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above; + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left; + break; + case Av1TransformSize.Size32x16: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left; + break; + case Av1TransformSize.Size8x8: + aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left; + break; + case Av1TransformSize.Size16x16: + aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left; + break; + case Av1TransformSize.Size32x32: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above; + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left; + break; + case Av1TransformSize.Size64x64: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 || + (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 || + (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8)); + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 || + (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 || + (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8)); + break; + case Av1TransformSize.Size32x64: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above; + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 || + (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 || + (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8)); + break; + case Av1TransformSize.Size64x32: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 || + (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 || + (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8)); + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left; + break; + case Av1TransformSize.Size4x16: + aboveEntropyContext = above[0] != 0; + leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left; + break; + case Av1TransformSize.Size16x4: + aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above; + leftEntropyContext = left[0] != 0; + break; + case Av1TransformSize.Size8x32: + aboveEntropyContext = (above[0] & (above[1] << 8)) != 0; // !!*(const uint16_t*)above; + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0; // !!*(const uint64_t*)left; + break; + case Av1TransformSize.Size32x8: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0; // !!*(const uint64_t*)above; + leftEntropyContext = (left[0] & (left[1] << 8)) != 0; // !!*(const uint16_t*)left; + break; + case Av1TransformSize.Size16x64: + aboveEntropyContext = (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0; // !!*(const uint32_t*)above; + leftEntropyContext = + (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0 || + (left[4] & (left[5] << 8) & (left[6] << 16) & (left[7] << 24)) != 0 || + (left[8] & (left[9] << 8) & (left[10] << 16) & (left[11] << 24)) != 0 || + (left[12] & (left[13] << 8) & (left[14] << 16) & (left[15] << 24)) != 0; // !!(*(const uint64_t*)left | *(const uint64_t*)(left + 8)); + break; + case Av1TransformSize.Size64x16: + aboveEntropyContext = + (above[0] & (above[1] << 8) & (above[2] << 16) & (above[3] << 24)) != 0 || + (above[4] & (above[5] << 8) & (above[6] << 16) & (above[7] << 24)) != 0 || + (above[8] & (above[9] << 8) & (above[10] << 16) & (above[11] << 24)) != 0 || + (above[12] & (above[13] << 8) & (above[14] << 16) & (above[15] << 24)) != 0; // !!(*(const uint64_t*)above | *(const uint64_t*)(above + 8)); + leftEntropyContext = (left[0] & (left[1] << 8) & (left[2] << 16) & (left[3] << 24)) != 0; // !!*(const uint32_t*)left; + break; + default: + Guard.IsTrue(false, nameof(transformSize), "Invalid transform size."); + break; } - return -1; + return (aboveEntropyContext ? 1 : 0) + (leftEntropyContext ? 1 : 0); } private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs new file mode 100644 index 0000000000..4e761b545a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal class Av1TransformBlockContext +{ + public int DcSignContext { get; set; } + + public int SkipContext { get; set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 1fc36fd100..a961be945c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -36,6 +36,29 @@ internal static class Av1TransformSizeExtensions // Transform block height in unit private static readonly int[] HighUnit = [1, 2, 4, 8, 16, 2, 1, 4, 2, 8, 4, 16, 8, 4, 1, 8, 2, 16, 4]; + // Transform size conversion into Block Size + private static readonly Av1BlockSize[] BlockSize = [ + Av1BlockSize.Block4x4, // TX_4X4 + Av1BlockSize.Block8x8, // TX_8X8 + Av1BlockSize.Block16x16, // TX_16X16 + Av1BlockSize.Block32x32, // TX_32X32 + Av1BlockSize.Block64x64, // TX_64X64 + Av1BlockSize.Block4x8, // TX_4X8 + Av1BlockSize.Block8x4, // TX_8X4 + Av1BlockSize.Block8x16, // TX_8X16 + Av1BlockSize.Block16x8, // TX_16X8 + Av1BlockSize.Block16x32, // TX_16X32 + Av1BlockSize.Block32x16, // TX_32X16 + Av1BlockSize.Block32x64, // TX_32X64 + Av1BlockSize.Block64x32, // TX_64X32 + Av1BlockSize.Block4x16, // TX_4X16 + Av1BlockSize.Block16x4, // TX_16X4 + Av1BlockSize.Block8x32, // TX_8X32 + Av1BlockSize.Block32x8, // TX_32X8 + Av1BlockSize.Block16x64, // TX_16X64 + Av1BlockSize.Block64x16, // TX_64X16 + ]; + public static int GetScale(this Av1TransformSize size) { int pels = Size2d[(int)size]; @@ -55,4 +78,6 @@ public static int GetScale(this Av1TransformSize size) public static int Get4x4HighCount(this Av1TransformSize size) => HighUnit[(int)size]; public static Av1TransformSize GetSubSize(this Av1TransformSize size) => SubTransformSize[(int)size]; + + public static Av1BlockSize GetBlockSize(this Av1TransformSize transformSize) => (Av1BlockSize)BlockSize[(int)transformSize]; } From 4e53f7cf8124af90e7ac6712c9050d4ad153ac93 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 5 Jul 2024 11:30:00 +0200 Subject: [PATCH 100/190] Add some code comments --- src/ImageSharp/Formats/Heif/Av1/Readme.md | 6 ++- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 3 -- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 33 +++++++++--- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 32 +++++------- .../Heif/Av1/Tiling/Av1TransformInfo.cs | 50 ++++++++++++++++--- .../Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 6 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md index fca0c25f4b..c523cde784 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Readme.md +++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md @@ -41,7 +41,11 @@ Paritions can contain other partitions and blocks. ## Block -A block is the smallest are of the image which has the same transformation parameters. A block contains ore or more ModeInfos. + +## Transform Block + +A Transform Block is the smallest area of the image, which has the same transformation parameters. A block contains ore or more ModeInfos. + ## ModeInfo diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 8ca3d67fe3..4bf6a99966 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -61,9 +61,6 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) this.superblockInfos[i] = new(this, point); for (int u = 0; u < this.modeInfoSizePerSuperblock; u++) { - this.transformInfosY[j] = new Av1TransformInfo(); - this.transformInfosY[j << 1] = new Av1TransformInfo(); - this.transformInfosY[(j << 1) + 1] = new Av1TransformInfo(); for (int v = 0; v < this.modeInfoSizePerSuperblock; v++) { this.modeInfos[k] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index 9ae390d117..1a73c5eefc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -17,29 +17,50 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI this.ModeInfo = modeInfo; this.SuperblockInfo = superblockInfo; this.IsChroma = isChroma; - this.PartitionType = partitionType; + this.Type = partitionType; this.CdefStrength = []; this.ReferenceFrame = [-1, -1]; } public Av1BlockModeInfo ModeInfo { get; } + /// + /// Gets the this partition resides inside. + /// public Av1SuperblockInfo SuperblockInfo { get; } public bool IsChroma { get; } - public Av1PartitionType PartitionType { get; } + public Av1PartitionType Type { get; } - public bool AvailableUp { get; set; } + /// + /// Gets or sets a value indicating whether the information from the block above can be used on the luma plane. + /// + public bool AvailableAbove { get; set; } + /// + /// Gets or sets a value indicating whether the information from the block left can be used on the luma plane. + /// public bool AvailableLeft { get; set; } - public bool AvailableUpForChroma { get; set; } + /// + /// Gets or sets a value indicating whether the information from the block above can be used on the chroma plane. + /// + public bool AvailableAboveForChroma { get; set; } + /// + /// Gets or sets a value indicating whether the information from the block left can be used on the chroma plane. + /// public bool AvailableLeftForChroma { get; set; } + /// + /// Gets or sets the horizontal location of the block in units of 4x4 luma samples. + /// public int ColumnIndex { get; set; } + /// + /// Gets or sets the vertical location of the block in units of 4x4 luma samples. + /// public int RowIndex { get; set; } public Av1BlockModeInfo? AboveModeInfo { get; set; } @@ -59,9 +80,9 @@ public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInf Av1BlockSize blockSize = this.ModeInfo.BlockSize; int bw4 = blockSize.Get4x4WideCount(); int bh4 = blockSize.Get4x4HighCount(); - this.AvailableUp = this.RowIndex > tileInfo.ModeInfoRowStart; + this.AvailableAbove = this.RowIndex > tileInfo.ModeInfoRowStart; this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart; - this.AvailableUpForChroma = this.AvailableUp; + this.AvailableAboveForChroma = this.AvailableAbove; this.AvailableLeftForChroma = this.AvailableLeft; this.modeBlockToLeftEdge = -(this.ColumnIndex << Av1Constants.ModeInfoSizeLog2) << 3; this.modeBlockToRightEdge = ((frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << Av1Constants.ModeInfoSizeLog2) << 3; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index a329a4ba6a..648a39d407 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -346,7 +346,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) { - partitionInfo.AvailableUpForChroma = this.IsInside(rowIndex - 2, columnIndex); + partitionInfo.AvailableAboveForChroma = this.IsInside(rowIndex - 2, columnIndex); } if (this.SequenceHeader.ColorConfig.SubSamplingX && block4x4Width == 1) @@ -355,7 +355,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 } } - if (partitionInfo.AvailableUp) + if (partitionInfo.AvailableAbove) { partitionInfo.AboveModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex - 1, columnIndex)); } @@ -456,8 +456,8 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf int coefficientIndex = this.coefficientIndex[plane]; int endOfBlock = 0; Av1TransformInfo transformInfo = this.FrameBuffer.GetTransform(plane, transformInfoIndices[plane])!; - int blockColumn = transformInfo.TransformOffsetX; - int blockRow = transformInfo.TransformOffsetY; + int blockColumn = transformInfo.OffsetX; + int blockRow = transformInfo.OffsetY; int startX = (partitionInfo.ColumnIndex >> subX) + blockColumn; int startY = (partitionInfo.RowIndex >> subY) + blockRow; @@ -469,7 +469,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf if (!partitionInfo.ModeInfo.Skip) { - endOfBlock = this.TransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.TransformSize, subX != 0, subY != 0); + endOfBlock = this.TransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.Size, subX != 0, subY != 0); } if (endOfBlock != 0) @@ -824,7 +824,7 @@ private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, int above = (aboveWidth >= maxTransformSize.GetWidth()) ? 1 : 0; int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.Position.Y]; int left = (leftHeight >= maxTransformSize.GetHeight()) ? 1 : 0; - bool hasAbove = partitionInfo.AvailableUp; + bool hasAbove = partitionInfo.AvailableAbove; bool hasLeft = partitionInfo.AvailableLeft; if (hasAbove && hasLeft) @@ -902,12 +902,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Block { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - this.FrameBuffer.SetTransformY(transformInfoYIndex, new Av1TransformInfo - { - TransformSize = transformSize, - OffsetX = blockColumn, - OffsetY = blockRow - }); + this.FrameBuffer.SetTransformY(transformInfoYIndex, new Av1TransformInfo( + transformSize, blockColumn, blockRow)); transformInfoYIndex++; lumaTusCount++; totalLumaTusCount++; @@ -931,12 +927,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Block { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - this.FrameBuffer.SetTransformUv(transformInfoUvIndex, new Av1TransformInfo - { - TransformSize = transformSizeUv, - OffsetX = blockColumn, - OffsetY = blockRow - }); + this.FrameBuffer.SetTransformUv(transformInfoUvIndex, new Av1TransformInfo( + transformSizeUv, blockColumn, blockRow)); transformInfoUvIndex++; chromaTusCount++; totalChromaTusCount++; @@ -1176,12 +1168,12 @@ private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partiti int prevL = -1; int columnIndex = partitionInfo.ColumnIndex; int rowIndex = partitionInfo.RowIndex; - if (partitionInfo.AvailableUp && partitionInfo.AvailableLeft) + if (partitionInfo.AvailableAbove && partitionInfo.AvailableLeft) { prevUL = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex - 1); } - if (partitionInfo.AvailableUp) + if (partitionInfo.AvailableAbove) { prevU = this.GetSegmentId(partitionInfo, rowIndex - 1, columnIndex); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index 252c969a30..9f6af30c53 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs @@ -5,28 +5,64 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +/// +/// Information of a single Transform Block. +/// internal class Av1TransformInfo { - public Av1TransformInfo() + /// + /// Initializes a new instance of the class. + /// + public Av1TransformInfo(Av1TransformSize size, int offsetX, int offsetY) { + this.Size = size; + this.OffsetX = offsetX; + this.OffsetY = offsetY; } + /// + /// Initializes a new instance of the class. + /// + /// The to copy the information from. public Av1TransformInfo(Av1TransformInfo originalInfo) { - this.TransformSize = originalInfo.TransformSize; + this.Size = originalInfo.Size; this.OffsetX = originalInfo.OffsetX; this.OffsetY = originalInfo.OffsetY; } - public Av1TransformSize TransformSize { get; internal set; } + /// + /// Gets or sets the transform size to be used for this Transform Block. + /// + public Av1TransformSize Size { get; internal set; } + /// + /// Gets or sets the transform type to be used for this Transform Block. + /// + public Av1TransformType Type { get; internal set; } + + /// + /// Gets or sets the X offset of this block in ModeInfo units. + /// public int OffsetX { get; internal set; } + /// + /// Gets or sets the Y offset of this block in ModeInfo units. + /// public int OffsetY { get; internal set; } + /// + /// Gets or sets a value indicating whether the Code block flag is set. + /// + /// + /// false + /// No residual for the block + /// + /// + /// true + /// Residual exists for the block + /// + /// + /// public bool CodeBlockFlag { get; internal set; } - - public int TransformOffsetX { get; internal set; } - - public int TransformOffsetY { get; internal set; } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index c0ce99c3ae..31cab43ee6 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class Av1TilingTests { - // [Theory] + [Theory] [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 0x03CC - 18)] public void ReadFirstTile(string filename, int headerOffset, int headerSize, int tileOffset, int tileSize) { From 38c23a5400b0845b212dac2283b9f62f9bcb8582 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 5 Jul 2024 11:36:59 +0200 Subject: [PATCH 101/190] Disable failing test for in-progress code --- tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 31cab43ee6..c0ce99c3ae 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class Av1TilingTests { - [Theory] + // [Theory] [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 0x03CC - 18)] public void ReadFirstTile(string filename, int headerOffset, int headerSize, int tileOffset, int tileSize) { From 30f88843915c26623bc48c36357ddfa9e84ab78e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 5 Jul 2024 21:41:49 +0200 Subject: [PATCH 102/190] Implement Coefficient syntax --- .../Formats/Heif/Av1/Av1Constants.cs | 26 + .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 2 +- .../Prediction/Av1PreditionModeExtensions.cs | 27 + .../Av1/Tiling/Av1DefaultDistributions.cs | 249 +++++++++ .../Heif/Av1/Tiling/Av1Distribution.cs | 10 + .../Formats/Heif/Av1/Tiling/Av1NzMap.cs | 356 ++++++++++++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 28 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 528 ++++++++++++++++-- .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 2 +- .../Heif/Av1/Transform/Av1ScanOrder.cs | 2 +- .../Av1/Transform/Av1ScanOrderConstants.cs | 8 +- .../Heif/Av1/Transform/Av1TransformClass.cs | 11 + .../Heif/Av1/Transform/Av1TransformSetType.cs | 37 ++ .../Transform/Av1TransformSizeExtensions.cs | 96 +++- .../Transform/Av1TransformTypeExtensions.cs | 42 ++ .../Formats/Heif/Av1/SymbolTest.cs | 4 +- 18 files changed, 1384 insertions(+), 48 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 05656a6d18..a8f1a86658 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -140,4 +140,30 @@ internal static class Av1Constants public const int MaxTransformCategories = 4; public const int CoefficientContextCount = 6; + + public const int BaseLevelsCount = 2; + + public const int CoefficientBaseRange = 12; + + public const int TransformPadHorizontalLog2 = 2; + + public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2; + + public const int TransformPadVertical = 6; + + public const int TransformPadEnd = 16; + + public const int CoefficientContextBits = 6; + + public const int CoefficientContextMask = (1 << CoefficientContextBits) - 1; + + public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd; + + public const int MaxTransformSize = 1 << 6; + + public const int TransformPadTop = 2; + + public const int BaseRangeSizeMinus1 = 3; + + public const int MaxBaseRange = 15; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index a107562637..7509da20a8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -33,7 +33,7 @@ internal class ObuFrameHeader public ObuFilmGrainParameters FilmGrainParameters { get; set; } = new ObuFilmGrainParameters(); - public bool ReducedTransformSet { get; set; } + public bool UseReducedTransformSet { get; set; } public ObuLoopFilterParameters LoopFilterParameters { get; set; } = new ObuLoopFilterParameters(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index db0131604a..95736d9dc4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1159,7 +1159,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameInfo.AllowWarpedMotion = reader.ReadBoolean(); } - frameInfo.ReducedTransformSet = reader.ReadBoolean(); + frameInfo.UseReducedTransformSet = reader.ReadBoolean(); ReadGlobalMotionParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame); frameInfo.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameInfo); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 226116c797..aa4b22a03b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -415,7 +415,7 @@ private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, // Not applicable for INTRA frames. // WriteFrameReferenceMode(ref writer, frameInfo.ReferenceMode, isIntraFrame); // WriteSkipModeParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); - writer.WriteBoolean(frameInfo.ReducedTransformSet); + writer.WriteBoolean(frameInfo.UseReducedTransformSet); // Not applicable for INTRA frames. // WriteGlobalMotionParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame); diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs new file mode 100644 index 0000000000..d3b1df0357 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal static class Av1PreditionModeExtensions +{ + private static readonly Av1TransformType[] IntraPreditionMode2TransformType = [ + Av1TransformType.DctDct, // DC + Av1TransformType.AdstDct, // V + Av1TransformType.DctAdst, // H + Av1TransformType.DctDct, // D45 + Av1TransformType.AdstAdst, // D135 + Av1TransformType.AdstDct, // D117 + Av1TransformType.DctAdst, // D153 + Av1TransformType.DctAdst, // D207 + Av1TransformType.AdstDct, // D63 + Av1TransformType.AdstAdst, // SMOOTH + Av1TransformType.AdstDct, // SMOOTH_V + Av1TransformType.DctAdst, // SMOOTH_H + Av1TransformType.AdstAdst, // PAETH + ]; + + public static Av1TransformType ToTransformType(this Av1PredictionMode mode) => IntraPreditionMode2TransformType[(int)mode]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index fbd0d99856..476a85f883 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal static class Av1DefaultDistributions @@ -179,4 +181,251 @@ internal static class Av1DefaultDistributions [new(12986, 15180), new(12986, 15180), new(24302, 25602)], [new(5782, 11475), new(5782, 11475), new(16803, 22759)], ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti16 => + [ + [ + [new(840, 1039, 1980, 4895), new(370, 671, 1883, 4471)], + [new(3247, 4950, 9688, 14563), new(1904, 3354, 7763, 14647)] + ], + [ + [new(2125, 2551, 5165, 8946), new(513, 765, 1859, 6339)], + [new(7637, 9498, 14259, 19108), new(2497, 4096, 8866, 16993)] + ], + [ + [new(4016, 4897, 8881, 14968), new(716, 1105, 2646, 10056)], + [new(11139, 13270, 18241, 23566), new(3192, 5032, 10297, 19755)] + ], + [ + [new(6708, 8958, 14746, 22133), new(1222, 2074, 4783, 15410)], + [new(19575, 21766, 26044, 29709), new(7297, 10767, 19273, 28194)] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti32 => + [ + [ + [new(400, 520, 977, 2102, 6542), new(210, 405, 1315, 3326, 7537)], + [new(2636, 4273, 7588, 11794, 20401), new(1786, 3179, 6902, 11357, 19054)] + ], + [ + [new(989, 1249, 2019, 4151, 10785), new(313, 441, 1099, 2917, 8562)], + [new(8394, 10352, 13932, 18855, 26014), new(2578, 4124, 8181, 13670, 24234)] + ], + [ + [new(2515, 3003, 4452, 8162, 16041), new(574, 821, 1836, 5089, 13128)], + [new(13468, 16303, 20361, 25105, 29281), new(3542, 5502, 10415, 16760, 25644)] + ], + [ + [new(4617, 5709, 8446, 13584, 23135), new(1156, 1702, 3675, 9274, 20539)], + [new(22086, 24282, 27010, 29770, 31743), new(7699, 10897, 20891, 26926, 31628)] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti64 => + [ + [ + [new(329, 498, 1101, 1784, 3265, 7758), new(335, 730, 1459, 5494, 8755, 12997)], + [new(3505, 5304, 10086, 13814, 17684, 23370), new(1563, 2700, 4876, 10911, 14706, 22480)], + ], + [ + [new(1260, 1446, 2253, 3712, 6652, 13369), new(401, 605, 1029, 2563, 5845, 12626)], + [new(8609, 10612, 14624, 18714, 22614, 29024), new(1923, 3127, 5867, 9703, 14277, 27100)] + ], + [ + [new(2374, 2772, 4583, 7276, 12288, 19706), new(497, 810, 1315, 3000, 7004, 15641)], + [new(15050, 17126, 21410, 24886, 28156, 30726), new(4034, 6290, 10235, 14982, 21214, 28491)] + ], + [ + [new(6307, 7541, 12060, 16358, 22553, 27865), new(1289, 2320, 3971, 7926, 14153, 24291)], + [new(24212, 25708, 28268, 30035, 31307, 32049), new(8726, 12378, 19409, 26450, 30038, 32462)] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti128 => + [ + [ + [new(219, 482, 1140, 2091, 3680, 6028, 12586), new(371, 699, 1254, 4830, 9479, 12562, 17497)], + [new(5245, 7456, 12880, 15852, 20033, 23932, 27608), new(2054, 3472, 5869, 14232, 18242, 20590, 26752)] + ], + [ + [new(685, 933, 1488, 2714, 4766, 8562, 19254), new(217, 352, 618, 2303, 5261, 9969, 17472)], + [new(8045, 11200, 15497, 19595, 23948, 27408, 30938), new(2310, 4160, 7471, 14997, 17931, 20768, 30240)] + ], + [ + [new(1366, 1738, 2527, 5016, 9355, 15797, 24643), new(354, 558, 944, 2760, 7287, 14037, 21779)], + [new(13627, 16246, 20173, 24429, 27948, 30415, 31863), new(6275, 9889, 14769, 23164, 27988, 30493, 32272)] + ], + [ + [new(3472, 4885, 7489, 12481, 18517, 24536, 29635), new(886, 1731, 3271, 8469, 15569, 22126, 28383)], + [new(24313, 26062, 28385, 30107, 31217, 31898, 32345), new(9165, 13282, 21150, 30286, 31894, 32571, 32712)] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti256 => + [ + [ + [ + new(310, 584, 1887, 3589, 6168, 8611, 11352, 15652), + new(998, 1850, 2998, 5604, 17341, 19888, 22899, 25583), + ], + [ + new(2520, 3240, 5952, 8870, 12577, 17558, 19954, 24168), + new(2203, 4130, 7435, 10739, 20652, 23681, 25609, 27261) + ], + ], + [ + [ + new(1448, 2109, 4151, 6263, 9329, 13260, 17944, 23300), + new(399, 1019, 1749, 3038, 10444, 15546, 22739, 27294) + ], + [ + new(6402, 8148, 12623, 15072, 18728, 22847, 26447, 29377), + new(1674, 3252, 5734, 10159, 22397, 23802, 24821, 30940) + ] + ], + [ + [ + new(3089, 3920, 6038, 9460, 14266, 19881, 25766, 29176), + new(1084, 2358, 3488, 5122, 11483, 18103, 26023, 29799) + ], + [ + new(11514, 13794, 17480, 20754, 24361, 27378, 29492, 31277), + new(6571, 9610, 15516, 21826, 29092, 30829, 31842, 32708) + ] + ], + [ + [ + new(5348, 7113, 11820, 15924, 22106, 26777, 30334, 31757), + new(2453, 4474, 6307, 8777, 16474, 22975, 29000, 31547) + ], + [ + new(23110, 24597, 27140, 28894, 30167, 30927, 31392, 32094), + new(9998, 17661, 25178, 28097, 31308, 32038, 32403, 32695) + ] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti512 => + [ + [ + [ + new(641, 983, 3707, 5430, 10234, 14958, 18788, 23412, 26061), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ], + [ + new(5095, 6446, 9996, 13354, 16017, 17986, 20919, 26129, 29140), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ] + ], + [ + [ + new(1230, 2278, 5035, 7776, 11871, 15346, 19590, 24584, 28749), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ], + [ + new(7265, 9979, 15819, 19250, 21780, 23846, 26478, 28396, 31811), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ] + ], + [ + [ + new(2624, 3936, 6480, 9686, 13979, 17726, 23267, 28410, 31078), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ], + [ + new(12015, 14769, 19588, 22052, 24222, 25812, 27300, 29219, 32114), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ] + ], + [ + [ + new(5927, 7809, 10923, 14597, 19439, 24135, 28456, 31142, 32060), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ], + [ + new(21093, 23043, 25742, 27658, 29097, 29716, 30073, 30820, 31956), + new(3277, 6554, 9830, 13107, 16384, 19661, 22938, 26214, 29491) + ] + ] + ]; + + private static Av1Distribution[][][] EndOfBlockFlagMulti1024 => + [ + [ + [ + new(393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ], + [ + new(1865, 1988, 2930, 4242, 10533, 16538, 21354, 27255, 28546, 31784), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ] + ], + [ + [ + new(696, 948, 3145, 5702, 9706, 13217, 17851, 21856, 25692, 28034), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ], + [ + new(2672, 3591, 9330, 17084, 22725, 24284, 26527, 28027, 28377, 30876), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ] + ], + [ + [ + new(2784, 3831, 7041, 10521, 14847, 18844, 23155, 26682, 29229, 31045), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ], + [ + new(9577, 12466, 17739, 20750, 22061, 23215, 24601, 25483, 25843, 32056), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ] + ], + [ + [ + new(6698, 8334, 11961, 15762, 20186, 23862, 27434, 29326, 31082, 32050), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ], + [ + new(20569, 22426, 25569, 26859, 28053, 28913, 29486, 29724, 29807, 32570), + new(2979, 5958, 8937, 11916, 14895, 17873, 20852, 23831, 26810, 29789) + ] + ] + ]; + + public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) + { + int qContext = GetQContext(baseQIndex); + return + [ + EndOfBlockFlagMulti16[qContext], + EndOfBlockFlagMulti32[qContext], + EndOfBlockFlagMulti64[qContext], + EndOfBlockFlagMulti128[qContext], + EndOfBlockFlagMulti256[qContext], + EndOfBlockFlagMulti512[qContext], + EndOfBlockFlagMulti1024[qContext], + ]; + } + + private static int GetQContext(int q) + { + if (q <= 20) + { + return 0; + } + + if (q <= 60) + { + return 1; + } + + if (q <= 120) + { + return 2; + } + + return 3; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs index c3d33a4409..241b6ec4e1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs @@ -54,11 +54,21 @@ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uin { } + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7) + : this([p0, p1, p2, p3, p4, p5, p6, p7, 0], 2) + { + } + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8) : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, 0], 2) { } + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, 0], 2) + { + } + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11) : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, 0], 2) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs new file mode 100644 index 0000000000..4326370ecc --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs @@ -0,0 +1,356 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal static class Av1NzMap +{ + private static readonly int[] ClipMax3 = [ + 0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 + ]; + + // SIG_COEF_CONTEXTS_2D = 26 + private const int NzMapContext0 = 26; + private const int NzMapContext5 = NzMapContext0 + 5; + private const int NzMapContext10 = NzMapContext0 + 10; + + private static readonly int[] NzMapContextOffset1d = [ + NzMapContext0, NzMapContext5, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, + NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, + NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, + NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, + NzMapContext10, NzMapContext10, NzMapContext10, NzMapContext10, + ]; + + // The ctx offset table when TX is TX_CLASS_2D. + // TX col and row indices are clamped to 4 + private static readonly int[] NzMapContextOffset4x4 = [0, 1, 6, 6, 1, 6, 6, 21, 6, 6, 21, 21, 6, 21, 21, 21]; + + private static readonly int[] NzMapContextOffset8x8 = [ + 0, 1, 6, 6, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 6, 6, 21, 21, 21, 21, + 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset16x16 = [ + 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset32x32 = [ + 0, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 1, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset8x4 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, + 16, 16, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset16x8 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset16x32 = [ + 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset32x16 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset32x64 = [ + 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset64x32 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, + 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset4x16 = [ + 0, 11, 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset16x4 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 6, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset8x32 = [ + 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 6, 6, 21, 21, 21, 21, 21, 21, 6, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[] NzMapContextOffset32x8 = [ + 0, 16, 6, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 16, 16, 6, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 16, 16, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, + ]; + + private static readonly int[][] NzMapContextOffset = [ + NzMapContextOffset4x4, // TX_4x4 + NzMapContextOffset8x8, // TX_8x8 + NzMapContextOffset16x16, // TX_16x16 + NzMapContextOffset32x32, // TX_32x32 + NzMapContextOffset32x32, // TX_32x32 + NzMapContextOffset4x16, // TX_4x8 + NzMapContextOffset8x4, // TX_8x4 + NzMapContextOffset8x32, // TX_8x16 + NzMapContextOffset16x8, // TX_16x8 + NzMapContextOffset16x32, // TX_16x32 + NzMapContextOffset32x16, // TX_32x16 + NzMapContextOffset32x64, // TX_32x64 + NzMapContextOffset64x32, // TX_64x32 + NzMapContextOffset4x16, // TX_4x16 + NzMapContextOffset16x4, // TX_16x4 + NzMapContextOffset8x32, // TX_8x32 + NzMapContextOffset32x8, // TX_32x8 + NzMapContextOffset16x32, // TX_16x64 + NzMapContextOffset64x32, // TX_64x16 + ]; + + public static int GetNzMagnitude(Span levels, int bwl, Av1TransformClass transformClass) + { + int mag; + + // Note: AOMMIN(level, 3) is useless for decoder since level < 3. + mag = ClipMax3[levels[1]]; // { 0, 1 } + mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal]]; // { 1, 0 } + + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1]]; // { 1, 1 } + mag += ClipMax3[levels[2]]; // { 0, 2 } + mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } + break; + + case Av1TransformClass.ClassVertical: + mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } + mag += ClipMax3[levels[(3 << bwl) + (3 << Av1Constants.TransformPadHorizontalLog2)]]; // { 3, 0 } + mag += ClipMax3[levels[(4 << bwl) + (4 << Av1Constants.TransformPadHorizontalLog2)]]; // { 4, 0 } + break; + case Av1TransformClass.ClassHorizontal: + mag += ClipMax3[levels[2]]; // { 0, 2 } + mag += ClipMax3[levels[3]]; // { 0, 3 } + mag += ClipMax3[levels[4]]; // { 0, 4 } + break; + } + + return mag; + } + + public static int GetNzMapContextFromStats(int stats, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + { + // tx_class == 0(TX_CLASS_2D) + if (((int)transformClass | pos) == 0) + { + return 0; + } + + int ctx = (stats + 1) >> 1; + ctx = Math.Min(ctx, 4); + switch (transformClass) + { + case Av1TransformClass.Class2D: + // This is the algorithm to generate eb_av1_nz_map_ctx_offset[][] + // const int width = tx_size_wide[tx_size]; + // const int height = tx_size_high[tx_size]; + // if (width < height) { + // if (row < 2) return 11 + ctx; + // } else if (width > height) { + // if (col < 2) return 16 + ctx; + // } + // if (row + col < 2) return ctx + 1; + // if (row + col < 4) return 5 + ctx + 1; + // return 21 + ctx; + return ctx + NzMapContextOffset[(int)transformSize][pos]; + case Av1TransformClass.ClassHorizontal: + int row = pos >> bwl; + int col = pos - (row << bwl); + return ctx + NzMapContextOffset1d[col]; + case Av1TransformClass.ClassVertical: + int row2 = pos >> bwl; + return ctx + NzMapContextOffset1d[row2]; + default: + break; + } + + return 0; + } + + public static int GetNzMapContext(Av1TransformSize transformSize, int pos) => NzMapContextOffset[(int)transformSize][pos]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 7d6eb4998f..b5340ecd26 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Drawing; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -23,9 +22,14 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution filterIntraMode = Av1DefaultDistributions.FilterIntraMode; private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; + private readonly Av1Distribution[][][] endOfBlockFlag; private Av1SymbolReader reader; - public Av1SymbolDecoder(Span tileData) => this.reader = new Av1SymbolReader(tileData); + public Av1SymbolDecoder(Span tileData, int qIndex) + { + this.reader = new Av1SymbolReader(tileData); + this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); + } public int ReadLiteral(int bitCount) { @@ -176,6 +180,26 @@ public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) return transformSize; } + public int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize) + { + int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; + int endOfBlockMultiSize = transformSize.GetLog2Minus4(); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1; + } + + public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) => throw new NotImplementedException(); + + public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) => throw new NotImplementedException(); + + public int ReadBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) => throw new NotImplementedException(); + + public int ReadDcSign(Av1PlaneType planeType, int dcSignContext) => throw new NotImplementedException(); + + public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) => throw new NotImplementedException(); + + public int ReadBase(int coeff_ctx, Av1TransformSize transformSizeContext, Av1PlaneType planeType) => throw new NotImplementedException(); + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 648a39d407..c9ea1487d5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; +using System.Formats.Asn1; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -21,16 +23,15 @@ internal class Av1TileDecoder : IAv1TileDecoder private static readonly int[][] SkipContexts = [ [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]]; + private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; + private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; - private Av1ParseAboveNeighbor4x4Context aboveNeighborContext; - private Av1ParseLeftNeighbor4x4Context leftNeighborContext; + private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext; + private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext; private int currentQuantizerIndex; - private int[][] aboveLevelContext = []; - private int[][] aboveDcContext = []; - private int[][] leftLevelContext = []; - private int[][] leftDcContext = []; private int[][] segmentIds = []; private int deltaLoopFilterResolution = -1; private bool readDeltas; @@ -76,7 +77,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo public void DecodeTile(Span tileData, int tileNum) { - Av1SymbolDecoder reader = new(tileData); + Av1SymbolDecoder reader = new(tileData, this.FrameInfo.QuantizationParameters.BaseQIndex); int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; @@ -521,6 +522,9 @@ private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) /// /// 5.11.35. Transform block syntax. /// + /// + /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. + /// private int TransformBlock( ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, @@ -561,7 +565,486 @@ private int TransformBlock( /// /// 5.11.39. Coefficients syntax. /// - private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int startY, int startX, int blockRow, int blockColumn, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, int coefficientIndex, Av1TransformInfo transformInfo) => throw new NotImplementedException(); + /// + /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. + /// + private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, int coefficientIndex, Av1TransformInfo transformInfo) + { + Span coefficientBuffer = this.FrameBuffer.GetCoefficients(plane); + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); + Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); + int culLevel = 0; + int dcValue = 0; + + int[] levelsBuffer = new int[Av1Constants.TransformPad2d]; + Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; + + bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); + int bwl = transformSize.GetBlockWidthLog2(); + int endOfBlock; + int maxScanLine = 0; + if (allZero) + { + if (plane == 0) + { + transformInfo.Type = Av1TransformType.DctDct; + transformInfo.CodeBlockFlag = false; + } + + this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); + return 0; + } + + int endOfBlockExtra = 0; + int endOfBlockPoint = 0; + + transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo); + Av1TransformClass transformClass = transformInfo.Type.ToClass(); + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); + Span scan = scanOrder.Scan; + + endOfBlockPoint = reader.ReadEndOfBlockFlag(planeType, transformClass, transformSize); + int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; + if (endOfBlockShift > 0) + { + int endOfBlockContext = endOfBlockPoint; + bool bit = reader.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); + if (bit) + { + endOfBlockExtra += 1 << (endOfBlockShift - 1); + } + else + { + for (int j = 1; j < endOfBlockShift; j++) + { + if (reader.ReadLiteral(1) != 0) + { + endOfBlockExtra += 1 << (endOfBlockShift - 1 - j); + } + } + } + } + + endOfBlock = RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); + if (endOfBlock > 1) + { + Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + } + + int i = endOfBlock - 1; + int pos = scan[i]; + int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i); + int level = reader.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) + { + int coefficinetBaseRange = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + level += coefficinetBaseRange; + if (coefficinetBaseRange < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + + if (endOfBlock > 1) + { + if (transformClass == Av1TransformClass.Class2D) + { + ReadCoefficientsReverse2d(ref reader, transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, 0, scan, bwl, levels, transformSizeContext, planeType); + } + else + { + ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + } + } + + coefficientBuffer[this.coefficientIndex[plane]] = endOfBlock; + for (int c = 0; c < endOfBlock; c++) + { + int sign = 0; + level = levels[GetPaddedIndex(scan[c], bwl)]; + if (level != 0) + { + maxScanLine = Math.Max(maxScanLine, scan[c]); + if (c == 0) + { + sign = reader.ReadDcSign(planeType, transformBlockContext.DcSignContext); + } + else + { + sign = reader.ReadLiteral(1); + } + + if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1) + { + level += ReadGolomb(ref reader); + } + + if (c == 0) + { + dcValue = sign != 0 ? -level : level; + } + + level &= 0xfffff; + culLevel += level; + } + + coefficientBuffer[c + 1] = sign != 0 ? -level : level; + } + + culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); + SetDcSign(ref culLevel, dcValue); + + this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); + + transformInfo.CodeBlockFlag = true; + return endOfBlock; + } + + private static void ReadCoefficientsReverse2d(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + for (int c = endSi; c >= startSi; --c) + { + int pos = scan[c]; + int coeff_ctx = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); + int level = reader.ReadBase(pos, transformSizeContext, planeType); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) + { + int k = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + level += k; + if (k < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + } + } + + private static int GetBaseRangeContext2d(Span levels, int c, int bwl) + { + DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); + int row = c >> bwl; + int col = c - (row << bwl); + int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; + int pos = (row * stride) + col; + int mag = + Math.Min(levels[pos + 1], Av1Constants.MaxBaseRange) + + Math.Min(levels[pos + stride], Av1Constants.MaxBaseRange) + + Math.Min(levels[pos + 1 + stride], Av1Constants.MaxBaseRange); + mag = Math.Min((mag + 1) >> 1, 6); + if ((row | col) < 2) + { + return mag + 7; + } + + return mag + 14; + } + + private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) + { + DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); + int mag; + levels = levels[GetPaddedIndex(pos, bwl)..]; + mag = Math.Min(levels[1], 3); // { 0, 1 } + mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } + mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } + mag += Math.Min(levels[2], 3); // { 0, 2 } + mag += Math.Min(levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } + + int ctx = Math.Min((mag + 1) >> 1, 4); + return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); + } + + private static void ReadCoefficientsReverse(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, Av1TransformType transformType, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + Av1TransformClass transformClass = transformType.ToClass(); + for (int c = endSi; c >= startSi; --c) + { + int pos = scan[c]; + int coeff_ctx = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); + int level = reader.ReadBase(coeff_ctx, transformSizeContext, planeType); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) + { + int k = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + level += k; + if (k < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + } + } + + private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) + { + int row = c >> bwl; + int col = c - (row << bwl); + int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; + int pos = (row * stride) + col; + int mag = levels[pos + 1]; + mag += levels[pos + stride]; + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += levels[pos + stride + 1]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if ((row < 2) && (col < 2)) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassHorizontal: + mag += levels[pos + 2]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (col == 0) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassVertical: + mag += levels[pos + (stride << 1)]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (row == 0) + { + return mag + 7; + } + + break; + default: + break; + } + + return mag + 14; + } + + private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + { + int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); + return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + } + + private static int ReadGolomb(ref Av1SymbolDecoder reader) + { + int x = 1; + int length = 0; + int i = 0; + + while (i == 0) + { + i = reader.ReadLiteral(1); + ++length; + if (length > 20) + { + // SVT_LOG("Invalid length in read_golomb"); + break; + } + } + + for (i = 0; i < length - 1; ++i) + { + x <<= 1; + x += reader.ReadLiteral(1); + } + + return x - 1; + } + + private static void SetDcSign(ref int culLevel, int dcValue) + { + if (dcValue < 0) + { + culLevel |= 1 << Av1Constants.CoefficientContextBits; + } + else if (dcValue > 0) + { + culLevel += 2 << Av1Constants.CoefficientContextBits; + } + } + + private static int GetPaddedIndex(int scanIndex, int bwl) + => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2); + + private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) + { + int row = pos >> bwl; + int col = pos - (row << bwl); + if (pos == 0) + { + return 0; + } + + if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || + (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || + (transformClass == Av1TransformClass.ClassVertical && row == 0)) + { + return 7; + } + + return 14; + } + + private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex) + { + if (scanIndex == 0) + { + return 0; + } + + if (scanIndex <= (height << bwl) >> 3) + { + return 1; + } + + if (scanIndex <= (height << bwl) >> 2) + { + return 2; + } + + return 3; + } + + private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int culLevel) + { + bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; + bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + int[] aboveContexts = this.aboveNeighborContext.GetContext(plane); + int[] leftContexts = this.leftNeighborContext.GetContext(plane); + int transformSizeWide = transformSize.Get4x4WideCount(); + int transformSizeHigh = transformSize.Get4x4HighCount(); + + if (partitionInfo.ModeBlockToRightEdge < 0) + { + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); + int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset); + Array.Fill(aboveContexts, culLevel, 0, aboveContextCount); + Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount); + } + else + { + Array.Fill(aboveContexts, culLevel, 0, transformSizeWide); + } + + if (partitionInfo.ModeBlockToBottomEdge < 0) + { + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); + int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); + Array.Fill(leftContexts, culLevel, 0, leftContextCount); + Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); + } + else + { + Array.Fill(leftContexts, culLevel, 0, transformSizeHigh); + } + } + + private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) + { + int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; + if (endOfBlock > 2) + { + endOfBlock += endOfBlockExtra; + } + + return endOfBlock; + } + + private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo) + { + Av1TransformType transformType = Av1TransformType.DctDct; + if (this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) + { + transformType = Av1TransformType.DctDct; + } + else + { + if (planeType == Av1PlaneType.Y) + { + transformType = transformInfo.Type; + } + else + { + // In intra mode, uv planes don't share the same prediction mode as y + // plane, so the tx_type should not be shared + transformType = ConvertIntraModeToTransformType(partitionInfo.ModeInfo, Av1PlaneType.Uv); + } + } + + Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameInfo.UseReducedTransformSet); + if (!transformType.IsExtendedSetUsed(transformSetType)) + { + transformType = Av1TransformType.DctDct; + } + + return transformType; + } + + private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) + { + Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); + + if (squareUpSize >= Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (useReducedSet) + { + return Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize squareSize = transformSize.GetSquareSize(); + return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + + private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) + { + Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; + if (mode == Av1PredictionMode.UvChromaFromLuma) + { + mode = Av1PredictionMode.DC; + } + + return mode.ToTransformType(); + } private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX) { @@ -583,7 +1066,7 @@ private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize trans if (plane == 0) { - if (planeBlockSize == transformSize.GetBlockSize()) + if (planeBlockSize == transformSize.ToBlockSize()) { transformBlockContext.SkipContext = 0; } @@ -617,7 +1100,7 @@ private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize trans else { int contextBase = GetEntropyContext(transformSize, aboveContext, leftContext); - int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.GetBlockSize().GetPelsLog2Count() ? 10 : 7; + int contextOffset = planeBlockSize.GetPelsLog2Count() > transformSize.ToBlockSize().GetPelsLog2Count() ? 10 : 7; transformBlockContext.SkipContext = contextBase + contextOffset; } @@ -771,30 +1254,9 @@ private static int GetEntropyContext(Av1TransformSize transformSize, int[] above /// /// Page 65, below 5.11.5. Decode block syntax. /// - private void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) + private static void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) { - int block4x4Width = blockSize.Get4x4WideCount(); - int block4x4Height = blockSize.Get4x4HighCount(); - bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - int endPlane = this.HasChroma(rowIndex, columnIndex, blockSize) ? 3 : 1; - this.aboveLevelContext = new int[3][]; - this.aboveDcContext = new int[3][]; - this.leftLevelContext = new int[3][]; - this.leftDcContext = new int[3][]; - for (int plane = 0; plane < endPlane; plane++) - { - int subX = plane > 0 && subsamplingX ? 1 : 0; - int subY = plane > 0 && subsamplingY ? 1 : 0; - this.aboveLevelContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; - this.aboveDcContext[plane] = new int[(columnIndex + block4x4Width) >> subX]; - this.leftLevelContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; - this.leftDcContext[plane] = new int[(rowIndex + block4x4Height) >> subY]; - Array.Fill(this.aboveLevelContext[plane], 0); - Array.Fill(this.aboveDcContext[plane], 0); - Array.Fill(this.leftLevelContext[plane], 0); - Array.Fill(this.leftDcContext[plane], 0); - } + // TODO: Do we still need this method. } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs index b52473dbd9..312891c85e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; internal class Av1InverseQuantizer { - public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] level, int[] qCoefficients, Av1TransformMode txType, Av1TransformSize txSize, Av1Plane plane) + public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] level, int[] qCoefficients, Av1TransformType txType, Av1TransformSize txSize, Av1Plane plane) { Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(txSize, txType); short[] scanIndices = scanOrder.Scan; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs index a773b5479b..94c0219a45 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs index a014ad2245..d8ff42bdb6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs @@ -1,8 +1,6 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1ScanOrderConstants @@ -70,6 +68,6 @@ internal static class Av1ScanOrderConstants 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; - public static Av1ScanOrder GetScanOrder(Av1TransformSize txSize, Av1TransformMode txMode) - => ScanOrders[(int)txSize][(int)txMode]; + public static Av1ScanOrder GetScanOrder(Av1TransformSize transformSize, Av1TransformType transformType) + => ScanOrders[(int)transformSize][(int)transformType]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs new file mode 100644 index 0000000000..afc0354821 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformClass.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformClass +{ + Class2D = 0, + ClassHorizontal = 1, + ClassVertical = 2, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs new file mode 100644 index 0000000000..f25d1cbf5e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSetType.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformSetType +{ + /// + /// DCT only. + /// + DctOnly, + + /// + /// DCT + Identity only + /// + DctIdentity, + + /// + /// Discrete Trig transforms w/o flip (4) + Identity (1) + /// + Dtt4Identity, + + /// + /// Discrete Trig transforms w/o flip (4) + Identity (1) + 1D Hor/vert DCT (2) + /// + Dtt4Identity1dDct, + + /// + /// Discrete Trig transforms w/ flip (9) + Identity (1) + 1D Hor/Ver DCT (2) + /// + Dtt9Identity1dDct, + + /// + /// Discrete Trig transforms w/ flip (9) + Identity (1) + 1D Hor/Ver (6) + /// + All16 +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index a961be945c..53c27e8741 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -59,6 +59,82 @@ internal static class Av1TransformSizeExtensions Av1BlockSize.Block64x16, // TX_64X16 ]; + private static readonly Av1TransformSize[] SquareMap = [ + Av1TransformSize.Size4x4, // TX_4X4 + Av1TransformSize.Size8x8, // TX_8X8 + Av1TransformSize.Size16x16, // TX_16X16 + Av1TransformSize.Size32x32, // TX_32X32 + Av1TransformSize.Size64x64, // TX_64X64 + Av1TransformSize.Size4x4, // TX_4X8 + Av1TransformSize.Size4x4, // TX_8X4 + Av1TransformSize.Size8x8, // TX_8X16 + Av1TransformSize.Size8x8, // TX_16X8 + Av1TransformSize.Size16x16, // TX_16X32 + Av1TransformSize.Size16x16, // TX_32X16 + Av1TransformSize.Size32x32, // TX_32X64 + Av1TransformSize.Size32x32, // TX_64X32 + Av1TransformSize.Size4x4, // TX_4X16 + Av1TransformSize.Size4x4, // TX_16X4 + Av1TransformSize.Size8x8, // TX_8X32 + Av1TransformSize.Size8x8, // TX_32X8 + Av1TransformSize.Size16x16, // TX_16X64 + Av1TransformSize.Size16x16, // TX_64X16 + ]; + + private static readonly Av1TransformSize[] SquareUpMap = [ + Av1TransformSize.Size4x4, // TX_4X4 + Av1TransformSize.Size8x8, // TX_8X8 + Av1TransformSize.Size16x16, // TX_16X16 + Av1TransformSize.Size32x32, // TX_32X32 + Av1TransformSize.Size64x64, // TX_64X64 + Av1TransformSize.Size8x8, // TX_4X8 + Av1TransformSize.Size8x8, // TX_8X4 + Av1TransformSize.Size16x16, // TX_8X16 + Av1TransformSize.Size16x16, // TX_16X8 + Av1TransformSize.Size32x32, // TX_16X32 + Av1TransformSize.Size32x32, // TX_32X16 + Av1TransformSize.Size64x64, // TX_32X64 + Av1TransformSize.Size64x64, // TX_64X32 + Av1TransformSize.Size16x16, // TX_4X16 + Av1TransformSize.Size16x16, // TX_16X4 + Av1TransformSize.Size32x32, // TX_8X32 + Av1TransformSize.Size32x32, // TX_32X8 + Av1TransformSize.Size64x64, // TX_16X64 + Av1TransformSize.Size64x64, // TX_64X16 + ]; + + private static readonly int[] Log2Minus4 = [ + 0, // TX_4X4 + 2, // TX_8X8 + 4, // TX_16X16 + 6, // TX_32X32 + 6, // TX_64X64 + 1, // TX_4X8 + 1, // TX_8X4 + 3, // TX_8X16 + 3, // TX_16X8 + 5, // TX_16X32 + 5, // TX_32X16 + 6, // TX_32X64 + 6, // TX_64X32 + 2, // TX_4X16 + 2, // TX_16X4 + 4, // TX_8X32 + 4, // TX_32X8 + 5, // TX_16X64 + 5, // TX_64X16 + ]; + + // Transform block width in log2 + private static readonly int[] BlockWidthLog2 = [ + 2, 3, 4, 5, 6, 2, 3, 3, 4, 4, 5, 5, 6, 2, 4, 3, 5, 4, 6, + ]; + + // Transform block height in log2 + private static readonly int[] BlockHeightLog2 = [ + 2, 3, 4, 5, 6, 3, 2, 4, 3, 5, 4, 6, 5, 4, 2, 5, 3, 6, 4, + ]; + public static int GetScale(this Av1TransformSize size) { int pels = Size2d[(int)size]; @@ -79,5 +155,23 @@ public static int GetScale(this Av1TransformSize size) public static Av1TransformSize GetSubSize(this Av1TransformSize size) => SubTransformSize[(int)size]; - public static Av1BlockSize GetBlockSize(this Av1TransformSize transformSize) => (Av1BlockSize)BlockSize[(int)transformSize]; + public static Av1TransformSize GetSquareSize(this Av1TransformSize size) => SquareMap[(int)size]; + + public static Av1TransformSize GetSquareUpSize(this Av1TransformSize size) => SquareUpMap[(int)size]; + + public static Av1BlockSize ToBlockSize(this Av1TransformSize transformSize) => BlockSize[(int)transformSize]; + + public static int GetLog2Minus4(this Av1TransformSize size) => Log2Minus4[(int)size]; + + public static Av1TransformSize GetAdjusted(this Av1TransformSize size) => size switch + { + Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64 => Av1TransformSize.Size32x32, + Av1TransformSize.Size64x16 => Av1TransformSize.Size32x16, + Av1TransformSize.Size16x64 => Av1TransformSize.Size16x32, + _ => size + }; + + public static int GetBlockWidthLog2(this Av1TransformSize size) => BlockWidthLog2[(int)GetAdjusted(size)]; + + public static int GetBlockHeightLog2(this Av1TransformSize size) => BlockHeightLog2[(int)GetAdjusted(size)]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs new file mode 100644 index 0000000000..48c14ba2aa --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1TransformTypeExtensions +{ + private static readonly Av1TransformClass[] Type2Class = [ + Av1TransformClass.Class2D, // DCT_DCT + Av1TransformClass.Class2D, // ADST_DCT + Av1TransformClass.Class2D, // DCT_ADST + Av1TransformClass.Class2D, // ADST_ADST + Av1TransformClass.Class2D, // FLIPADST_DCT + Av1TransformClass.Class2D, // DCT_FLIPADST + Av1TransformClass.Class2D, // FLIPADST_FLIPADST + Av1TransformClass.Class2D, // ADST_FLIPADST + Av1TransformClass.Class2D, // FLIPADST_ADST + Av1TransformClass.Class2D, // IDTX + Av1TransformClass.ClassVertical, // V_DCT + Av1TransformClass.ClassHorizontal, // H_DCT + Av1TransformClass.ClassVertical, // V_ADST + Av1TransformClass.ClassHorizontal, // H_ADST + Av1TransformClass.ClassVertical, // V_FLIPADST + Av1TransformClass.ClassHorizontal, // H_FLIPADST + ]; + + private static readonly bool[][] ExtendedTransformUsed = [ + [true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false], + [true, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false], + [true, true, true, true, false, false, false, false, false, true, false, false, false, false, false, false], + [true, true, true, true, false, false, false, false, false, true, true, true, false, false, false, false], + [true, true, true, true, true, true, true, true, true, true, true, true, false, false, false, false], + [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true], + ]; + + public static Av1TransformClass ToClass(this Av1TransformType transformType) => Type2Class[(int)transformType]; + + public static bool IsExtendedSetUsed(this Av1TransformType transformType, Av1TransformSetType setType) + => ExtendedTransformUsed[(int)setType][(int)transformType]; +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 4a1fa85801..8e3147ec7a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -206,7 +206,7 @@ public void RoundTripPartitionType() using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan()); + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -234,7 +234,7 @@ public void RoundTripUseIntraBlockCopy() using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan()); + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { From c8a6203b2500302a2a895bb3c8bd203bd9a1d196 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 6 Jul 2024 13:33:25 +0200 Subject: [PATCH 103/190] Implement ScanOrder scan constants --- .../Heif/Av1/Transform/Av1ScanOrder.cs | 8 +- .../Av1/Transform/Av1ScanOrderConstants.cs | 1063 ++++++++++++++++- 2 files changed, 1029 insertions(+), 42 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs index 94c0219a45..2046fe95d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -12,11 +12,11 @@ public Av1ScanOrder(short[]? scan) this.Neighbors = []; } - public Av1ScanOrder(short[] scan, short[] iscan, short[] neighbors) + public Av1ScanOrder(short[]? scan, short[]? iscan, short[]? neighbors) { - this.Scan = scan; - this.IScan = iscan; - this.Neighbors = neighbors; + this.Scan = scan!; + this.IScan = iscan!; + this.Neighbors = neighbors!; } public short[] Scan { get; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs index d8ff42bdb6..229d2fca58 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs @@ -13,61 +13,1048 @@ internal static class Av1ScanOrderConstants // Transform size 4x4 [ - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(DefaultScan4x4), - new Av1ScanOrder(MatrixRowScan4x4), - new Av1ScanOrder(MatrixColumnScan4x4), - new Av1ScanOrder(MatrixRowScan4x4), - new Av1ScanOrder(MatrixColumnScan4x4), - new Av1ScanOrder(MatrixRowScan4x4), - new Av1ScanOrder(MatrixColumnScan4x4), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), ], // Transform size 8x8 [ - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(DefaultScan8x8), - new Av1ScanOrder(MatrixRowScan8x8), - new Av1ScanOrder(MatrixColumnScan8x8), - new Av1ScanOrder(MatrixRowScan8x8), - new Av1ScanOrder(MatrixColumnScan8x8), - new Av1ScanOrder(MatrixRowScan8x8), - new Av1ScanOrder(MatrixColumnScan8x8), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), ], + + // Transform size 16x16 + [ + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + ], + + // Transform size 32x32 + [ + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ + + // Transform size 64X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ + + // Transform size 4X8 + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + ], + [ + + // Transform size 8X4 + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + ], + [ + + // Transform size 8X16 + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + ], + [ + + // Transform size 16X8 + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + ], + [ + + // Transform size 16X32 + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + ], + [ + + // Transform size 32X16 + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + ], + [ + + // Transform size 32X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ + + // Transform size 64X32 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ + + // Transform size 4X16 + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + ], + [ + + // Transform size 16X4 + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + ], + [ + + // Transform size 8X32 + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + ], + [ + + // Transform size 32X8 + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + ], + [ + + // Transform size 16X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + ], + [ + + // Transform size 64X16 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + ] ]; private static readonly short[] DefaultScan4x4 = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; - private static readonly short[] MatrixColumnScan4x4 = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]; - private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - - private static readonly short[] DefaultScan8x8 = [0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, + private static readonly short[] DefaultScan8x8 = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]; - private static readonly short[] MatrixColumnScan8x8 = [0, 8, 16, 24, 32, 40, 48, 56, 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, + private static readonly short[] DefaultScan16x16 = [ + 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96, + 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, 113, 98, 83, 68, 53, 38, 23, + 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, + 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, + 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 224, 209, 194, 179, 164, + 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, + 195, 210, 225, 240, 241, 226, 211, 196, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77, + 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, 93, + 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, 185, 170, 155, 140, + 125, 110, 95, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127, + 143, 158, 173, 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, 220, 235, 250, 251, + 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, 223, 239, 254, 255]; + + private static readonly short[] DefaultScan32x32 = [ + 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160, + 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, 224, 256, 225, 194, 163, 132, 101, + 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103, + 72, 41, 10, 11, 42, 73, 104, 135, 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229, + 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, 199, 230, 261, 292, 323, 354, 385, 416, + 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170, + 201, 232, 263, 294, 325, 356, 387, 418, 449, 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202, + 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, 451, + 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, + 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608, + 640, 609, 578, 547, 516, 485, 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, 20, + 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, 486, 517, 548, 579, 610, 641, + 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, + 84, 53, 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550, + 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, 613, 582, 551, 520, 489, 458, 427, 396, 365, 334, + 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, 56, 87, 118, 149, 180, 211, 242, 273, 304, 335, + 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677, + 646, 615, 584, 553, 522, 491, 460, 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, + 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, 368, 399, 430, 461, 492, 523, 554, 585, 616, 647, + 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493, + 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184, + 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, 556, 587, 618, 649, 680, 711, 742, 773, 804, 835, + 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, 433, + 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248, + 279, 310, 341, 372, 403, 434, 465, 496, 527, 558, 589, 620, 651, 682, 713, 744, 775, 806, 837, 868, 899, + 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, 590, 559, 528, 497, 466, + 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312, + 343, 374, 405, 436, 467, 498, 529, 560, 591, 622, 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963, + 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, 685, 654, 623, 592, 561, 530, 499, 468, 437, 406, + 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, 469, 500, + 531, 562, 593, 624, 655, 686, 717, 748, 779, 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873, + 842, 811, 780, 749, 718, 687, 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, + 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, 626, 657, 688, 719, 750, 781, 812, + 843, 874, 905, 936, 967, 998, 999, 968, 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565, + 534, 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, 535, 566, 597, + 628, 659, 690, 721, 752, 783, 814, 845, 876, 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784, + 753, 722, 691, 660, 629, 598, 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, 382, 413, 444, 475, 506, + 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, 1003, 972, 941, 910, 879, + 848, 817, 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539, + 570, 601, 632, 663, 694, 725, 756, 787, 818, 849, 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850, + 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, 510, 541, 572, 603, 634, 665, 696, + 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697, + 666, 635, 604, 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977, + 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, 730, 699, 668, 637, 606, 575, 607, 638, 669, 700, 731, + 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, 763, 732, 701, 670, + 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796, + 765, 734, 703, 735, 766, 797, 828, 859, 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798, + 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, 986, 955, 924, 893, 862, 831, 863, 894, 925, 956, 987, + 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023]; + + private static readonly short[] DefaultScan4x8 = [ + 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14, + 17, 20, 15, 18, 21, 24, 19, 22, 25, 28, 23, 26, 29, 27, 30, 31,]; + + private static readonly short[] DefaultScan8x4 = [ + 0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 25, 18, 11, 4, 26, 19, + 12, 5, 27, 20, 13, 6, 28, 21, 14, 7, 29, 22, 15, 30, 23, 31,]; + + private static readonly short[] DefaultScan8x16 = [ + 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6, + 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64, + 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74, + 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84, + 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 87, 94, 101, + 108, 115, 122, 95, 102, 109, 116, 123, 103, 110, 117, 124, 111, 118, 125, 119, 126, 127,]; + + private static readonly short[] DefaultScan16x8 = [ + 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 64, 49, 34, 19, 4, 80, 65, 50, 35, 20, 5, 96, + 81, 66, 51, 36, 21, 6, 112, 97, 82, 67, 52, 37, 22, 7, 113, 98, 83, 68, 53, 38, 23, 8, + 114, 99, 84, 69, 54, 39, 24, 9, 115, 100, 85, 70, 55, 40, 25, 10, 116, 101, 86, 71, 56, 41, + 26, 11, 117, 102, 87, 72, 57, 42, 27, 12, 118, 103, 88, 73, 58, 43, 28, 13, 119, 104, 89, 74, + 59, 44, 29, 14, 120, 105, 90, 75, 60, 45, 30, 15, 121, 106, 91, 76, 61, 46, 31, 122, 107, 92, + 77, 62, 47, 123, 108, 93, 78, 63, 124, 109, 94, 79, 125, 110, 95, 126, 111, 127,]; + + private static readonly short[] DefaultScan16x32 = [ + 0, 1, 16, 2, 17, 32, 3, 18, 33, 48, 4, 19, 34, 49, 64, 5, 20, 35, 50, 65, 80, 6, 21, + 36, 51, 66, 81, 96, 7, 22, 37, 52, 67, 82, 97, 112, 8, 23, 38, 53, 68, 83, 98, 113, 128, 9, + 24, 39, 54, 69, 84, 99, 114, 129, 144, 10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 11, 26, 41, + 56, 71, 86, 101, 116, 131, 146, 161, 176, 12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192, 13, + 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 14, 29, 44, 59, 74, 89, 104, 119, 134, 149, + 164, 179, 194, 209, 224, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 31, 46, + 61, 76, 91, 106, 121, 136, 151, 166, 181, 196, 211, 226, 241, 256, 47, 62, 77, 92, 107, 122, 137, 152, 167, + 182, 197, 212, 227, 242, 257, 272, 63, 78, 93, 108, 123, 138, 153, 168, 183, 198, 213, 228, 243, 258, 273, 288, + 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 259, 274, 289, 304, 95, 110, 125, 140, 155, 170, 185, + 200, 215, 230, 245, 260, 275, 290, 305, 320, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 261, 276, 291, 306, + 321, 336, 127, 142, 157, 172, 187, 202, 217, 232, 247, 262, 277, 292, 307, 322, 337, 352, 143, 158, 173, 188, 203, + 218, 233, 248, 263, 278, 293, 308, 323, 338, 353, 368, 159, 174, 189, 204, 219, 234, 249, 264, 279, 294, 309, 324, + 339, 354, 369, 384, 175, 190, 205, 220, 235, 250, 265, 280, 295, 310, 325, 340, 355, 370, 385, 400, 191, 206, 221, + 236, 251, 266, 281, 296, 311, 326, 341, 356, 371, 386, 401, 416, 207, 222, 237, 252, 267, 282, 297, 312, 327, 342, + 357, 372, 387, 402, 417, 432, 223, 238, 253, 268, 283, 298, 313, 328, 343, 358, 373, 388, 403, 418, 433, 448, 239, + 254, 269, 284, 299, 314, 329, 344, 359, 374, 389, 404, 419, 434, 449, 464, 255, 270, 285, 300, 315, 330, 345, 360, + 375, 390, 405, 420, 435, 450, 465, 480, 271, 286, 301, 316, 331, 346, 361, 376, 391, 406, 421, 436, 451, 466, 481, + 496, 287, 302, 317, 332, 347, 362, 377, 392, 407, 422, 437, 452, 467, 482, 497, 303, 318, 333, 348, 363, 378, 393, + 408, 423, 438, 453, 468, 483, 498, 319, 334, 349, 364, 379, 394, 409, 424, 439, 454, 469, 484, 499, 335, 350, 365, + 380, 395, 410, 425, 440, 455, 470, 485, 500, 351, 366, 381, 396, 411, 426, 441, 456, 471, 486, 501, 367, 382, 397, + 412, 427, 442, 457, 472, 487, 502, 383, 398, 413, 428, 443, 458, 473, 488, 503, 399, 414, 429, 444, 459, 474, 489, + 504, 415, 430, 445, 460, 475, 490, 505, 431, 446, 461, 476, 491, 506, 447, 462, 477, 492, 507, 463, 478, 493, 508, + 479, 494, 509, 495, 510, 511,]; + + private static readonly short[] DefaultScan32x16 = [ + 0, 32, 1, 64, 33, 2, 96, 65, 34, 3, 128, 97, 66, 35, 4, 160, 129, 98, 67, 36, 5, 192, 161, + 130, 99, 68, 37, 6, 224, 193, 162, 131, 100, 69, 38, 7, 256, 225, 194, 163, 132, 101, 70, 39, 8, 288, + 257, 226, 195, 164, 133, 102, 71, 40, 9, 320, 289, 258, 227, 196, 165, 134, 103, 72, 41, 10, 352, 321, 290, + 259, 228, 197, 166, 135, 104, 73, 42, 11, 384, 353, 322, 291, 260, 229, 198, 167, 136, 105, 74, 43, 12, 416, + 385, 354, 323, 292, 261, 230, 199, 168, 137, 106, 75, 44, 13, 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, + 138, 107, 76, 45, 14, 480, 449, 418, 387, 356, 325, 294, 263, 232, 201, 170, 139, 108, 77, 46, 15, 481, 450, + 419, 388, 357, 326, 295, 264, 233, 202, 171, 140, 109, 78, 47, 16, 482, 451, 420, 389, 358, 327, 296, 265, 234, + 203, 172, 141, 110, 79, 48, 17, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, 18, + 484, 453, 422, 391, 360, 329, 298, 267, 236, 205, 174, 143, 112, 81, 50, 19, 485, 454, 423, 392, 361, 330, 299, + 268, 237, 206, 175, 144, 113, 82, 51, 20, 486, 455, 424, 393, 362, 331, 300, 269, 238, 207, 176, 145, 114, 83, + 52, 21, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, 84, 53, 22, 488, 457, 426, 395, 364, + 333, 302, 271, 240, 209, 178, 147, 116, 85, 54, 23, 489, 458, 427, 396, 365, 334, 303, 272, 241, 210, 179, 148, + 117, 86, 55, 24, 490, 459, 428, 397, 366, 335, 304, 273, 242, 211, 180, 149, 118, 87, 56, 25, 491, 460, 429, + 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, 492, 461, 430, 399, 368, 337, 306, 275, 244, 213, + 182, 151, 120, 89, 58, 27, 493, 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 494, + 463, 432, 401, 370, 339, 308, 277, 246, 215, 184, 153, 122, 91, 60, 29, 495, 464, 433, 402, 371, 340, 309, 278, + 247, 216, 185, 154, 123, 92, 61, 30, 496, 465, 434, 403, 372, 341, 310, 279, 248, 217, 186, 155, 124, 93, 62, + 31, 497, 466, 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 498, 467, 436, 405, 374, 343, 312, + 281, 250, 219, 188, 157, 126, 95, 499, 468, 437, 406, 375, 344, 313, 282, 251, 220, 189, 158, 127, 500, 469, 438, + 407, 376, 345, 314, 283, 252, 221, 190, 159, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, 191, 502, 471, 440, + 409, 378, 347, 316, 285, 254, 223, 503, 472, 441, 410, 379, 348, 317, 286, 255, 504, 473, 442, 411, 380, 349, 318, + 287, 505, 474, 443, 412, 381, 350, 319, 506, 475, 444, 413, 382, 351, 507, 476, 445, 414, 383, 508, 477, 446, 415, + 509, 478, 447, 510, 479, 511,]; + + private static readonly short[] DefaultScan4x16 = [ + 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14, 17, 20, 15, 18, 21, 24, + 19, 22, 25, 28, 23, 26, 29, 32, 27, 30, 33, 36, 31, 34, 37, 40, 35, 38, 41, 44, 39, 42, + 45, 48, 43, 46, 49, 52, 47, 50, 53, 56, 51, 54, 57, 60, 55, 58, 61, 59, 62, 63,]; + + private static readonly short[] DefaultScan16x4 = [ + 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 49, 34, 19, 4, 50, 35, 20, 5, 51, 36, 21, 6, + 52, 37, 22, 7, 53, 38, 23, 8, 54, 39, 24, 9, 55, 40, 25, 10, 56, 41, 26, 11, 57, 42, + 27, 12, 58, 43, 28, 13, 59, 44, 29, 14, 60, 45, 30, 15, 61, 46, 31, 62, 47, 63,]; + + private static readonly short[] DefaultScan8x32 = [ + 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6, + 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64, + 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74, + 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84, + 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 128, 87, 94, + 101, 108, 115, 122, 129, 136, 95, 102, 109, 116, 123, 130, 137, 144, 103, 110, 117, 124, 131, 138, 145, 152, + 111, 118, 125, 132, 139, 146, 153, 160, 119, 126, 133, 140, 147, 154, 161, 168, 127, 134, 141, 148, 155, 162, + 169, 176, 135, 142, 149, 156, 163, 170, 177, 184, 143, 150, 157, 164, 171, 178, 185, 192, 151, 158, 165, 172, + 179, 186, 193, 200, 159, 166, 173, 180, 187, 194, 201, 208, 167, 174, 181, 188, 195, 202, 209, 216, 175, 182, + 189, 196, 203, 210, 217, 224, 183, 190, 197, 204, 211, 218, 225, 232, 191, 198, 205, 212, 219, 226, 233, 240, + 199, 206, 213, 220, 227, 234, 241, 248, 207, 214, 221, 228, 235, 242, 249, 215, 222, 229, 236, 243, 250, 223, + 230, 237, 244, 251, 231, 238, 245, 252, 239, 246, 253, 247, 254, 255,]; + + private static readonly short[] DefaultScan32x8 = [ + 0, 32, 1, 64, 33, 2, 96, 65, 34, 3, 128, 97, 66, 35, 4, 160, 129, 98, 67, 36, 5, 192, + 161, 130, 99, 68, 37, 6, 224, 193, 162, 131, 100, 69, 38, 7, 225, 194, 163, 132, 101, 70, 39, 8, + 226, 195, 164, 133, 102, 71, 40, 9, 227, 196, 165, 134, 103, 72, 41, 10, 228, 197, 166, 135, 104, 73, + 42, 11, 229, 198, 167, 136, 105, 74, 43, 12, 230, 199, 168, 137, 106, 75, 44, 13, 231, 200, 169, 138, + 107, 76, 45, 14, 232, 201, 170, 139, 108, 77, 46, 15, 233, 202, 171, 140, 109, 78, 47, 16, 234, 203, + 172, 141, 110, 79, 48, 17, 235, 204, 173, 142, 111, 80, 49, 18, 236, 205, 174, 143, 112, 81, 50, 19, + 237, 206, 175, 144, 113, 82, 51, 20, 238, 207, 176, 145, 114, 83, 52, 21, 239, 208, 177, 146, 115, 84, + 53, 22, 240, 209, 178, 147, 116, 85, 54, 23, 241, 210, 179, 148, 117, 86, 55, 24, 242, 211, 180, 149, + 118, 87, 56, 25, 243, 212, 181, 150, 119, 88, 57, 26, 244, 213, 182, 151, 120, 89, 58, 27, 245, 214, + 183, 152, 121, 90, 59, 28, 246, 215, 184, 153, 122, 91, 60, 29, 247, 216, 185, 154, 123, 92, 61, 30, + 248, 217, 186, 155, 124, 93, 62, 31, 249, 218, 187, 156, 125, 94, 63, 250, 219, 188, 157, 126, 95, 251, + 220, 189, 158, 127, 252, 221, 190, 159, 253, 222, 191, 254, 223, 255,]; + + private static readonly short[] MatrixColumnScan4x4 = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15]; + private static readonly short[] MatrixColumnScan8x8 = [ + 0, 8, 16, 24, 32, 40, 48, 56, 1, 9, 17, 25, 33, 41, 49, 57, 2, 10, 18, 26, 34, 42, 50, 58, 3, 11, 19, 27, 35, 43, 51, 59, 4, 12, 20, 28, 36, 44, 52, 60, 5, 13, 21, 29, 37, 45, 53, 61, 6, 14, 22, 30, 38, 46, 54, 62, 7, 15, 23, 31, 39, 47, 55, 63]; - private static readonly short[] MatrixRowScan8x8 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + private static readonly short[] MatrixColumnScan16x16 = [ + 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 1, 17, 33, 49, 65, 81, + 97, 113, 129, 145, 161, 177, 193, 209, 225, 241, 2, 18, 34, 50, 66, 82, 98, 114, 130, 146, 162, 178, + 194, 210, 226, 242, 3, 19, 35, 51, 67, 83, 99, 115, 131, 147, 163, 179, 195, 211, 227, 243, 4, 20, + 36, 52, 68, 84, 100, 116, 132, 148, 164, 180, 196, 212, 228, 244, 5, 21, 37, 53, 69, 85, 101, 117, + 133, 149, 165, 181, 197, 213, 229, 245, 6, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, + 230, 246, 7, 23, 39, 55, 71, 87, 103, 119, 135, 151, 167, 183, 199, 215, 231, 247, 8, 24, 40, 56, + 72, 88, 104, 120, 136, 152, 168, 184, 200, 216, 232, 248, 9, 25, 41, 57, 73, 89, 105, 121, 137, 153, + 169, 185, 201, 217, 233, 249, 10, 26, 42, 58, 74, 90, 106, 122, 138, 154, 170, 186, 202, 218, 234, 250, + 11, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 203, 219, 235, 251, 12, 28, 44, 60, 76, 92, + 108, 124, 140, 156, 172, 188, 204, 220, 236, 252, 13, 29, 45, 61, 77, 93, 109, 125, 141, 157, 173, 189, + 205, 221, 237, 253, 14, 30, 46, 62, 78, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 254, 15, 31, + 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255,]; + + private static readonly short[] MatrixColumnScan32x32 = [ + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, + 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1, 33, 65, 97, 129, 161, 193, 225, + 257, 289, 321, 353, 385, 417, 449, 481, 513, 545, 577, 609, 641, 673, 705, 737, 769, 801, 833, 865, + 897, 929, 961, 993, 2, 34, 66, 98, 130, 162, 194, 226, 258, 290, 322, 354, 386, 418, 450, 482, + 514, 546, 578, 610, 642, 674, 706, 738, 770, 802, 834, 866, 898, 930, 962, 994, 3, 35, 67, 99, + 131, 163, 195, 227, 259, 291, 323, 355, 387, 419, 451, 483, 515, 547, 579, 611, 643, 675, 707, 739, + 771, 803, 835, 867, 899, 931, 963, 995, 4, 36, 68, 100, 132, 164, 196, 228, 260, 292, 324, 356, + 388, 420, 452, 484, 516, 548, 580, 612, 644, 676, 708, 740, 772, 804, 836, 868, 900, 932, 964, 996, + 5, 37, 69, 101, 133, 165, 197, 229, 261, 293, 325, 357, 389, 421, 453, 485, 517, 549, 581, 613, + 645, 677, 709, 741, 773, 805, 837, 869, 901, 933, 965, 997, 6, 38, 70, 102, 134, 166, 198, 230, + 262, 294, 326, 358, 390, 422, 454, 486, 518, 550, 582, 614, 646, 678, 710, 742, 774, 806, 838, 870, + 902, 934, 966, 998, 7, 39, 71, 103, 135, 167, 199, 231, 263, 295, 327, 359, 391, 423, 455, 487, + 519, 551, 583, 615, 647, 679, 711, 743, 775, 807, 839, 871, 903, 935, 967, 999, 8, 40, 72, 104, + 136, 168, 200, 232, 264, 296, 328, 360, 392, 424, 456, 488, 520, 552, 584, 616, 648, 680, 712, 744, + 776, 808, 840, 872, 904, 936, 968, 1000, 9, 41, 73, 105, 137, 169, 201, 233, 265, 297, 329, 361, + 393, 425, 457, 489, 521, 553, 585, 617, 649, 681, 713, 745, 777, 809, 841, 873, 905, 937, 969, 1001, + 10, 42, 74, 106, 138, 170, 202, 234, 266, 298, 330, 362, 394, 426, 458, 490, 522, 554, 586, 618, + 650, 682, 714, 746, 778, 810, 842, 874, 906, 938, 970, 1002, 11, 43, 75, 107, 139, 171, 203, 235, + 267, 299, 331, 363, 395, 427, 459, 491, 523, 555, 587, 619, 651, 683, 715, 747, 779, 811, 843, 875, + 907, 939, 971, 1003, 12, 44, 76, 108, 140, 172, 204, 236, 268, 300, 332, 364, 396, 428, 460, 492, + 524, 556, 588, 620, 652, 684, 716, 748, 780, 812, 844, 876, 908, 940, 972, 1004, 13, 45, 77, 109, + 141, 173, 205, 237, 269, 301, 333, 365, 397, 429, 461, 493, 525, 557, 589, 621, 653, 685, 717, 749, + 781, 813, 845, 877, 909, 941, 973, 1005, 14, 46, 78, 110, 142, 174, 206, 238, 270, 302, 334, 366, + 398, 430, 462, 494, 526, 558, 590, 622, 654, 686, 718, 750, 782, 814, 846, 878, 910, 942, 974, 1006, + 15, 47, 79, 111, 143, 175, 207, 239, 271, 303, 335, 367, 399, 431, 463, 495, 527, 559, 591, 623, + 655, 687, 719, 751, 783, 815, 847, 879, 911, 943, 975, 1007, 16, 48, 80, 112, 144, 176, 208, 240, + 272, 304, 336, 368, 400, 432, 464, 496, 528, 560, 592, 624, 656, 688, 720, 752, 784, 816, 848, 880, + 912, 944, 976, 1008, 17, 49, 81, 113, 145, 177, 209, 241, 273, 305, 337, 369, 401, 433, 465, 497, + 529, 561, 593, 625, 657, 689, 721, 753, 785, 817, 849, 881, 913, 945, 977, 1009, 18, 50, 82, 114, + 146, 178, 210, 242, 274, 306, 338, 370, 402, 434, 466, 498, 530, 562, 594, 626, 658, 690, 722, 754, + 786, 818, 850, 882, 914, 946, 978, 1010, 19, 51, 83, 115, 147, 179, 211, 243, 275, 307, 339, 371, + 403, 435, 467, 499, 531, 563, 595, 627, 659, 691, 723, 755, 787, 819, 851, 883, 915, 947, 979, 1011, + 20, 52, 84, 116, 148, 180, 212, 244, 276, 308, 340, 372, 404, 436, 468, 500, 532, 564, 596, 628, + 660, 692, 724, 756, 788, 820, 852, 884, 916, 948, 980, 1012, 21, 53, 85, 117, 149, 181, 213, 245, + 277, 309, 341, 373, 405, 437, 469, 501, 533, 565, 597, 629, 661, 693, 725, 757, 789, 821, 853, 885, + 917, 949, 981, 1013, 22, 54, 86, 118, 150, 182, 214, 246, 278, 310, 342, 374, 406, 438, 470, 502, + 534, 566, 598, 630, 662, 694, 726, 758, 790, 822, 854, 886, 918, 950, 982, 1014, 23, 55, 87, 119, + 151, 183, 215, 247, 279, 311, 343, 375, 407, 439, 471, 503, 535, 567, 599, 631, 663, 695, 727, 759, + 791, 823, 855, 887, 919, 951, 983, 1015, 24, 56, 88, 120, 152, 184, 216, 248, 280, 312, 344, 376, + 408, 440, 472, 504, 536, 568, 600, 632, 664, 696, 728, 760, 792, 824, 856, 888, 920, 952, 984, 1016, + 25, 57, 89, 121, 153, 185, 217, 249, 281, 313, 345, 377, 409, 441, 473, 505, 537, 569, 601, 633, + 665, 697, 729, 761, 793, 825, 857, 889, 921, 953, 985, 1017, 26, 58, 90, 122, 154, 186, 218, 250, + 282, 314, 346, 378, 410, 442, 474, 506, 538, 570, 602, 634, 666, 698, 730, 762, 794, 826, 858, 890, + 922, 954, 986, 1018, 27, 59, 91, 123, 155, 187, 219, 251, 283, 315, 347, 379, 411, 443, 475, 507, + 539, 571, 603, 635, 667, 699, 731, 763, 795, 827, 859, 891, 923, 955, 987, 1019, 28, 60, 92, 124, + 156, 188, 220, 252, 284, 316, 348, 380, 412, 444, 476, 508, 540, 572, 604, 636, 668, 700, 732, 764, + 796, 828, 860, 892, 924, 956, 988, 1020, 29, 61, 93, 125, 157, 189, 221, 253, 285, 317, 349, 381, + 413, 445, 477, 509, 541, 573, 605, 637, 669, 701, 733, 765, 797, 829, 861, 893, 925, 957, 989, 1021, + 30, 62, 94, 126, 158, 190, 222, 254, 286, 318, 350, 382, 414, 446, 478, 510, 542, 574, 606, 638, + 670, 702, 734, 766, 798, 830, 862, 894, 926, 958, 990, 1022, 31, 63, 95, 127, 159, 191, 223, 255, + 287, 319, 351, 383, 415, 447, 479, 511, 543, 575, 607, 639, 671, 703, 735, 767, 799, 831, 863, 895, + 927, 959, 991, 1023,]; + + private static readonly short[] MatrixColumnScan4x8 = [ + 0, 4, 8, 12, 16, 20, 24, 28, 1, 5, 9, 13, 17, 21, 25, 29, + 2, 6, 10, 14, 18, 22, 26, 30, 3, 7, 11, 15, 19, 23, 27, 31,]; + + private static readonly short[] MatrixColumnScan8x4 = [ + 0, 8, 16, 24, 1, 9, 17, 25, 2, 10, 18, 26, 3, 11, 19, 27, + 4, 12, 20, 28, 5, 13, 21, 29, 6, 14, 22, 30, 7, 15, 23, 31,]; + + private static readonly short[] MatrixColumnScan8x16 = [ + 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 1, 9, 17, 25, 33, 41, + 49, 57, 65, 73, 81, 89, 97, 105, 113, 121, 2, 10, 18, 26, 34, 42, 50, 58, 66, 74, 82, 90, + 98, 106, 114, 122, 3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91, 99, 107, 115, 123, 4, 12, + 20, 28, 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 5, 13, 21, 29, 37, 45, 53, 61, + 69, 77, 85, 93, 101, 109, 117, 125, 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, + 118, 126, 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127,]; + + private static readonly short[] MatrixColumnScan16x8 = [ + 0, 16, 32, 48, 64, 80, 96, 112, 1, 17, 33, 49, 65, 81, 97, 113, 2, 18, 34, 50, 66, 82, + 98, 114, 3, 19, 35, 51, 67, 83, 99, 115, 4, 20, 36, 52, 68, 84, 100, 116, 5, 21, 37, 53, + 69, 85, 101, 117, 6, 22, 38, 54, 70, 86, 102, 118, 7, 23, 39, 55, 71, 87, 103, 119, 8, 24, + 40, 56, 72, 88, 104, 120, 9, 25, 41, 57, 73, 89, 105, 121, 10, 26, 42, 58, 74, 90, 106, 122, + 11, 27, 43, 59, 75, 91, 107, 123, 12, 28, 44, 60, 76, 92, 108, 124, 13, 29, 45, 61, 77, 93, + 109, 125, 14, 30, 46, 62, 78, 94, 110, 126, 15, 31, 47, 63, 79, 95, 111, 127,]; + + private static readonly short[] MatrixColumnScan16x32 = [ + 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, + 368, 384, 400, 416, 432, 448, 464, 480, 496, 1, 17, 33, 49, 65, 81, 97, 113, 129, 145, 161, 177, 193, 209, + 225, 241, 257, 273, 289, 305, 321, 337, 353, 369, 385, 401, 417, 433, 449, 465, 481, 497, 2, 18, 34, 50, 66, + 82, 98, 114, 130, 146, 162, 178, 194, 210, 226, 242, 258, 274, 290, 306, 322, 338, 354, 370, 386, 402, 418, 434, + 450, 466, 482, 498, 3, 19, 35, 51, 67, 83, 99, 115, 131, 147, 163, 179, 195, 211, 227, 243, 259, 275, 291, + 307, 323, 339, 355, 371, 387, 403, 419, 435, 451, 467, 483, 499, 4, 20, 36, 52, 68, 84, 100, 116, 132, 148, + 164, 180, 196, 212, 228, 244, 260, 276, 292, 308, 324, 340, 356, 372, 388, 404, 420, 436, 452, 468, 484, 500, 5, + 21, 37, 53, 69, 85, 101, 117, 133, 149, 165, 181, 197, 213, 229, 245, 261, 277, 293, 309, 325, 341, 357, 373, + 389, 405, 421, 437, 453, 469, 485, 501, 6, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230, + 246, 262, 278, 294, 310, 326, 342, 358, 374, 390, 406, 422, 438, 454, 470, 486, 502, 7, 23, 39, 55, 71, 87, + 103, 119, 135, 151, 167, 183, 199, 215, 231, 247, 263, 279, 295, 311, 327, 343, 359, 375, 391, 407, 423, 439, 455, + 471, 487, 503, 8, 24, 40, 56, 72, 88, 104, 120, 136, 152, 168, 184, 200, 216, 232, 248, 264, 280, 296, 312, + 328, 344, 360, 376, 392, 408, 424, 440, 456, 472, 488, 504, 9, 25, 41, 57, 73, 89, 105, 121, 137, 153, 169, + 185, 201, 217, 233, 249, 265, 281, 297, 313, 329, 345, 361, 377, 393, 409, 425, 441, 457, 473, 489, 505, 10, 26, + 42, 58, 74, 90, 106, 122, 138, 154, 170, 186, 202, 218, 234, 250, 266, 282, 298, 314, 330, 346, 362, 378, 394, + 410, 426, 442, 458, 474, 490, 506, 11, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 203, 219, 235, 251, + 267, 283, 299, 315, 331, 347, 363, 379, 395, 411, 427, 443, 459, 475, 491, 507, 12, 28, 44, 60, 76, 92, 108, + 124, 140, 156, 172, 188, 204, 220, 236, 252, 268, 284, 300, 316, 332, 348, 364, 380, 396, 412, 428, 444, 460, 476, + 492, 508, 13, 29, 45, 61, 77, 93, 109, 125, 141, 157, 173, 189, 205, 221, 237, 253, 269, 285, 301, 317, 333, + 349, 365, 381, 397, 413, 429, 445, 461, 477, 493, 509, 14, 30, 46, 62, 78, 94, 110, 126, 142, 158, 174, 190, + 206, 222, 238, 254, 270, 286, 302, 318, 334, 350, 366, 382, 398, 414, 430, 446, 462, 478, 494, 510, 15, 31, 47, + 63, 79, 95, 111, 127, 143, 159,]; + + private static readonly short[] MatrixColumnScan32x16 = [ + 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 1, 33, 65, 97, 129, 161, 193, + 225, 257, 289, 321, 353, 385, 417, 449, 481, 2, 34, 66, 98, 130, 162, 194, 226, 258, 290, 322, 354, 386, 418, + 450, 482, 3, 35, 67, 99, 131, 163, 195, 227, 259, 291, 323, 355, 387, 419, 451, 483, 4, 36, 68, 100, 132, + 164, 196, 228, 260, 292, 324, 356, 388, 420, 452, 484, 5, 37, 69, 101, 133, 165, 197, 229, 261, 293, 325, 357, + 389, 421, 453, 485, 6, 38, 70, 102, 134, 166, 198, 230, 262, 294, 326, 358, 390, 422, 454, 486, 7, 39, 71, + 103, 135, 167, 199, 231, 263, 295, 327, 359, 391, 423, 455, 487, 8, 40, 72, 104, 136, 168, 200, 232, 264, 296, + 328, 360, 392, 424, 456, 488, 9, 41, 73, 105, 137, 169, 201, 233, 265, 297, 329, 361, 393, 425, 457, 489, 10, + 42, 74, 106, 138, 170, 202, 234, 266, 298, 330, 362, 394, 426, 458, 490, 11, 43, 75, 107, 139, 171, 203, 235, + 267, 299, 331, 363, 395, 427, 459, 491, 12, 44, 76, 108, 140, 172, 204, 236, 268, 300, 332, 364, 396, 428, 460, + 492, 13, 45, 77, 109, 141, 173, 205, 237, 269, 301, 333, 365, 397, 429, 461, 493, 14, 46, 78, 110, 142, 174, + 206, 238, 270, 302, 334, 366, 398, 430, 462, 494, 15, 47, 79, 111, 143, 175, 207, 239, 271, 303, 335, 367, 399, + 431, 463, 495, 16, 48, 80, 112, 144, 176, 208, 240, 272, 304, 336, 368, 400, 432, 464, 496, 17, 49, 81, 113, + 145, 177, 209, 241, 273, 305, 337, 369, 401, 433, 465, 497, 18, 50, 82, 114, 146, 178, 210, 242, 274, 306, 338, + 370, 402, 434, 466, 498, 19, 51, 83, 115, 147, 179, 211, 243, 275, 307, 339, 371, 403, 435, 467, 499, 20, 52, + 84, 116, 148, 180, 212, 244, 276, 308, 340, 372, 404, 436, 468, 500, 21, 53, 85, 117, 149, 181, 213, 245, 277, + 309, 341, 373, 405, 437, 469, 501, 22, 54, 86, 118, 150, 182, 214, 246, 278, 310, 342, 374, 406, 438, 470, 502, + 23, 55, 87, 119, 151, 183, 215, 247, 279, 311, 343, 375, 407, 439, 471, 503, 24, 56, 88, 120, 152, 184, 216, + 248, 280, 312, 344, 376, 408, 440, 472, 504, 25, 57, 89, 121, 153, 185, 217, 249, 281, 313, 345, 377, 409, 441, + 473, 505, 26, 58, 90, 122, 154, 186, 218, 250, 282, 314, 346, 378, 410, 442, 474, 506, 27, 59, 91, 123, 155, + 187, 219, 251, 283, 315, 347, 379, 411, 443, 475, 507, 28, 60, 92, 124, 156, 188, 220, 252, 284, 316, 348, 380, + 412, 444, 476, 508, 29, 61, 93, 125, 157, 189, 221, 253, 285, 317, 349, 381, 413, 445, 477, 509, 30, 62, 94, + 126, 158, 190, 222, 254, 286, 318, 350, 382, 414, 446, 478, 510, 31, 63, 95, 127, 159, 191, 223, 255, 287, 319, + 351, 383, 415, 447, 479, 511,]; + + private static readonly short[] MatrixColumnScan4x16 = [ + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 1, 5, 9, 13, 17, 21, + 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, + 50, 54, 58, 62, 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63,]; + + private static readonly short[] MatrixColumnScan16x4 = [ + 0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51, 4, 20, 36, 52, 5, 21, + 37, 53, 6, 22, 38, 54, 7, 23, 39, 55, 8, 24, 40, 56, 9, 25, 41, 57, 10, 26, 42, 58, + 11, 27, 43, 59, 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, 62, 15, 31, 47, 63,]; + + private static readonly short[] MatrixColumnScan8x32 = [ + 0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, 168, + 176, 184, 192, 200, 208, 216, 224, 232, 240, 248, 1, 9, 17, 25, 33, 41, 49, 57, 65, 73, 81, 89, + 97, 105, 113, 121, 129, 137, 145, 153, 161, 169, 177, 185, 193, 201, 209, 217, 225, 233, 241, 249, 2, 10, + 18, 26, 34, 42, 50, 58, 66, 74, 82, 90, 98, 106, 114, 122, 130, 138, 146, 154, 162, 170, 178, 186, + 194, 202, 210, 218, 226, 234, 242, 250, 3, 11, 19, 27, 35, 43, 51, 59, 67, 75, 83, 91, 99, 107, + 115, 123, 131, 139, 147, 155, 163, 171, 179, 187, 195, 203, 211, 219, 227, 235, 243, 251, 4, 12, 20, 28, + 36, 44, 52, 60, 68, 76, 84, 92, 100, 108, 116, 124, 132, 140, 148, 156, 164, 172, 180, 188, 196, 204, + 212, 220, 228, 236, 244, 252, 5, 13, 21, 29, 37, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, + 133, 141, 149, 157, 165, 173, 181, 189, 197, 205, 213, 221, 229, 237, 245, 253, 6, 14, 22, 30, 38, 46, + 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 142, 150, 158, 166, 174, 182, 190, 198, 206, 214, 222, + 230, 238, 246, 254, 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143, + 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, 247, 255,]; + + private static readonly short[] MatrixColumnScan32x8 = [ + 0, 32, 64, 96, 128, 160, 192, 224, 1, 33, 65, 97, 129, 161, 193, 225, 2, 34, 66, 98, 130, 162, 194, 226, + 3, 35, 67, 99, 131, 163, 195, 227, 4, 36, 68, 100, 132, 164, 196, 228, 5, 37, 69, 101, 133, 165, 197, 229, + 6, 38, 70, 102, 134, 166, 198, 230, 7, 39, 71, 103, 135, 167, 199, 231, 8, 40, 72, 104, 136, 168, 200, 232, + 9, 41, 73, 105, 137, 169, 201, 233, 10, 42, 74, 106, 138, 170, 202, 234, 11, 43, 75, 107, 139, 171, 203, 235, + 12, 44, 76, 108, 140, 172, 204, 236, 13, 45, 77, 109, 141, 173, 205, 237, 14, 46, 78, 110, 142, 174, 206, 238, + 15, 47, 79, 111, 143, 175, 207, 239, 16, 48, 80, 112, 144, 176, 208, 240, 17, 49, 81, 113, 145, 177, 209, 241, + 18, 50, 82, 114, 146, 178, 210, 242, 19, 51, 83, 115, 147, 179, 211, 243, 20, 52, 84, 116, 148, 180, 212, 244, + 21, 53, 85, 117, 149, 181, 213, 245, 22, 54, 86, 118, 150, 182, 214, 246, 23, 55, 87, 119, 151, 183, 215, 247, + 24, 56, 88, 120, 152, 184, 216, 248, 25, 57, 89, 121, 153, 185, 217, 249, 26, 58, 90, 122, 154, 186, 218, 250, + 27, 59, 91, 123, 155, 187, 219, 251, 28, 60, 92, 124, 156, 188, 220, 252, 29, 61, 93, 125, 157, 189, 221, 253, + 30, 62, 94, 126, 158, 190, 222, 254, 31, 63, 95, 127, 159, 191, 223, 255,]; + + private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + private static readonly short[] MatrixRowScan8x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; + private static readonly short[] MatrixRowScan16x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + + private static readonly short[] MatrixRowScan32x32 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, + 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, + 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, + 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, + 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, + 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, + 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, + 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, + 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, + 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, + 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, + 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, + 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, + 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, + 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, + 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, + 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, + 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, + 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, + 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, + 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, + 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, + 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, + 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, + 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, + 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, + 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, + 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, + 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, + 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, + 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, + 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, + 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023,]; + + private static readonly short[] MatrixRowScan4x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + + private static readonly short[] MatrixRowScan8x4 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + + private static readonly short[] MatrixRowScan8x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + + private static readonly short[] MatrixRowScan16x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + + private static readonly short[] MatrixRowScan16x32 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, + 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, + 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, + 506, 507, 508, 509, 510, 511,]; + + private static readonly short[] MatrixRowScan32x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, + 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, + 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, + 506, 507, 508, 509, 510, 511,]; + + private static readonly short[] MatrixRowScan4x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; + + private static readonly short[] MatrixRowScan16x4 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; + + private static readonly short[] MatrixRowScan8x32 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + + private static readonly short[] MatrixRowScan32x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + + // IScan is not used (yet) for AVIF coding, leave these arrays empty for now. + private static readonly short[] DefaultIScan4x4 = []; + private static readonly short[] DefaultIScan8x8 = []; + private static readonly short[] DefaultIScan16x16 = []; + private static readonly short[] DefaultIScan32x32 = []; + private static readonly short[] DefaultIScan64x64 = []; + private static readonly short[] DefaultIScan4x8 = []; + private static readonly short[] DefaultIScan8x4 = []; + private static readonly short[] DefaultIScan8x16 = []; + private static readonly short[] DefaultIScan16x8 = []; + private static readonly short[] DefaultIScan16x32 = []; + private static readonly short[] DefaultIScan32x16 = []; + private static readonly short[] DefaultIScan4x16 = []; + private static readonly short[] DefaultIScan16x4 = []; + private static readonly short[] DefaultIScan8x32 = []; + private static readonly short[] DefaultIScan32x8 = []; + + private static readonly short[] MatrixColumnIScan4x4 = []; + private static readonly short[] MatrixColumnIScan8x8 = []; + private static readonly short[] MatrixColumnIScan16x16 = []; + private static readonly short[] MatrixColumnIScan32x32 = []; + private static readonly short[] MatrixColumnIScan64x64 = []; + private static readonly short[] MatrixColumnIScan4x8 = []; + private static readonly short[] MatrixColumnIScan8x4 = []; + private static readonly short[] MatrixColumnIScan8x16 = []; + private static readonly short[] MatrixColumnIScan16x8 = []; + private static readonly short[] MatrixColumnIScan16x32 = []; + private static readonly short[] MatrixColumnIScan32x16 = []; + private static readonly short[] MatrixColumnIScan4x16 = []; + private static readonly short[] MatrixColumnIScan16x4 = []; + private static readonly short[] MatrixColumnIScan8x32 = []; + private static readonly short[] MatrixColumnIScan32x8 = []; + + private static readonly short[] MatrixRowIScan4x4 = []; + private static readonly short[] MatrixRowIScan8x8 = []; + private static readonly short[] MatrixRowIScan16x16 = []; + private static readonly short[] MatrixRowIScan32x32 = []; + private static readonly short[] MatrixRowIScan64x64 = []; + private static readonly short[] MatrixRowIScan4x8 = []; + private static readonly short[] MatrixRowIScan8x4 = []; + private static readonly short[] MatrixRowIScan8x16 = []; + private static readonly short[] MatrixRowIScan16x8 = []; + private static readonly short[] MatrixRowIScan16x32 = []; + private static readonly short[] MatrixRowIScan32x16 = []; + private static readonly short[] MatrixRowIScan4x16 = []; + private static readonly short[] MatrixRowIScan16x4 = []; + private static readonly short[] MatrixRowIScan8x32 = []; + private static readonly short[] MatrixRowIScan32x8 = []; + + // Neighborss are not used (yet) for AVIF coding, leave these arrays empty for now. + private static readonly short[] DefaultScan4x4Neighbors = []; + private static readonly short[] DefaultScan8x8Neighbors = []; + private static readonly short[] DefaultScan16x16Neighbors = []; + private static readonly short[] DefaultScan32x32Neighbors = []; + private static readonly short[] DefaultScan64x64Neighbors = []; + private static readonly short[] DefaultScan4x8Neighbors = []; + private static readonly short[] DefaultScan8x4Neighbors = []; + private static readonly short[] DefaultScan8x16Neighbors = []; + private static readonly short[] DefaultScan16x8Neighbors = []; + private static readonly short[] DefaultScan16x32Neighbors = []; + private static readonly short[] DefaultScan32x16Neighbors = []; + private static readonly short[] DefaultScan4x16Neighbors = []; + private static readonly short[] DefaultScan16x4Neighbors = []; + private static readonly short[] DefaultScan8x32Neighbors = []; + private static readonly short[] DefaultScan32x8Neighbors = []; + + private static readonly short[] MatrixColumnScan4x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x8Neighbors = []; + private static readonly short[] MatrixColumnScan16x16Neighbors = []; + private static readonly short[] MatrixColumnScan32x32Neighbors = []; + private static readonly short[] MatrixColumnScan64x64Neighbors = []; + private static readonly short[] MatrixColumnScan4x8Neighbors = []; + private static readonly short[] MatrixColumnScan8x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x16Neighbors = []; + private static readonly short[] MatrixColumnScan16x8Neighbors = []; + private static readonly short[] MatrixColumnScan16x32Neighbors = []; + private static readonly short[] MatrixColumnScan32x16Neighbors = []; + private static readonly short[] MatrixColumnScan4x16Neighbors = []; + private static readonly short[] MatrixColumnScan16x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x32Neighbors = []; + private static readonly short[] MatrixColumnScan32x8Neighbors = []; + + private static readonly short[] MatrixRowScan4x4Neighbors = []; + private static readonly short[] MatrixRowScan8x8Neighbors = []; + private static readonly short[] MatrixRowScan16x16Neighbors = []; + private static readonly short[] MatrixRowScan32x32Neighbors = []; + private static readonly short[] MatrixRowScan64x64Neighbors = []; + private static readonly short[] MatrixRowScan4x8Neighbors = []; + private static readonly short[] MatrixRowScan8x4Neighbors = []; + private static readonly short[] MatrixRowScan8x16Neighbors = []; + private static readonly short[] MatrixRowScan16x8Neighbors = []; + private static readonly short[] MatrixRowScan16x32Neighbors = []; + private static readonly short[] MatrixRowScan32x16Neighbors = []; + private static readonly short[] MatrixRowScan4x16Neighbors = []; + private static readonly short[] MatrixRowScan16x4Neighbors = []; + private static readonly short[] MatrixRowScan8x32Neighbors = []; + private static readonly short[] MatrixRowScan32x8Neighbors = []; + public static Av1ScanOrder GetScanOrder(Av1TransformSize transformSize, Av1TransformType transformType) => ScanOrders[(int)transformSize][(int)transformType]; } From 1d83ac3e44854bd990439a5b1a56062a6f32bc03 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 6 Jul 2024 15:53:52 +0200 Subject: [PATCH 104/190] Implement remaining distributions --- .../Av1/Tiling/Av1DefaultDistributions.cs | 1618 +++++++++++++++++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 48 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 10 +- 3 files changed, 1665 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index 476a85f883..3991b55f5f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -394,6 +394,1606 @@ internal static class Av1DefaultDistributions ] ]; + private static Av1Distribution[][][][] CoefficientsBaseRange => + [ + [ + [ + [ + new(14298, 20718, 24174), new(12536, 19601, 23789), new(8712, 15051, 19503), + new(6170, 11327, 15434), new(4742, 8926, 12538), new(3803, 7317, 10546), + new(1696, 3317, 4871), new(14392, 19951, 22756), new(15978, 23218, 26818), + new(12187, 19474, 23889), new(9176, 15640, 20259), new(7068, 12655, 17028), + new(5656, 10442, 14472), new(2580, 4992, 7244), new(12136, 18049, 21426), + new(13784, 20721, 24481), new(10836, 17621, 21900), new(8372, 14444, 18847), + new(6523, 11779, 16000), new(5337, 9898, 13760), new(3034, 5860, 8462) + ], + [ + new(15967, 22905, 26286), new(13534, 20654, 24579), new(9504, 16092, 20535), + new(6975, 12568, 16903), new(5364, 10091, 14020), new(4357, 8370, 11857), + new(2506, 4934, 7218), new(23032, 28815, 30936), new(19540, 26704, 29719), + new(15158, 22969, 27097), new(11408, 18865, 23650), new(8885, 15448, 20250), + new(7108, 12853, 17416), new(4231, 8041, 11480), new(19823, 26490, 29156), + new(18890, 25929, 28932), new(15660, 23491, 27433), new(12147, 19776, 24488), + new(9728, 16774, 21649), new(7919, 14277, 19066), new(5440, 10170, 14185) + ] + ], + [ + [ + new(14406, 20862, 24414), new(11824, 18907, 23109), new(8257, 14393, 18803), + new(5860, 10747, 14778), new(4475, 8486, 11984), new(3606, 6954, 10043), + new(1736, 3410, 5048), new(14430, 20046, 22882), new(15593, 22899, 26709), + new(12102, 19368, 23811), new(9059, 15584, 20262), new(6999, 12603, 17048), + new(5684, 10497, 14553), new(2822, 5438, 7862), new(15785, 21585, 24359), + new(18347, 25229, 28266), new(14974, 22487, 26389), new(11423, 18681, 23271), + new(8863, 15350, 20008), new(7153, 12852, 17278), new(3707, 7036, 9982) + ], + [ + new(15460, 21696, 25469), new(12170, 19249, 23191), new(8723, 15027, 19332), + new(6428, 11704, 15874), new(4922, 9292, 13052), new(4139, 7695, 11010), + new(2291, 4508, 6598), new(19856, 26920, 29828), new(17923, 25289, 28792), + new(14278, 21968, 26297), new(10910, 18136, 22950), new(8423, 14815, 19627), + new(6771, 12283, 16774), new(4074, 7750, 11081), new(19852, 26074, 28672), + new(19371, 26110, 28989), new(16265, 23873, 27663), new(12758, 20378, 24952), + new(10095, 17098, 21961), new(8250, 14628, 19451), new(5205, 9745, 13622) + ] + ], + [ + [ + new(10563, 16233, 19763), new(9794, 16022, 19804), new(6750, 11945, 15759), + new(4963, 9186, 12752), new(3845, 7435, 10627), new(3051, 6085, 8834), + new(1311, 2596, 3830), new(11246, 16404, 19689), new(12315, 18911, 22731), + new(10557, 17095, 21289), new(8136, 14006, 18249), new(6348, 11474, 15565), + new(5196, 9655, 13400), new(2349, 4526, 6587), new(13337, 18730, 21569), + new(19306, 26071, 28882), new(15952, 23540, 27254), new(12409, 19934, 24430), + new(9760, 16706, 21389), new(8004, 14220, 18818), new(4138, 7794, 10961) + ], + [ + new(10870, 16684, 20949), new(9664, 15230, 18680), new(6886, 12109, 15408), + new(4825, 8900, 12305), new(3630, 7162, 10314), new(3036, 6429, 9387), + new(1671, 3296, 4940), new(13819, 19159, 23026), new(11984, 19108, 23120), + new(10690, 17210, 21663), new(7984, 14154, 18333), new(6868, 12294, 16124), + new(5274, 8994, 12868), new(2988, 5771, 8424), new(19736, 26647, 29141), + new(18933, 26070, 28984), new(15779, 23048, 27200), new(12638, 20061, 24532), + new(10692, 17545, 22220), new(9217, 15251, 20054), new(5078, 9284, 12594) + ] + ], + [ + [ + new(2331, 3662, 5244), new(2891, 4771, 6145), new(4598, 7623, 9729), + new(3520, 6845, 9199), new(3417, 6119, 9324), new(2601, 5412, 7385), + new(600, 1173, 1744), new(7672, 13286, 17469), new(4232, 7792, 10793), + new(2915, 5317, 7397), new(2318, 4356, 6152), new(2127, 4000, 5554), + new(1850, 3478, 5275), new(977, 1933, 2843), new(18280, 24387, 27989), + new(15852, 22671, 26185), new(13845, 20951, 24789), new(11055, 17966, 22129), + new(9138, 15422, 19801), new(7454, 13145, 17456), new(3370, 6393, 9013) + ], + [ + new(5842, 9229, 10838), new(2313, 3491, 4276), new(2998, 6104, 7496), + new(2420, 7447, 9868), new(3034, 8495, 10923), new(4076, 8937, 10975), + new(1086, 2370, 3299), new(9714, 17254, 20444), new(8543, 13698, 17123), + new(4918, 9007, 11910), new(4129, 7532, 10553), new(2364, 5533, 8058), + new(1834, 3546, 5563), new(1473, 2908, 4133), new(15405, 21193, 25619), + new(15691, 21952, 26561), new(12962, 19194, 24165), new(10272, 17855, 22129), + new(8588, 15270, 20718), new(8682, 14669, 19500), new(4870, 9636, 13205) + ] + ], + [ + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(14995, 21341, 24749), new(13158, 20289, 24601), new(8941, 15326, 19876), + new(6297, 11541, 15807), new(4817, 9029, 12776), new(3731, 7273, 10627), + new(1847, 3617, 5354), new(14472, 19659, 22343), new(16806, 24162, 27533), + new(12900, 20404, 24713), new(9411, 16112, 20797), new(7056, 12697, 17148), + new(5544, 10339, 14460), new(2954, 5704, 8319), new(12464, 18071, 21354), + new(15482, 22528, 26034), new(12070, 19269, 23624), new(8953, 15406, 20106), + new(7027, 12730, 17220), new(5887, 10913, 15140), new(3793, 7278, 10447) + ], + [ + new(15571, 22232, 25749), new(14506, 21575, 25374), new(10189, 17089, 21569), + new(7316, 13301, 17915), new(5783, 10912, 15190), new(4760, 9155, 13088), + new(2993, 5966, 8774), new(23424, 28903, 30778), new(20775, 27666, 30290), + new(16474, 24410, 28299), new(12471, 20180, 24987), new(9410, 16487, 21439), + new(7536, 13614, 18529), new(5048, 9586, 13549), new(21090, 27290, 29756), + new(20796, 27402, 30026), new(17819, 25485, 28969), new(13860, 21909, 26462), + new(11002, 18494, 23529), new(8953, 15929, 20897), new(6448, 11918, 16454) + ] + ], + [ + [ + new(15999, 22208, 25449), new(13050, 19988, 24122), new(8594, 14864, 19378), + new(6033, 11079, 15238), new(4554, 8683, 12347), new(3672, 7139, 10337), + new(1900, 3771, 5576), new(15788, 21340, 23949), new(16825, 24235, 27758), + new(12873, 20402, 24810), new(9590, 16363, 21094), new(7352, 13209, 17733), + new(5960, 10989, 15184), new(3232, 6234, 9007), new(15761, 20716, 23224), + new(19318, 25989, 28759), new(15529, 23094, 26929), new(11662, 18989, 23641), + new(8955, 15568, 20366), new(7281, 13106, 17708), new(4248, 8059, 11440) + ], + [ + new(14899, 21217, 24503), new(13519, 20283, 24047), new(9429, 15966, 20365), + new(6700, 12355, 16652), new(5088, 9704, 13716), new(4243, 8154, 11731), + new(2702, 5364, 7861), new(22745, 28388, 30454), new(20235, 27146, 29922), + new(15896, 23715, 27637), new(11840, 19350, 24131), new(9122, 15932, 20880), + new(7488, 13581, 18362), new(5114, 9568, 13370), new(20845, 26553, 28932), + new(20981, 27372, 29884), new(17781, 25335, 28785), new(13760, 21708, 26297), + new(10975, 18415, 23365), new(9045, 15789, 20686), new(6130, 11199, 15423) + ] + ], + [ + [ + new(13549, 19724, 23158), new(11844, 18382, 22246), new(7919, 13619, 17773), + new(5486, 10143, 13946), new(4166, 7983, 11324), new(3364, 6506, 9427), + new(1598, 3160, 4674), new(15281, 20979, 23781), new(14939, 22119, 25952), + new(11363, 18407, 22812), new(8609, 14857, 19370), new(6737, 12184, 16480), + new(5506, 10263, 14262), new(2990, 5786, 8380), new(20249, 25253, 27417), + new(21070, 27518, 30001), new(16854, 24469, 28074), new(12864, 20486, 25000), + new(9962, 16978, 21778), new(8074, 14338, 19048), new(4494, 8479, 11906) + ], + [ + new(13960, 19617, 22829), new(11150, 17341, 21228), new(7150, 12964, 17190), + new(5331, 10002, 13867), new(4167, 7744, 11057), new(3480, 6629, 9646), + new(1883, 3784, 5686), new(18752, 25660, 28912), new(16968, 24586, 28030), + new(13520, 21055, 25313), new(10453, 17626, 22280), new(8386, 14505, 19116), + new(6742, 12595, 17008), new(4273, 8140, 11499), new(22120, 27827, 30233), + new(20563, 27358, 29895), new(17076, 24644, 28153), new(13362, 20942, 25309), + new(10794, 17965, 22695), new(9014, 15652, 20319), new(5708, 10512, 14497) + ] + ], + [ + [ + new(5705, 10930, 15725), new(7946, 12765, 16115), new(6801, 12123, 16226), + new(5462, 10135, 14200), new(4189, 8011, 11507), new(3191, 6229, 9408), + new(1057, 2137, 3212), new(10018, 17067, 21491), new(7380, 12582, 16453), + new(6068, 10845, 14339), new(5098, 9198, 12555), new(4312, 8010, 11119), + new(3700, 6966, 9781), new(1693, 3326, 4887), new(18757, 24930, 27774), + new(17648, 24596, 27817), new(14707, 22052, 26026), new(11720, 18852, 23292), + new(9357, 15952, 20525), new(7810, 13753, 18210), new(3879, 7333, 10328) + ], + [ + new(8278, 13242, 15922), new(10547, 15867, 18919), new(9106, 15842, 20609), + new(6833, 13007, 17218), new(4811, 9712, 13923), new(3985, 7352, 11128), + new(1688, 3458, 5262), new(12951, 21861, 26510), new(9788, 16044, 20276), + new(6309, 11244, 14870), new(5183, 9349, 12566), new(4389, 8229, 11492), + new(3633, 6945, 10620), new(3600, 6847, 9907), new(21748, 28137, 30255), + new(19436, 26581, 29560), new(16359, 24201, 27953), new(13961, 21693, 25871), + new(11544, 18686, 23322), new(9372, 16462, 20952), new(6138, 11210, 15390) + ] + ], + [ + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(16138, 22223, 25509), new(15347, 22430, 26332), new(9614, 16736, 21332), + new(6600, 12275, 16907), new(4811, 9424, 13547), new(3748, 7809, 11420), + new(2254, 4587, 6890), new(15196, 20284, 23177), new(18317, 25469, 28451), + new(13918, 21651, 25842), new(10052, 17150, 21995), new(7499, 13630, 18587), + new(6158, 11417, 16003), new(4014, 7785, 11252), new(15048, 21067, 24384), + new(18202, 25346, 28553), new(14302, 22019, 26356), new(10839, 18139, 23166), + new(8715, 15744, 20806), new(7536, 13576, 18544), new(5413, 10335, 14498) + ], + [ + new(17394, 24501, 27895), new(15889, 23420, 27185), new(11561, 19133, 23870), + new(8285, 14812, 19844), new(6496, 12043, 16550), new(4771, 9574, 13677), + new(3603, 6830, 10144), new(21656, 27704, 30200), new(21324, 27915, 30511), + new(17327, 25336, 28997), new(13417, 21381, 26033), new(10132, 17425, 22338), + new(8580, 15016, 19633), new(5694, 11477, 16411), new(24116, 29780, 31450), + new(23853, 29695, 31591), new(20085, 27614, 30428), new(15326, 24335, 28575), + new(11814, 19472, 24810), new(10221, 18611, 24767), new(7689, 14558, 20321) + ] + ], + [ + [ + new(16214, 22380, 25770), new(14213, 21304, 25295), new(9213, 15823, 20455), + new(6395, 11758, 16139), new(4779, 9187, 13066), new(3821, 7501, 10953), + new(2293, 4567, 6795), new(15859, 21283, 23820), new(18404, 25602, 28726), + new(14325, 21980, 26206), new(10669, 17937, 22720), new(8297, 14642, 19447), + new(6746, 12389, 16893), new(4324, 8251, 11770), new(16532, 21631, 24475), + new(20667, 27150, 29668), new(16728, 24510, 28175), new(12861, 20645, 25332), + new(10076, 17361, 22417), new(8395, 14940, 19963), new(5731, 10683, 14912) + ], + [ + new(14433, 21155, 24938), new(14658, 21716, 25545), new(9923, 16824, 21557), + new(6982, 13052, 17721), new(5419, 10503, 15050), new(4852, 9162, 13014), + new(3271, 6395, 9630), new(22210, 27833, 30109), new(20750, 27368, 29821), + new(16894, 24828, 28573), new(13247, 21276, 25757), new(10038, 17265, 22563), + new(8587, 14947, 20327), new(5645, 11371, 15252), new(22027, 27526, 29714), + new(23098, 29146, 31221), new(19886, 27341, 30272), new(15609, 23747, 28046), + new(11993, 20065, 24939), new(9637, 18267, 23671), new(7625, 13801, 19144) + ] + ], + [ + [ + new(14438, 20798, 24089), new(12621, 19203, 23097), new(8177, 14125, 18402), + new(5674, 10501, 14456), new(4236, 8239, 11733), new(3447, 6750, 9806), + new(1986, 3950, 5864), new(16208, 22099, 24930), new(16537, 24025, 27585), + new(12780, 20381, 24867), new(9767, 16612, 21416), new(7686, 13738, 18398), + new(6333, 11614, 15964), new(3941, 7571, 10836), new(22819, 27422, 29202), + new(22224, 28514, 30721), new(17660, 25433, 28913), new(13574, 21482, 26002), + new(10629, 17977, 22938), new(8612, 15298, 20265), new(5607, 10491, 14596) + ], + [ + new(13569, 19800, 23206), new(13128, 19924, 23869), new(8329, 14841, 19403), + new(6130, 10976, 15057), new(4682, 8839, 12518), new(3656, 7409, 10588), + new(2577, 5099, 7412), new(22427, 28684, 30585), new(20913, 27750, 30139), + new(15840, 24109, 27834), new(12308, 20029, 24569), new(10216, 16785, 21458), + new(8309, 14203, 19113), new(6043, 11168, 15307), new(23166, 28901, 30998), + new(21899, 28405, 30751), new(18413, 26091, 29443), new(15233, 23114, 27352), + new(12683, 20472, 25288), new(10702, 18259, 23409), new(8125, 14464, 19226) + ] + ], + [ + [ + new(9040, 14786, 18360), new(9979, 15718, 19415), new(7913, 13918, 18311), + new(5859, 10889, 15184), new(4593, 8677, 12510), new(3820, 7396, 10791), + new(1730, 3471, 5192), new(11803, 18365, 22709), new(11419, 18058, 22225), + new(9418, 15774, 20243), new(7539, 13325, 17657), new(6233, 11317, 15384), + new(5137, 9656, 13545), new(2977, 5774, 8349), new(21207, 27246, 29640), + new(19547, 26578, 29497), new(16169, 23871, 27690), new(12820, 20458, 25018), + new(10224, 17332, 22214), new(8526, 15048, 19884), new(5037, 9410, 13118) + ], + [ + new(12339, 17329, 20140), new(13505, 19895, 23225), new(9847, 16944, 21564), + new(7280, 13256, 18348), new(4712, 10009, 14454), new(4361, 7914, 12477), + new(2870, 5628, 7995), new(20061, 25504, 28526), new(15235, 22878, 26145), + new(12985, 19958, 24155), new(9782, 16641, 21403), new(9456, 16360, 20760), + new(6855, 12940, 18557), new(5661, 10564, 15002), new(25656, 30602, 31894), + new(22570, 29107, 31092), new(18917, 26423, 29541), new(15940, 23649, 27754), + new(12803, 20581, 25219), new(11082, 18695, 23376), new(7939, 14373, 19005) + ] + ], + [ + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(18315, 24289, 27551), new(16854, 24068, 27835), new(10140, 17927, 23173), + new(6722, 12982, 18267), new(4661, 9826, 14706), new(3832, 8165, 12294), + new(2795, 6098, 9245), new(17145, 23326, 26672), new(20733, 27680, 30308), + new(16032, 24461, 28546), new(11653, 20093, 25081), new(9290, 16429, 22086), + new(7796, 14598, 19982), new(6502, 12378, 17441), new(21681, 27732, 30320), + new(22389, 29044, 31261), new(19027, 26731, 30087), new(14739, 23755, 28624), + new(11358, 20778, 25511), new(10995, 18073, 24190), new(9162, 14990, 20617) + ], + [ + new(21425, 27952, 30388), new(18062, 25838, 29034), new(11956, 19881, 24808), + new(7718, 15000, 20980), new(5702, 11254, 16143), new(4898, 9088, 16864), + new(3679, 6776, 11907), new(23294, 30160, 31663), new(24397, 29896, 31836), + new(19245, 27128, 30593), new(13202, 19825, 26404), new(11578, 19297, 23957), + new(8073, 13297, 21370), new(5461, 10923, 19745), new(27367, 30521, 31934), + new(24904, 30671, 31940), new(23075, 28460, 31299), new(14400, 23658, 30417), + new(13885, 23882, 28325), new(14746, 22938, 27853), new(5461, 16384, 27307) + ] + ], + [ + [ + new(18274, 24813, 27890), new(15537, 23149, 27003), new(9449, 16740, 21827), + new(6700, 12498, 17261), new(4988, 9866, 14198), new(4236, 8147, 11902), + new(2867, 5860, 8654), new(17124, 23171, 26101), new(20396, 27477, 30148), + new(16573, 24629, 28492), new(12749, 20846, 25674), new(10233, 17878, 22818), + new(8525, 15332, 20363), new(6283, 11632, 16255), new(20466, 26511, 29286), + new(23059, 29174, 31191), new(19481, 27263, 30241), new(15458, 23631, 28137), + new(12416, 20608, 25693), new(10261, 18011, 23261), new(8016, 14655, 19666) + ], + [ + new(17616, 24586, 28112), new(15809, 23299, 27155), new(10767, 18890, 23793), + new(7727, 14255, 18865), new(6129, 11926, 16882), new(4482, 9704, 14861), + new(3277, 7452, 11522), new(22956, 28551, 30730), new(22724, 28937, 30961), + new(18467, 26324, 29580), new(13234, 20713, 25649), new(11181, 17592, 22481), + new(8291, 18358, 24576), new(7568, 11881, 14984), new(24948, 29001, 31147), + new(25674, 30619, 32151), new(20841, 26793, 29603), new(14669, 24356, 28666), + new(11334, 23593, 28219), new(8922, 14762, 22873), new(8301, 13544, 20535) + ] + ], + [ + [ + new(17113, 23733, 27081), new(14139, 21406, 25452), new(8552, 15002, 19776), + new(5871, 11120, 15378), new(4455, 8616, 12253), new(3469, 6910, 10386), + new(2255, 4553, 6782), new(18224, 24376, 27053), new(19290, 26710, 29614), + new(14936, 22991, 27184), new(11238, 18951, 23762), new(8786, 15617, 20588), + new(7317, 13228, 18003), new(5101, 9512, 13493), new(22639, 28222, 30210), + new(23216, 29331, 31307), new(19075, 26762, 29895), new(15014, 23113, 27457), + new(11938, 19857, 24752), new(9942, 17280, 22282), new(7167, 13144, 17752) + ], + [ + new(15820, 22738, 26488), new(13530, 20885, 25216), new(8395, 15530, 20452), + new(6574, 12321, 16380), new(5353, 10419, 14568), new(4613, 8446, 12381), + new(3440, 7158, 9903), new(24247, 29051, 31224), new(22118, 28058, 30369), + new(16498, 24768, 28389), new(12920, 21175, 26137), new(10730, 18619, 25352), + new(10187, 16279, 22791), new(9310, 14631, 22127), new(24970, 30558, 32057), + new(24801, 29942, 31698), new(22432, 28453, 30855), new(19054, 25680, 29580), + new(14392, 23036, 28109), new(12495, 20947, 26650), new(12442, 20326, 26214) + ] + ], + [ + [ + new(12162, 18785, 22648), new(12749, 19697, 23806), new(8580, 15297, 20346), + new(6169, 11749, 16543), new(4836, 9391, 13448), new(3821, 7711, 11613), + new(2228, 4601, 7070), new(16319, 24725, 28280), new(15698, 23277, 27168), + new(12726, 20368, 25047), new(9912, 17015, 21976), new(7888, 14220, 19179), + new(6777, 12284, 17018), new(4492, 8590, 12252), new(23249, 28904, 30947), + new(21050, 27908, 30512), new(17440, 25340, 28949), new(14059, 22018, 26541), + new(11288, 18903, 23898), new(9411, 16342, 21428), new(6278, 11588, 15944) + ], + [ + new(13981, 20067, 23226), new(16922, 23580, 26783), new(11005, 19039, 24487), + new(7389, 14218, 19798), new(5598, 11505, 17206), new(6090, 11213, 15659), + new(3820, 7371, 10119), new(21082, 26925, 29675), new(21262, 28627, 31128), + new(18392, 26454, 30437), new(14870, 22910, 27096), new(12620, 19484, 24908), + new(9290, 16553, 22802), new(6668, 14288, 20004), new(27704, 31055, 31949), + new(24709, 29978, 31788), new(21668, 29264, 31657), new(18295, 26968, 30074), + new(16399, 24422, 29313), new(14347, 23026, 28104), new(12370, 19806, 24477) + ] + ], + [ + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ] + ]; + + private static Av1Distribution[][][][] CoefficientsBase => + [ + [ + [ + [ + new(4034, 8930, 12727), new(18082, 29741, 31877), new(12596, 26124, 30493), + new(9446, 21118, 27005), new(6308, 15141, 21279), new(2463, 6357, 9783), + new(20667, 30546, 31929), new(13043, 26123, 30134), new(8151, 18757, 24778), + new(5255, 12839, 18632), new(2820, 7206, 11161), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(15736, 27553, 30604), new(11210, 23794, 28787), new(5947, 13874, 19701), + new(4215, 9323, 13891), new(2833, 6462, 10059), new(19605, 30393, 31582), + new(13523, 26252, 30248), new(8446, 18622, 24512), new(3818, 10343, 15974), + new(1481, 4117, 6796), new(22649, 31302, 32190), new(14829, 27127, 30449), + new(8313, 17702, 23304), new(3022, 8301, 12786), new(1536, 4412, 7184), + new(22354, 29774, 31372), new(14723, 25472, 29214), new(6673, 13745, 18662), + new(2068, 5766, 9322), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(6302, 16444, 21761), new(23040, 31538, 32475), new(15196, 28452, 31496), + new(10020, 22946, 28514), new(6533, 16862, 23501), new(3538, 9816, 15076), + new(24444, 31875, 32525), new(15881, 28924, 31635), new(9922, 22873, 28466), + new(6527, 16966, 23691), new(4114, 11303, 17220), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(20201, 30770, 32209), new(14754, 28071, 31258), new(8378, 20186, 26517), + new(5916, 15299, 21978), new(4268, 11583, 17901), new(24361, 32025, 32581), + new(18673, 30105, 31943), new(10196, 22244, 27576), new(5495, 14349, 20417), + new(2676, 7415, 11498), new(24678, 31958, 32585), new(18629, 29906, 31831), + new(9364, 20724, 26315), new(4641, 12318, 18094), new(2758, 7387, 11579), + new(25433, 31842, 32469), new(18795, 29289, 31411), new(7644, 17584, 23592), + new(3408, 9014, 15047), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(4536, 10072, 14001), new(25459, 31416, 32206), new(16605, 28048, 30818), + new(11008, 22857, 27719), new(6915, 16268, 22315), new(2625, 6812, 10537), + new(24257, 31788, 32499), new(16880, 29454, 31879), new(11958, 25054, 29778), + new(7916, 18718, 25084), new(3383, 8777, 13446), new(22720, 31603, 32393), + new(14960, 28125, 31335), new(9731, 22210, 27928), new(6304, 15832, 22277), + new(2910, 7818, 12166), new(20375, 30627, 32131), new(13904, 27284, 30887), + new(9368, 21558, 27144), new(5937, 14966, 21119), new(2667, 7225, 11319), + new(23970, 31470, 32378), new(17173, 29734, 32018), new(12795, 25441, 29965), + new(8981, 19680, 25893), new(4728, 11372, 16902), new(24287, 31797, 32439), + new(16703, 29145, 31696), new(10833, 23554, 28725), new(6468, 16566, 23057), + new(2415, 6562, 10278), new(26610, 32395, 32659), new(18590, 30498, 32117), + new(12420, 25756, 29950), new(7639, 18746, 24710), new(3001, 8086, 12347), + new(25076, 32064, 32580), new(17946, 30128, 32028), new(12024, 24985, 29378), + new(7517, 18390, 24304), new(3243, 8781, 13331), new(8192, 16384, 24576) + ], + [ + new(6037, 16771, 21957), new(24774, 31704, 32426), new(16830, 28589, 31056), + new(10602, 22828, 27760), new(6733, 16829, 23071), new(3250, 8914, 13556), + new(25582, 32220, 32668), new(18659, 30342, 32223), new(12546, 26149, 30515), + new(8420, 20451, 26801), new(4636, 12420, 18344), new(27581, 32362, 32639), + new(18987, 30083, 31978), new(11327, 24248, 29084), new(7264, 17719, 24120), + new(3995, 10768, 16169), new(25893, 31831, 32487), new(16577, 28587, 31379), + new(10189, 22748, 28182), new(6832, 17094, 23556), new(3708, 10110, 15334), + new(25904, 32282, 32656), new(19721, 30792, 32276), new(12819, 26243, 30411), + new(8572, 20614, 26891), new(5364, 14059, 20467), new(26580, 32438, 32677), + new(20852, 31225, 32340), new(12435, 25700, 29967), new(8691, 20825, 26976), + new(4446, 12209, 17269), new(27350, 32429, 32696), new(21372, 30977, 32272), + new(12673, 25270, 29853), new(9208, 20925, 26640), new(5018, 13351, 18732), + new(27351, 32479, 32713), new(21398, 31209, 32387), new(12162, 25047, 29842), + new(7896, 18691, 25319), new(4670, 12882, 18881), new(8192, 16384, 24576) + ] + ], + [ + [ + new(5487, 10460, 13708), new(21597, 28303, 30674), new(11037, 21953, 26476), + new(8147, 17962, 22952), new(5242, 13061, 18532), new(1889, 5208, 8182), + new(26774, 32133, 32590), new(17844, 29564, 31767), new(11690, 24438, 29171), + new(7542, 18215, 24459), new(2993, 8050, 12319), new(28023, 32328, 32591), + new(18651, 30126, 31954), new(12164, 25146, 29589), new(7762, 18530, 24771), + new(3492, 9183, 13920), new(27591, 32008, 32491), new(17149, 28853, 31510), + new(11485, 24003, 28860), new(7697, 18086, 24210), new(3075, 7999, 12218), + new(28268, 32482, 32654), new(19631, 31051, 32404), new(13860, 27260, 31020), + new(9605, 21613, 27594), new(4876, 12162, 17908), new(27248, 32316, 32576), + new(18955, 30457, 32075), new(11824, 23997, 28795), new(7346, 18196, 24647), + new(3403, 9247, 14111), new(29711, 32655, 32735), new(21169, 31394, 32417), + new(13487, 27198, 30957), new(8828, 21683, 27614), new(4270, 11451, 17038), + new(28708, 32578, 32731), new(20120, 31241, 32482), new(13692, 27550, 31321), + new(9418, 22514, 28439), new(4999, 13283, 19462), new(8192, 16384, 24576) + ], + [ + new(5673, 14302, 19711), new(26251, 30701, 31834), new(12782, 23783, 27803), + new(9127, 20657, 25808), new(6368, 16208, 21462), new(2465, 7177, 10822), + new(29961, 32563, 32719), new(18318, 29891, 31949), new(11361, 24514, 29357), + new(7900, 19603, 25607), new(4002, 10590, 15546), new(29637, 32310, 32595), + new(18296, 29913, 31809), new(10144, 21515, 26871), new(5358, 14322, 20394), + new(3067, 8362, 13346), new(28652, 32470, 32676), new(17538, 30771, 32209), + new(13924, 26882, 30494), new(10496, 22837, 27869), new(7236, 16396, 21621), + new(30743, 32687, 32746), new(23006, 31676, 32489), new(14494, 27828, 31120), + new(10174, 22801, 28352), new(6242, 15281, 21043), new(25817, 32243, 32720), + new(18618, 31367, 32325), new(13997, 28318, 31878), new(12255, 26534, 31383), + new(9561, 21588, 28450), new(28188, 32635, 32724), new(22060, 32365, 32728), + new(18102, 30690, 32528), new(14196, 28864, 31999), new(12262, 25792, 30865), + new(24176, 32109, 32628), new(18280, 29681, 31963), new(10205, 23703, 29664), + new(7889, 20025, 27676), new(6060, 16743, 23970), new(8192, 16384, 24576) + ] + ], + [ + [ + new(5141, 7096, 8260), new(27186, 29022, 29789), new(6668, 12568, 15682), + new(2172, 6181, 8638), new(1126, 3379, 4531), new(443, 1361, 2254), + new(26083, 31153, 32436), new(13486, 24603, 28483), new(6508, 14840, 19910), + new(3386, 8800, 13286), new(1530, 4322, 7054), new(29639, 32080, 32548), + new(15897, 27552, 30290), new(8588, 20047, 25383), new(4889, 13339, 19269), + new(2240, 6871, 10498), new(28165, 32197, 32517), new(20735, 30427, 31568), + new(14325, 24671, 27692), new(5119, 12554, 17805), new(1810, 5441, 8261), + new(31212, 32724, 32748), new(23352, 31766, 32545), new(14669, 27570, 31059), + new(8492, 20894, 27272), new(3644, 10194, 15204), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(2461, 7013, 9371), new(24749, 29600, 30986), new(9466, 19037, 22417), + new(3584, 9280, 14400), new(1505, 3929, 5433), new(677, 1500, 2736), + new(23987, 30702, 32117), new(13554, 24571, 29263), new(6211, 14556, 21155), + new(3135, 10972, 15625), new(2435, 7127, 11427), new(31300, 32532, 32550), + new(14757, 30365, 31954), new(4405, 11612, 18553), new(580, 4132, 7322), + new(1695, 10169, 14124), new(30008, 32282, 32591), new(19244, 30108, 31748), + new(11180, 24158, 29555), new(5650, 14972, 19209), new(2114, 5109, 8456), + new(31856, 32716, 32748), new(23012, 31664, 32572), new(13694, 26656, 30636), + new(8142, 19508, 26093), new(4253, 10955, 16724), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(601, 983, 1311), new(18725, 23406, 28087), new(5461, 8192, 10923), + new(3781, 15124, 21425), new(2587, 7761, 12072), new(106, 458, 810), + new(22282, 29710, 31894), new(8508, 20926, 25984), new(3726, 12713, 18083), + new(1620, 7112, 10893), new(729, 2236, 3495), new(30163, 32474, 32684), + new(18304, 30464, 32000), new(11443, 26526, 29647), new(6007, 15292, 21299), + new(2234, 6703, 8937), new(30954, 32177, 32571), new(17363, 29562, 31076), + new(9686, 22464, 27410), new(8192, 16384, 21390), new(1755, 8046, 11264), + new(31168, 32734, 32748), new(22486, 31441, 32471), new(12833, 25627, 29738), + new(6980, 17379, 23122), new(3111, 8887, 13479), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(6041, 11854, 15927), new(20326, 30905, 32251), new(14164, 26831, 30725), + new(9760, 20647, 26585), new(6416, 14953, 21219), new(2966, 7151, 10891), + new(23567, 31374, 32254), new(14978, 27416, 30946), new(9434, 20225, 26254), + new(6658, 14558, 20535), new(3916, 8677, 12989), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(18088, 29545, 31587), new(13062, 25843, 30073), new(8940, 16827, 22251), + new(7654, 13220, 17973), new(5733, 10316, 14456), new(22879, 31388, 32114), + new(15215, 27993, 30955), new(9397, 19445, 24978), new(3442, 9813, 15344), + new(1368, 3936, 6532), new(25494, 32033, 32406), new(16772, 27963, 30718), + new(9419, 18165, 23260), new(2677, 7501, 11797), new(1516, 4344, 7170), + new(26556, 31454, 32101), new(17128, 27035, 30108), new(8324, 15344, 20249), + new(1903, 5696, 9469), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8455, 19003, 24368), new(23563, 32021, 32604), new(16237, 29446, 31935), + new(10724, 23999, 29358), new(6725, 17528, 24416), new(3927, 10927, 16825), + new(26313, 32288, 32634), new(17430, 30095, 32095), new(11116, 24606, 29679), + new(7195, 18384, 25269), new(4726, 12852, 19315), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(22822, 31648, 32483), new(16724, 29633, 31929), new(10261, 23033, 28725), + new(7029, 17840, 24528), new(4867, 13886, 21502), new(25298, 31892, 32491), + new(17809, 29330, 31512), new(9668, 21329, 26579), new(4774, 12956, 18976), + new(2322, 7030, 11540), new(25472, 31920, 32543), new(17957, 29387, 31632), + new(9196, 20593, 26400), new(4680, 12705, 19202), new(2917, 8456, 13436), + new(26471, 32059, 32574), new(18458, 29783, 31909), new(8400, 19464, 25956), + new(3812, 10973, 17206), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(6779, 13743, 17678), new(24806, 31797, 32457), new(17616, 29047, 31372), + new(11063, 23175, 28003), new(6521, 16110, 22324), new(2764, 7504, 11654), + new(25266, 32367, 32637), new(19054, 30553, 32175), new(12139, 25212, 29807), + new(7311, 18162, 24704), new(3397, 9164, 14074), new(25988, 32208, 32522), + new(16253, 28912, 31526), new(9151, 21387, 27372), new(5688, 14915, 21496), + new(2717, 7627, 12004), new(23144, 31855, 32443), new(16070, 28491, 31325), + new(8702, 20467, 26517), new(5243, 13956, 20367), new(2621, 7335, 11567), + new(26636, 32340, 32630), new(19990, 31050, 32341), new(13243, 26105, 30315), + new(8588, 19521, 25918), new(4717, 11585, 17304), new(25844, 32292, 32582), + new(19090, 30635, 32097), new(11963, 24546, 28939), new(6218, 16087, 22354), + new(2340, 6608, 10426), new(28046, 32576, 32694), new(21178, 31313, 32296), + new(13486, 26184, 29870), new(7149, 17871, 23723), new(2833, 7958, 12259), + new(27710, 32528, 32686), new(20674, 31076, 32268), new(12413, 24955, 29243), + new(6676, 16927, 23097), new(2966, 8333, 12919), new(8192, 16384, 24576) + ], + [ + new(8639, 19339, 24429), new(24404, 31837, 32525), new(16997, 29425, 31784), + new(11253, 24234, 29149), new(6751, 17394, 24028), new(3490, 9830, 15191), + new(26283, 32471, 32714), new(19599, 31168, 32442), new(13146, 26954, 30893), + new(8214, 20588, 26890), new(4699, 13081, 19300), new(28212, 32458, 32669), + new(18594, 30316, 32100), new(11219, 24408, 29234), new(6865, 17656, 24149), + new(3678, 10362, 16006), new(25825, 32136, 32616), new(17313, 29853, 32021), + new(11197, 24471, 29472), new(6947, 17781, 24405), new(3768, 10660, 16261), + new(27352, 32500, 32706), new(20850, 31468, 32469), new(14021, 27707, 31133), + new(8964, 21748, 27838), new(5437, 14665, 21187), new(26304, 32492, 32698), + new(20409, 31380, 32385), new(13682, 27222, 30632), new(8974, 21236, 26685), + new(4234, 11665, 16934), new(26273, 32357, 32711), new(20672, 31242, 32441), + new(14172, 27254, 30902), new(9870, 21898, 27275), new(5164, 13506, 19270), + new(26725, 32459, 32728), new(20991, 31442, 32527), new(13071, 26434, 30811), + new(8184, 20090, 26742), new(4803, 13255, 19895), new(8192, 16384, 24576) + ] + ], + [ + [ + new(7555, 14942, 18501), new(24410, 31178, 32287), new(14394, 26738, 30253), + new(8413, 19554, 25195), new(4766, 12924, 18785), new(2029, 5806, 9207), + new(26776, 32364, 32663), new(18732, 29967, 31931), new(11005, 23786, 28852), + new(6466, 16909, 23510), new(3044, 8638, 13419), new(29208, 32582, 32704), + new(20068, 30857, 32208), new(12003, 25085, 29595), new(6947, 17750, 24189), + new(3245, 9103, 14007), new(27359, 32465, 32669), new(19421, 30614, 32174), + new(11915, 25010, 29579), new(6950, 17676, 24074), new(3007, 8473, 13096), + new(29002, 32676, 32735), new(22102, 31849, 32576), new(14408, 28009, 31405), + new(9027, 21679, 27931), new(4694, 12678, 18748), new(28216, 32528, 32682), + new(20849, 31264, 32318), new(12756, 25815, 29751), new(7565, 18801, 24923), + new(3509, 9533, 14477), new(30133, 32687, 32739), new(23063, 31910, 32515), + new(14588, 28051, 31132), new(9085, 21649, 27457), new(4261, 11654, 17264), + new(29518, 32691, 32748), new(22451, 31959, 32613), new(14864, 28722, 31700), + new(9695, 22964, 28716), new(4932, 13358, 19502), new(8192, 16384, 24576) + ], + [ + new(6465, 16958, 21688), new(25199, 31514, 32360), new(14774, 27149, 30607), + new(9257, 21438, 26972), new(5723, 15183, 21882), new(3150, 8879, 13731), + new(26989, 32262, 32682), new(17396, 29937, 32085), new(11387, 24901, 29784), + new(7289, 18821, 25548), new(3734, 10577, 16086), new(29728, 32501, 32695), + new(17431, 29701, 31903), new(9921, 22826, 28300), new(5896, 15434, 22068), + new(3430, 9646, 14757), new(28614, 32511, 32705), new(19364, 30638, 32263), + new(13129, 26254, 30402), new(8754, 20484, 26440), new(4378, 11607, 17110), + new(30292, 32671, 32744), new(21780, 31603, 32501), new(14314, 27829, 31291), + new(9611, 22327, 28263), new(4890, 13087, 19065), new(25862, 32567, 32733), + new(20794, 32050, 32567), new(17243, 30625, 32254), new(13283, 27628, 31474), + new(9669, 22532, 28918), new(27435, 32697, 32748), new(24922, 32390, 32714), + new(21449, 31504, 32536), new(16392, 29729, 31832), new(11692, 24884, 29076), + new(24193, 32290, 32735), new(18909, 31104, 32563), new(12236, 26841, 31403), + new(8171, 21840, 29082), new(7224, 17280, 25275), new(8192, 16384, 24576) + ] + ], + [ + [ + new(3078, 6839, 9890), new(13837, 20450, 24479), new(5914, 14222, 19328), + new(3866, 10267, 14762), new(2612, 7208, 11042), new(1067, 2991, 4776), + new(25817, 31646, 32529), new(13708, 26338, 30385), new(7328, 18585, 24870), + new(4691, 13080, 19276), new(1825, 5253, 8352), new(29386, 32315, 32624), + new(17160, 29001, 31360), new(9602, 21862, 27396), new(5915, 15772, 22148), + new(2786, 7779, 12047), new(29246, 32450, 32663), new(18696, 29929, 31818), + new(10510, 23369, 28560), new(6229, 16499, 23125), new(2608, 7448, 11705), + new(30753, 32710, 32748), new(21638, 31487, 32503), new(12937, 26854, 30870), + new(8182, 20596, 26970), new(3637, 10269, 15497), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(5244, 12150, 16906), new(20486, 26858, 29701), new(7756, 18317, 23735), + new(3452, 9256, 13146), new(2020, 5206, 8229), new(1801, 4993, 7903), + new(27051, 31858, 32531), new(15988, 27531, 30619), new(9188, 21484, 26719), + new(6273, 17186, 23800), new(3108, 9355, 14764), new(31076, 32520, 32680), + new(18119, 30037, 31850), new(10244, 22969, 27472), new(4692, 14077, 19273), + new(3694, 11677, 17556), new(30060, 32581, 32720), new(21011, 30775, 32120), + new(11931, 24820, 29289), new(7119, 17662, 24356), new(3833, 10706, 16304), + new(31954, 32731, 32748), new(23913, 31724, 32489), new(15520, 28060, 31286), + new(11517, 23008, 28571), new(6193, 14508, 20629), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(1035, 2807, 4156), new(13162, 18138, 20939), new(2696, 6633, 8755), + new(1373, 4161, 6853), new(1099, 2746, 4716), new(340, 1021, 1599), + new(22826, 30419, 32135), new(10395, 21762, 26942), new(4726, 12407, 17361), + new(2447, 7080, 10593), new(1227, 3717, 6011), new(28156, 31424, 31934), + new(16915, 27754, 30373), new(9148, 20990, 26431), new(5950, 15515, 21148), + new(2492, 7327, 11526), new(30602, 32477, 32670), new(20026, 29955, 31568), + new(11220, 23628, 28105), new(6652, 17019, 22973), new(3064, 8536, 13043), + new(31769, 32724, 32748), new(22230, 30887, 32373), new(12234, 25079, 29731), + new(7326, 18816, 25353), new(3933, 10907, 16616), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(8896, 16227, 20630), new(23629, 31782, 32527), new(15173, 27755, 31321), + new(10158, 21233, 27382), new(6420, 14857, 21558), new(3269, 8155, 12646), + new(24835, 32009, 32496), new(16509, 28421, 31579), new(10957, 21514, 27418), + new(7881, 15930, 22096), new(5388, 10960, 15918), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(20745, 30773, 32093), new(15200, 27221, 30861), new(13032, 20873, 25667), + new(12285, 18663, 23494), new(11563, 17481, 21489), new(26260, 31982, 32320), + new(15397, 28083, 31100), new(9742, 19217, 24824), new(3261, 9629, 15362), + new(1480, 4322, 7499), new(27599, 32256, 32460), new(16857, 27659, 30774), + new(9551, 18290, 23748), new(3052, 8933, 14103), new(2021, 5910, 9787), + new(29005, 32015, 32392), new(17677, 27694, 30863), new(9204, 17356, 23219), + new(2403, 7516, 12814), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(10808, 22056, 26896), new(25739, 32313, 32676), new(17288, 30203, 32221), + new(11359, 24878, 29896), new(6949, 17767, 24893), new(4287, 11796, 18071), + new(27880, 32521, 32705), new(19038, 31004, 32414), new(12564, 26345, 30768), + new(8269, 19947, 26779), new(5674, 14657, 21674), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(25742, 32319, 32671), new(19557, 31164, 32454), new(13381, 26381, 30755), + new(10101, 21466, 26722), new(9209, 19650, 26825), new(27107, 31917, 32432), + new(18056, 28893, 31203), new(10200, 21434, 26764), new(4660, 12913, 19502), + new(2368, 6930, 12504), new(26960, 32158, 32613), new(18628, 30005, 32031), + new(10233, 22442, 28232), new(5471, 14630, 21516), new(3235, 10767, 17109), + new(27696, 32440, 32692), new(20032, 31167, 32438), new(8700, 21341, 28442), + new(5662, 14831, 21795), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(9704, 17294, 21132), new(26762, 32278, 32633), new(18382, 29620, 31819), + new(10891, 23475, 28723), new(6358, 16583, 23309), new(3248, 9118, 14141), + new(27204, 32573, 32699), new(19818, 30824, 32329), new(11772, 25120, 30041), + new(6995, 18033, 25039), new(3752, 10442, 16098), new(27222, 32256, 32559), + new(15356, 28399, 31475), new(8821, 20635, 27057), new(5511, 14404, 21239), + new(2935, 8222, 13051), new(24875, 32120, 32529), new(15233, 28265, 31445), + new(8605, 20570, 26932), new(5431, 14413, 21196), new(2994, 8341, 13223), + new(28201, 32604, 32700), new(21041, 31446, 32456), new(13221, 26213, 30475), + new(8255, 19385, 26037), new(4930, 12585, 18830), new(28768, 32448, 32627), + new(19705, 30561, 32021), new(11572, 23589, 28220), new(5532, 15034, 21446), + new(2460, 7150, 11456), new(29874, 32619, 32699), new(21621, 31071, 32201), + new(12511, 24747, 28992), new(6281, 16395, 22748), new(3246, 9278, 14497), + new(29715, 32625, 32712), new(20958, 31011, 32283), new(11233, 23671, 28806), + new(6012, 16128, 22868), new(3427, 9851, 15414), new(8192, 16384, 24576) + ], + [ + new(11016, 22111, 26794), new(25946, 32357, 32677), new(17890, 30452, 32252), + new(11678, 25142, 29816), new(6720, 17534, 24584), new(4230, 11665, 17820), + new(28400, 32623, 32747), new(21164, 31668, 32575), new(13572, 27388, 31182), + new(8234, 20750, 27358), new(5065, 14055, 20897), new(28981, 32547, 32705), + new(18681, 30543, 32239), new(10919, 24075, 29286), new(6431, 17199, 24077), + new(3819, 10464, 16618), new(26870, 32467, 32693), new(19041, 30831, 32347), + new(11794, 25211, 30016), new(6888, 18019, 24970), new(4370, 12363, 18992), + new(29578, 32670, 32744), new(23159, 32007, 32613), new(15315, 28669, 31676), + new(9298, 22607, 28782), new(6144, 15913, 22968), new(28110, 32499, 32669), + new(21574, 30937, 32015), new(12759, 24818, 28727), new(6545, 16761, 23042), + new(3649, 10597, 16833), new(28163, 32552, 32728), new(22101, 31469, 32464), + new(13160, 25472, 30143), new(7303, 18684, 25468), new(5241, 13975, 20955), + new(28400, 32631, 32744), new(22104, 31793, 32603), new(13557, 26571, 30846), + new(7749, 19861, 26675), new(4873, 14030, 21234), new(8192, 16384, 24576) + ] + ], + [ + [ + new(9800, 17635, 21073), new(26153, 31885, 32527), new(15038, 27852, 31006), + new(8718, 20564, 26486), new(5128, 14076, 20514), new(2636, 7566, 11925), + new(27551, 32504, 32701), new(18310, 30054, 32100), new(10211, 23420, 29082), + new(6222, 16876, 23916), new(3462, 9954, 15498), new(29991, 32633, 32721), + new(19883, 30751, 32201), new(11141, 24184, 29285), new(6420, 16940, 23774), + new(3392, 9753, 15118), new(28465, 32616, 32712), new(19850, 30702, 32244), + new(10983, 24024, 29223), new(6294, 16770, 23582), new(3244, 9283, 14509), + new(30023, 32717, 32748), new(22940, 32032, 32626), new(14282, 27928, 31473), + new(8562, 21327, 27914), new(4846, 13393, 19919), new(29981, 32590, 32695), + new(20465, 30963, 32166), new(11479, 23579, 28195), new(5916, 15648, 22073), + new(3031, 8605, 13398), new(31146, 32691, 32739), new(23106, 31724, 32444), + new(13783, 26738, 30439), new(7852, 19468, 25807), new(3860, 11124, 16853), + new(31014, 32724, 32748), new(23629, 32109, 32628), new(14747, 28115, 31403), + new(8545, 21242, 27478), new(4574, 12781, 19067), new(8192, 16384, 24576) + ], + [ + new(9185, 19694, 24688), new(26081, 31985, 32621), new(16015, 29000, 31787), + new(10542, 23690, 29206), new(6732, 17945, 24677), new(3916, 11039, 16722), + new(28224, 32566, 32744), new(19100, 31138, 32485), new(12528, 26620, 30879), + new(7741, 20277, 26885), new(4566, 12845, 18990), new(29933, 32593, 32718), + new(17670, 30333, 32155), new(10385, 23600, 28909), new(6243, 16236, 22407), + new(3976, 10389, 16017), new(28377, 32561, 32738), new(19366, 31175, 32482), + new(13327, 27175, 31094), new(8258, 20769, 27143), new(4703, 13198, 19527), + new(31086, 32706, 32748), new(22853, 31902, 32583), new(14759, 28186, 31419), + new(9284, 22382, 28348), new(5585, 15192, 21868), new(28291, 32652, 32746), + new(19849, 32107, 32571), new(14834, 26818, 29214), new(10306, 22594, 28672), + new(6615, 17384, 23384), new(28947, 32604, 32745), new(25625, 32289, 32646), + new(18758, 28672, 31403), new(10017, 23430, 28523), new(6862, 15269, 22131), + new(23933, 32509, 32739), new(19927, 31495, 32631), new(11903, 26023, 30621), + new(7026, 20094, 27252), new(5998, 18106, 24437), new(8192, 16384, 24576) + ] + ], + [ + [ + new(4456, 11274, 15533), new(21219, 29079, 31616), new(11173, 23774, 28567), + new(7282, 18293, 24263), new(4890, 13286, 19115), new(1890, 5508, 8659), + new(26651, 32136, 32647), new(14630, 28254, 31455), new(8716, 21287, 27395), + new(5615, 15331, 22008), new(2675, 7700, 12150), new(29954, 32526, 32690), + new(16126, 28982, 31633), new(9030, 21361, 27352), new(5411, 14793, 21271), + new(2943, 8422, 13163), new(29539, 32601, 32730), new(18125, 30385, 32201), + new(10422, 24090, 29468), new(6468, 17487, 24438), new(2970, 8653, 13531), + new(30912, 32715, 32748), new(20666, 31373, 32497), new(12509, 26640, 30917), + new(8058, 20629, 27290), new(4231, 12006, 18052), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(10202, 20633, 25484), new(27336, 31445, 32352), new(12420, 24384, 28552), + new(7648, 18115, 23856), new(5662, 14341, 19902), new(3611, 10328, 15390), + new(30945, 32616, 32736), new(18682, 30505, 32253), new(11513, 25336, 30203), + new(7449, 19452, 26148), new(4482, 13051, 18886), new(32022, 32690, 32747), + new(18578, 30501, 32146), new(11249, 23368, 28631), new(5645, 16958, 22158), + new(5009, 11444, 16637), new(31357, 32710, 32748), new(21552, 31494, 32504), + new(13891, 27677, 31340), new(9051, 22098, 28172), new(5190, 13377, 19486), + new(32364, 32740, 32748), new(24839, 31907, 32551), new(17160, 28779, 31696), + new(12452, 24137, 29602), new(6165, 15389, 22477), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(2575, 7281, 11077), new(14002, 20866, 25402), new(6343, 15056, 19658), + new(4474, 11858, 17041), new(2865, 8299, 12534), new(1344, 3949, 6391), + new(24720, 31239, 32459), new(12585, 25356, 29968), new(7181, 18246, 24444), + new(5025, 13667, 19885), new(2521, 7304, 11605), new(29908, 32252, 32584), + new(17421, 29156, 31575), new(9889, 22188, 27782), new(5878, 15647, 22123), + new(2814, 8665, 13323), new(30183, 32568, 32713), new(18528, 30195, 32049), + new(10982, 24606, 29657), new(6957, 18165, 25231), new(3508, 10118, 15468), + new(31761, 32736, 32748), new(21041, 31328, 32546), new(12568, 26732, 31166), + new(8052, 20720, 27733), new(4336, 12192, 18396), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ], + [ + [ + [ + new(7062, 16472, 22319), new(24538, 32261, 32674), new(13675, 28041, 31779), + new(8590, 20674, 27631), new(5685, 14675, 22013), new(3655, 9898, 15731), + new(26493, 32418, 32658), new(16376, 29342, 32090), new(10594, 22649, 28970), + new(8176, 17170, 24303), new(5605, 12694, 19139), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(23888, 31902, 32542), new(18612, 29687, 31987), new(16245, 24852, 29249), + new(15765, 22608, 27559), new(19895, 24699, 27510), new(28401, 32212, 32457), + new(15274, 27825, 30980), new(9364, 18128, 24332), new(2283, 8193, 15082), + new(1228, 3972, 7881), new(29455, 32469, 32620), new(17981, 28245, 31388), + new(10921, 20098, 26240), new(3743, 11829, 18657), new(2374, 9593, 15715), + new(31068, 32466, 32635), new(20321, 29572, 31971), new(10771, 20255, 27119), + new(2795, 10410, 17361), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(9320, 22102, 27840), new(27057, 32464, 32724), new(16331, 30268, 32309), + new(10319, 23935, 29720), new(6189, 16448, 24106), new(3589, 10884, 18808), + new(29026, 32624, 32748), new(19226, 31507, 32587), new(12692, 26921, 31203), + new(7049, 19532, 27635), new(7727, 15669, 23252), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(28056, 32625, 32748), new(22383, 32075, 32669), new(15417, 27098, 31749), + new(18127, 26493, 27190), new(5461, 16384, 21845), new(27982, 32091, 32584), + new(19045, 29868, 31972), new(10397, 22266, 27932), new(5990, 13697, 21500), + new(1792, 6912, 15104), new(28198, 32501, 32718), new(21534, 31521, 32569), + new(11109, 25217, 30017), new(5671, 15124, 26151), new(4681, 14043, 18725), + new(28688, 32580, 32741), new(22576, 32079, 32661), new(10627, 22141, 28340), + new(9362, 14043, 28087), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(7754, 16948, 22142), new(25670, 32330, 32691), new(15663, 29225, 31994), + new(9878, 23288, 29158), new(6419, 17088, 24336), new(3859, 11003, 17039), + new(27562, 32595, 32725), new(17575, 30588, 32399), new(10819, 24838, 30309), + new(7124, 18686, 25916), new(4479, 12688, 19340), new(28385, 32476, 32673), + new(15306, 29005, 31938), new(8937, 21615, 28322), new(5982, 15603, 22786), + new(3620, 10267, 16136), new(27280, 32464, 32667), new(15607, 29160, 32004), + new(9091, 22135, 28740), new(6232, 16632, 24020), new(4047, 11377, 17672), + new(29220, 32630, 32718), new(19650, 31220, 32462), new(13050, 26312, 30827), + new(9228, 20870, 27468), new(6146, 15149, 21971), new(30169, 32481, 32623), + new(17212, 29311, 31554), new(9911, 21311, 26882), new(4487, 13314, 20372), + new(2570, 7772, 12889), new(30924, 32613, 32708), new(19490, 30206, 32107), + new(11232, 23998, 29276), new(6769, 17955, 25035), new(4398, 12623, 19214), + new(30609, 32627, 32722), new(19370, 30582, 32287), new(10457, 23619, 29409), + new(6443, 17637, 24834), new(4645, 13236, 20106), new(8192, 16384, 24576) + ], + [ + new(8626, 20271, 26216), new(26707, 32406, 32711), new(16999, 30329, 32286), + new(11445, 25123, 30286), new(6411, 18828, 25601), new(6801, 12458, 20248), + new(29918, 32682, 32748), new(20649, 31739, 32618), new(12879, 27773, 31581), + new(7896, 21751, 28244), new(5260, 14870, 23698), new(29252, 32593, 32731), + new(17072, 30460, 32294), new(10653, 24143, 29365), new(6536, 17490, 23983), + new(4929, 13170, 20085), new(28137, 32518, 32715), new(18171, 30784, 32407), + new(11437, 25436, 30459), new(7252, 18534, 26176), new(4126, 13353, 20978), + new(31162, 32726, 32748), new(23017, 32222, 32701), new(15629, 29233, 32046), + new(9387, 22621, 29480), new(6922, 17616, 25010), new(28838, 32265, 32614), + new(19701, 30206, 31920), new(11214, 22410, 27933), new(5320, 14177, 23034), + new(5049, 12881, 17827), new(27484, 32471, 32734), new(21076, 31526, 32561), + new(12707, 26303, 31211), new(8169, 21722, 28219), new(6045, 19406, 27042), + new(27753, 32572, 32745), new(20832, 31878, 32653), new(13250, 27356, 31674), + new(7718, 21508, 29858), new(7209, 18350, 25559), new(8192, 16384, 24576) + ] + ], + [ + [ + new(7876, 16901, 21741), new(24001, 31898, 32625), new(14529, 27959, 31451), + new(8273, 20818, 27258), new(5278, 14673, 21510), new(2983, 8843, 14039), + new(28016, 32574, 32732), new(17471, 30306, 32301), new(10224, 24063, 29728), + new(6602, 17954, 25052), new(4002, 11585, 17759), new(30190, 32634, 32739), + new(17497, 30282, 32270), new(10229, 23729, 29538), new(6344, 17211, 24440), + new(3849, 11189, 17108), new(28570, 32583, 32726), new(17521, 30161, 32238), + new(10153, 23565, 29378), new(6455, 17341, 24443), new(3907, 11042, 17024), + new(30689, 32715, 32748), new(21546, 31840, 32610), new(13547, 27581, 31459), + new(8912, 21757, 28309), new(5548, 15080, 22046), new(30783, 32540, 32685), + new(17540, 29528, 31668), new(10160, 21468, 26783), new(4724, 13393, 20054), + new(2702, 8174, 13102), new(31648, 32686, 32742), new(20954, 31094, 32337), + new(12420, 25698, 30179), new(7304, 19320, 26248), new(4366, 12261, 18864), + new(31581, 32723, 32748), new(21373, 31586, 32525), new(12744, 26625, 30885), + new(7431, 20322, 26950), new(4692, 13323, 20111), new(8192, 16384, 24576) + ], + [ + new(7833, 18369, 24095), new(26650, 32273, 32702), new(16371, 29961, 32191), + new(11055, 24082, 29629), new(6892, 18644, 25400), new(5006, 13057, 19240), + new(29834, 32666, 32748), new(19577, 31335, 32570), new(12253, 26509, 31122), + new(7991, 20772, 27711), new(5677, 15910, 23059), new(30109, 32532, 32720), + new(16747, 30166, 32252), new(10134, 23542, 29184), new(5791, 16176, 23556), + new(4362, 10414, 17284), new(29492, 32626, 32748), new(19894, 31402, 32525), + new(12942, 27071, 30869), new(8346, 21216, 27405), new(6572, 17087, 23859), + new(32035, 32735, 32748), new(22957, 31838, 32618), new(14724, 28572, 31772), + new(10364, 23999, 29553), new(7004, 18433, 25655), new(27528, 32277, 32681), + new(16959, 31171, 32096), new(10486, 23593, 27962), new(8192, 16384, 23211), + new(8937, 17873, 20852), new(27715, 32002, 32615), new(15073, 29491, 31676), + new(11264, 24576, 28672), new(2341, 18725, 23406), new(7282, 18204, 25486), + new(28547, 32213, 32657), new(20788, 29773, 32239), new(6780, 21469, 30508), + new(5958, 14895, 23831), new(16384, 21845, 27307), new(8192, 16384, 24576) + ] + ], + [ + [ + new(5992, 14304, 19765), new(22612, 31238, 32456), new(13456, 27162, 31087), + new(8001, 20062, 26504), new(5168, 14105, 20764), new(2632, 7771, 12385), + new(27034, 32344, 32709), new(15850, 29415, 31997), new(9494, 22776, 28841), + new(6151, 16830, 23969), new(3461, 10039, 15722), new(30134, 32569, 32731), + new(15638, 29422, 31945), new(9150, 21865, 28218), new(5647, 15719, 22676), + new(3402, 9772, 15477), new(28530, 32586, 32735), new(17139, 30298, 32292), + new(10200, 24039, 29685), new(6419, 17674, 24786), new(3544, 10225, 15824), + new(31333, 32726, 32748), new(20618, 31487, 32544), new(12901, 27217, 31232), + new(8624, 21734, 28171), new(5104, 14191, 20748), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(11206, 21090, 26561), new(28759, 32279, 32671), new(14171, 27952, 31569), + new(9743, 22907, 29141), new(6871, 17886, 24868), new(4960, 13152, 19315), + new(31077, 32661, 32748), new(19400, 31195, 32515), new(12752, 26858, 31040), + new(8370, 22098, 28591), new(5457, 15373, 22298), new(31697, 32706, 32748), + new(17860, 30657, 32333), new(12510, 24812, 29261), new(6180, 19124, 24722), + new(5041, 13548, 17959), new(31552, 32716, 32748), new(21908, 31769, 32623), + new(14470, 28201, 31565), new(9493, 22982, 28608), new(6858, 17240, 24137), + new(32543, 32752, 32756), new(24286, 32097, 32666), new(15958, 29217, 32024), + new(10207, 24234, 29958), new(6929, 18305, 25652), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ], + [ + [ + new(4137, 10847, 15682), new(17824, 27001, 30058), new(10204, 22796, 28291), + new(6076, 15935, 22125), new(3852, 10937, 16816), new(2252, 6324, 10131), + new(25840, 32016, 32662), new(15109, 28268, 31531), new(9385, 22231, 28340), + new(6082, 16672, 23479), new(3318, 9427, 14681), new(30594, 32574, 32718), + new(16836, 29552, 31859), new(9556, 22542, 28356), new(6305, 16725, 23540), + new(3376, 9895, 15184), new(29383, 32617, 32745), new(18891, 30809, 32401), + new(11688, 25942, 30687), new(7468, 19469, 26651), new(3909, 11358, 17012), + new(31564, 32736, 32748), new(20906, 31611, 32600), new(13191, 27621, 31537), + new(8768, 22029, 28676), new(5079, 14109, 20906), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ], + [ + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576), + new(8192, 16384, 24576), new(8192, 16384, 24576), new(8192, 16384, 24576) + ] + ] + ] + ]; + + private static Av1Distribution[][][][] BaseEndOfBlock => + [ + [ + [ + [new(17837, 29055), new(29600, 31446), new(30844, 31878), new(24926, 28948)], + [new(21365, 30026), new(30512, 32423), new(31658, 32621), new(29630, 31881)] + ], + [ + [new(5717, 26477), new(30491, 31703), new(31550, 32158), new(29648, 31491)], + [new(12608, 27820), new(30680, 32225), new(30809, 32335), new(31299, 32423)] + ], + [ + [new(1786, 12612), new(30663, 31625), new(32339, 32468), new(31148, 31833)], + [new(18857, 23865), new(31428, 32428), new(31744, 32373), new(31775, 32526)] + ], + [ + [new(1787, 2532), new(30832, 31662), new(31824, 32682), new(32133, 32569)], + [new(13751, 22235), new(32089, 32409), new(27084, 27920), new(29291, 32594)] + ], + [ + [new(1725, 3449), new(31102, 31935), new(32457, 32613), new(32412, 32649)], + [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)] + ] + ], + [ + [ + [new(17560, 29888), new(29671, 31549), new(31007, 32056), new(27286, 30006)], + [new(26594, 31212), new(31208, 32582), new(31835, 32637), new(30595, 32206)] + ], + [ + [new(15239, 29932), new(31315, 32095), new(32130, 32434), new(30864, 31996)], + [new(26279, 30968), new(31142, 32495), new(31713, 32540), new(31929, 32594)] + ], + [ + [new(2644, 25198), new(32038, 32451), new(32639, 32695), new(32166, 32518)], + [new(17187, 27668), new(31714, 32550), new(32283, 32678), new(31930, 32563)] + ], + [ + [new(1044, 2257), new(30755, 31923), new(32208, 32693), new(32244, 32615)], + [new(21317, 26207), new(29133, 30868), new(29311, 31231), new(29657, 31087)] + ], + [ + [new(478, 1834), new(31005, 31987), new(32317, 32724), new(30865, 32648)], + [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)] + ] + ], + [ + [ + [new(20092, 30774), new(30695, 32020), new(31131, 32103), new(28666, 30870)], + [new(27258, 31095), new(31804, 32623), new(31763, 32528), new(31438, 32506)] + ], + [ + [new(18049, 30489), new(31706, 32286), new(32163, 32473), new(31550, 32184)], + [new(27116, 30842), new(31971, 32598), new(32088, 32576), new(32067, 32664)] + ], + [ + [new(12854, 29093), new(32272, 32558), new(32667, 32729), new(32306, 32585)], + [new(25476, 30366), new(32169, 32687), new(32479, 32689), new(31673, 32634)] + ], + [ + [new(2809, 19301), new(32205, 32622), new(32338, 32730), new(31786, 32616)], + [new(22737, 29105), new(30810, 32362), new(30014, 32627), new(30528, 32574)] + ], + [ + [new(935, 3382), new(30789, 31909), new(32466, 32756), new(30860, 32513)], + [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)] + ] + ], + [ + [ + [new(22497, 31198), new(31715, 32495), new(31606, 32337), new(30388, 31990)], + [new(27877, 31584), new(32170, 32728), new(32155, 32688), new(32219, 32702)] + ], + [ + [new(21457, 31043), new(31951, 32483), new(32153, 32562), new(31473, 32215)], + [new(27558, 31151), new(32020, 32640), new(32097, 32575), new(32242, 32719)] + ], + [ + [new(19980, 30591), new(32219, 32597), new(32581, 32706), new(31803, 32287)], + [new(26473, 30507), new(32431, 32723), new(32196, 32611), new(31588, 32528)] + ], + [ + [new(24647, 30463), new(32412, 32695), new(32468, 32720), new(31269, 32523)], + [new(28482, 31505), new(32152, 32701), new(31732, 32598), new(31767, 32712)] + ], + [ + [new(12358, 24977), new(31331, 32385), new(32634, 32756), new(30411, 32548)], + [new(10923, 21845), new(10923, 21845), new(10923, 21845), new(10923, 21845)] + ] + ] + ]; + + private static Av1Distribution[][][] DcSign => + [ + [ + [new(128 * 125), new(128 * 102), new(128 * 147)], + [new(128 * 119), new(128 * 101), new(128 * 135)] + ], + [ + [new(128 * 125), new(128 * 102), new(128 * 147)], + [new(128 * 119), new(128 * 101), new(128 * 135)] + ], + [ + [new(128 * 125), new(128 * 102), new(128 * 147)], + [new(128 * 119), new(128 * 101), new(128 * 135)] + ], + [ + [new(128 * 125), new(128 * 102), new(128 * 147)], + [new(128 * 119), new(128 * 101), new(128 * 135)] + ], + ]; + + private static Av1Distribution[][][] TransformBlockSkip => + [ + [ + [ + new(31849), new(5892), new(12112), new(21935), new(20289), new(27473), new(32487), + new(7654), new(19473), new(29984), new(9961), new(30242), new(32117) + ], + [ + new(31548), new(1549), new(10130), new(16656), new(18591), new(26308), new(32537), + new(5403), new(18096), new(30003), new(16384), new(16384), new(16384) + ], + [ + new(29957), new(5391), new(18039), new(23566), new(22431), new(25822), new(32197), + new(3778), new(15336), new(28981), new(16384), new(16384), new(16384) + ], + [ + new(17920), new(1818), new(7282), new(25273), new(10923), new(31554), new(32624), + new(1366), new(15628), new(30462), new(146), new(5132), new(31657) + ], + [ + new(6308), new(117), new(1638), new(2161), new(16384), new(10923), new(30247), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(30371), new(7570), new(13155), new(20751), new(20969), new(27067), new(32013), + new(5495), new(17942), new(28280), new(16384), new(16384), new(16384) + ], + [ + new(31782), new(1836), new(10689), new(17604), new(21622), new(27518), new(32399), + new(4419), new(16294), new(28345), new(16384), new(16384), new(16384) + ], + [ + new(31901), new(10311), new(18047), new(24806), new(23288), new(27914), new(32296), + new(4215), new(15756), new(28341), new(16384), new(16384), new(16384) + ], + [ + new(26726), new(1045), new(11703), new(20590), new(18554), new(25970), new(31938), + new(5583), new(21313), new(29390), new(641), new(22265), new(31452) + ], + [ + new(26584), new(188), new(8847), new(24519), new(22938), new(30583), new(32608), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(29614), new(9068), new(12924), new(19538), new(17737), new(24619), new(30642), + new(4119), new(16026), new(25657), new(16384), new(16384), new(16384) + ], + [ + new(31957), new(3230), new(11153), new(18123), new(20143), new(26536), new(31986), + new(3050), new(14603), new(25155), new(16384), new(16384), new(16384) + ], + [ + new(32363), new(10692), new(19090), new(24357), new(24442), new(28312), new(32169), + new(3648), new(15690), new(26815), new(16384), new(16384), new(16384) + ], + [ + new(30669), new(3832), new(11663), new(18889), new(19782), new(23313), new(31330), + new(5124), new(18719), new(28468), new(3082), new(20982), new(29443) + ], + [ + new(28573), new(3183), new(17802), new(25977), new(26677), new(27832), new(32387), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(26887), new(6729), new(10361), new(17442), new(15045), new(22478), new(29072), + new(2713), new(11861), new(20773), new(16384), new(16384), new(16384) + ], + [ + new(31903), new(2044), new(7528), new(14618), new(16182), new(24168), new(31037), + new(2786), new(11194), new(20155), new(16384), new(16384), new(16384) + ], + [ + new(32510), new(8430), new(17318), new(24154), new(23674), new(28789), new(32139), + new(3440), new(13117), new(22702), new(16384), new(16384), new(16384) + ], + [ + new(31671), new(2056), new(11746), new(16852), new(18635), new(24715), new(31484), + new(4656), new(16074), new(24704), new(1806), new(14645), new(25336) + ], + [ + new(31539), new(8433), new(20576), new(27904), new(27852), new(30026), new(32441), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384) + ] + ] + ]; + + private static Av1Distribution[][][][] EndOfBlockExtra => + [ + [ + [ + [ + new(16384), new(16384), new(16384), new(16961), new(17223), new(7621), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(19069), new(22525), new(13377), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(20401), new(17025), new(12845), + new(12873), new(14094), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(20681), new(20701), new(15250), + new(15017), new(14928), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(23905), new(17194), new(16170), + new(17695), new(13826), new(15810), new(12036), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(23959), new(20799), new(19021), + new(16203), new(17886), new(14144), new(12010), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(27399), new(16327), new(18071), + new(19584), new(20721), new(18432), new(19560), new(10150), new(8805), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(24932), new(20833), new(12027), + new(16670), new(19914), new(15106), new(17662), new(13783), new(28756), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(23406), new(21845), new(18432), + new(16384), new(17096), new(12561), new(17320), new(22395), new(21370), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ] + ], + [ + [ + [ + new(16384), new(16384), new(16384), new(17471), new(20223), new(11357), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(20335), new(21667), new(14818), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(20430), new(20662), new(15367), + new(16970), new(14657), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(22117), new(22028), new(18650), + new(16042), new(15885), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(22409), new(21012), new(15650), + new(17395), new(15469), new(20205), new(19511), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(24220), new(22480), new(17737), + new(18916), new(19268), new(18412), new(18844), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(25991), new(20314), new(17731), + new(19678), new(18649), new(17307), new(21798), new(17549), new(15630), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(26585), new(21469), new(20432), + new(17735), new(19280), new(15235), new(20297), new(22471), new(28997), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(26605), new(11304), new(16726), + new(16560), new(20866), new(23524), new(19878), new(13469), new(23084), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ] + ], + [ + [ + [ + new(16384), new(16384), new(16384), new(18983), new(20512), new(14885), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(20090), new(19444), new(17286), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(19139), new(21487), new(18959), + new(20910), new(19089), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(20536), new(20664), new(20625), + new(19123), new(14862), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(19833), new(21502), new(17485), + new(20267), new(18353), new(23329), new(21478), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(22041), new(23434), new(20001), + new(20554), new(20951), new(20145), new(15562), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(23312), new(21607), new(16526), + new(18957), new(18034), new(18934), new(24247), new(16921), new(17080), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(26579), new(24910), new(18637), + new(19800), new(20388), new(9887), new(15642), new(30198), new(24721), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(26998), new(16737), new(17838), + new(18922), new(19515), new(18636), new(17333), new(15776), new(22658), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ] + ], + [ + [ + [ + new(16384), new(16384), new(16384), new(20177), new(20789), new(20262), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(21416), new(20855), new(23410), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(20238), new(21057), new(19159), + new(22337), new(20159), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(20125), new(20559), new(21707), + new(22296), new(17333), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(19941), new(20527), new(21470), + new(22487), new(19558), new(22354), new(20331), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(22752), new(25006), new(22075), + new(21576), new(17740), new(21690), new(19211), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(21442), new(22358), new(18503), + new(20291), new(19945), new(21294), new(21178), new(19400), new(10556), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(24648), new(24949), new(20708), + new(23905), new(20501), new(9558), new(9423), new(30365), new(19253), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ], + [ + [ + new(16384), new(16384), new(16384), new(26064), new(22098), new(19613), + new(20525), new(17595), new(16618), new(20497), new(18989), new(15513), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ], + [ + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384), new(16384), new(16384), + new(16384), new(16384), new(16384), new(16384) + ] + ] + ] + ]; + public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) { int qContext = GetQContext(baseQIndex); @@ -409,6 +2009,24 @@ public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) ]; } + public static Av1Distribution[][][] GetCoefficientsBaseRange(int baseQIndex) + => CoefficientsBaseRange[GetQContext(baseQIndex)]; + + public static Av1Distribution[][][] GetCoefficientsBase(int baseQIndex) + => CoefficientsBase[GetQContext(baseQIndex)]; + + public static Av1Distribution[][][] GetBaseEndOfBlock(int baseQIndex) + => BaseEndOfBlock[GetQContext(baseQIndex)]; + + public static Av1Distribution[][] GetDcSign(int baseQIndex) + => DcSign[GetQContext(baseQIndex)]; + + public static Av1Distribution[][] GetTransformBlockSkip(int baseQIndex) + => TransformBlockSkip[GetQContext(baseQIndex)]; + + public static Av1Distribution[][][] GetEndOfBlockExtra(int baseQIndex) + => EndOfBlockExtra[GetQContext(baseQIndex)]; + private static int GetQContext(int q) { if (q <= 20) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index b5340ecd26..ed22714deb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -23,12 +23,24 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[] filterIntra = Av1DefaultDistributions.FilterIntra; private readonly Av1Distribution[][] transformSize = Av1DefaultDistributions.TransformSize; private readonly Av1Distribution[][][] endOfBlockFlag; + private readonly Av1Distribution[][][] coefficientsBase; + private readonly Av1Distribution[][][] baseEndOfBlock; + private readonly Av1Distribution[][] dcSign; + private readonly Av1Distribution[][][] coefficientsBaseRange; + private readonly Av1Distribution[][] transformBlockSkip; + private readonly Av1Distribution[][][] endOfBlockExtra; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData, int qIndex) { this.reader = new Av1SymbolReader(tileData); this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); + this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); + this.baseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); + this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex); + this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex); + this.transformBlockSkip = Av1DefaultDistributions.GetTransformBlockSkip(qIndex); + this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); } public int ReadLiteral(int bitCount) @@ -188,17 +200,41 @@ public int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transfor return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1; } - public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) => throw new NotImplementedException(); + public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0; + } - public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) => throw new NotImplementedException(); + public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; + } - public int ReadBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) => throw new NotImplementedException(); + public int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); + } - public int ReadDcSign(Av1PlaneType planeType, int dcSignContext) => throw new NotImplementedException(); + public int ReadDcSign(Av1PlaneType planeType, int dcSignContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]); + } - public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) => throw new NotImplementedException(); + public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]); + } - public int ReadBase(int coeff_ctx, Av1TransformSize transformSizeContext, Av1PlaneType planeType) => throw new NotImplementedException(); + public int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.coefficientsBase[coefficientContext][(int)transformSizeContext][(int)planeType]); + } private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index c9ea1487d5..c5f8e0d98b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -642,7 +642,7 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) { - int coefficinetBaseRange = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + int coefficinetBaseRange = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); level += coefficinetBaseRange; if (coefficinetBaseRange < Av1Constants.BaseRangeSizeMinus1) { @@ -715,13 +715,13 @@ private static void ReadCoefficientsReverse2d(ref Av1SymbolDecoder reader, Av1Tr { int pos = scan[c]; int coeff_ctx = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); - int level = reader.ReadBase(pos, transformSizeContext, planeType); + int level = reader.ReadCoefficientsBase(pos, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { - int k = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); level += k; if (k < Av1Constants.BaseRangeSizeMinus1) { @@ -776,13 +776,13 @@ private static void ReadCoefficientsReverse(ref Av1SymbolDecoder reader, Av1Tran { int pos = scan[c]; int coeff_ctx = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); - int level = reader.ReadBase(coeff_ctx, transformSizeContext, planeType); + int level = reader.ReadCoefficientsBase(coeff_ctx, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { - int k = reader.ReadBaseRange(transformSizeContext, planeType, baseRangeContext); + int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); level += k; if (k < Av1Constants.BaseRangeSizeMinus1) { From 23d533a713eb4946484bfef25bca40405e8bcd19 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 6 Jul 2024 18:51:05 +0200 Subject: [PATCH 105/190] Some bug fixes --- .../Formats/Heif/Av1/Av1Constants.cs | 6 +- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 37 +- .../Heif/Av1/Transform/Av1ScanOrder.cs | 12 +- .../Av1/Transform/Av1ScanOrderConstants.cs | 1542 ++++++++--------- 6 files changed, 806 insertions(+), 795 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index a8f1a86658..b997165d74 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -153,14 +153,16 @@ internal static class Av1Constants public const int TransformPadEnd = 16; - public const int CoefficientContextBits = 6; + public const int CoefficientContextBitCount = 6; - public const int CoefficientContextMask = (1 << CoefficientContextBits) - 1; + public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1; public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd; public const int MaxTransformSize = 1 << 6; + public const int MaxTransformSizeUnit = MaxTransformSize >> 2; + public const int TransformPadTop = 2; public const int BaseRangeSizeMinus1 = 3; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 4bf6a99966..4c263d072c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -151,8 +151,8 @@ public Span GetCoefficients(int plane) => plane switch { 0 => (Span)this.coefficientsY, + 1 => (Span)this.coefficientsY, 2 => (Span)this.coefficientsY, - 3 => (Span)this.coefficientsY, _ => null, }; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index ed22714deb..01226aef26 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -233,7 +233,7 @@ public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneTyp public int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.coefficientsBase[coefficientContext][(int)transformSizeContext][(int)planeType]); + return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index c5f8e0d98b..44d61813b9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Formats.Asn1; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -216,18 +214,18 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } else if (hasRows && hasColumns) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); partitionType = reader.ReadPartitionType(ctx); } else if (hasColumns) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } else if (hasRows) { - int ctx = this.GetPartitionContext(modeInfoLocation, blockSize, tileInfo, this.FrameInfo.ModeInfoRowCount); + int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; } @@ -666,7 +664,8 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part } } - coefficientBuffer[this.coefficientIndex[plane]] = endOfBlock; + DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); + coefficientBuffer[0] = endOfBlock; for (int c = 0; c < endOfBlock; c++) { int sign = 0; @@ -890,11 +889,11 @@ private static void SetDcSign(ref int culLevel, int dcValue) { if (dcValue < 0) { - culLevel |= 1 << Av1Constants.CoefficientContextBits; + culLevel |= 1 << Av1Constants.CoefficientContextBitCount; } else if (dcValue > 0) { - culLevel += 2 << Av1Constants.CoefficientContextBits; + culLevel += 2 << Av1Constants.CoefficientContextBitCount; } } @@ -1053,16 +1052,26 @@ private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize trans int[] leftContext = this.leftNeighborContext.GetContext(plane); int dcSign = 0; int k = 0; - int mask = (1 << Av1Constants.CoefficientContextCount) - 1; + int mask = (1 << Av1Constants.CoefficientContextBitCount) - 1; do { - int sign = aboveContext[k] >> Av1Constants.CoefficientContextCount; - DebugGuard.MustBeLessThanOrEqualTo(sign, 2, nameof(sign)); + uint sign = (uint)aboveContext[k] >> Av1Constants.CoefficientContextBitCount; + DebugGuard.MustBeLessThanOrEqualTo(sign, 2U, nameof(sign)); + dcSign += Signs[sign]; + } + while (++k < transformBlockUnitWideCount); + + k = 0; + do + { + uint sign = (uint)leftContext[k] >> Av1Constants.CoefficientContextBitCount; + DebugGuard.MustBeLessThanOrEqualTo(sign, 2U, nameof(sign)); dcSign += Signs[sign]; } while (++k < transformBlockUnitHighCount); - transformBlockContext.DcSignContext = dcSign; + + transformBlockContext.DcSignContext = DcSignContexts[dcSign + (Av1Constants.MaxTransformSizeUnit << 1)]; if (plane == 0) { @@ -1869,11 +1878,11 @@ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blo return xPos && yPos; }*/ - private int GetPartitionContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileLoc, int superblockModeInfoRowCount) + private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo) { // Maximum partition point is 8x8. Offset the log value occordingly. int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileLoc.ModeInfoColumnStart]; - int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockModeInfoRowCount) & Av1PartitionContext.Mask]; + int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask]; int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); int above = (aboveCtx >> blockSizeLog) & 0x1; int left = (leftCtx >> blockSizeLog) & 0x1; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs index 2046fe95d8..513ef27c61 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -5,18 +5,18 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal readonly struct Av1ScanOrder { - public Av1ScanOrder(short[]? scan) + public Av1ScanOrder(short[] scan) { - this.Scan = scan ?? []; + this.Scan = scan; this.IScan = []; this.Neighbors = []; } - public Av1ScanOrder(short[]? scan, short[]? iscan, short[]? neighbors) + public Av1ScanOrder(short[] scan, short[] iscan, short[] neighbors) { - this.Scan = scan!; - this.IScan = iscan!; - this.Neighbors = neighbors!; + this.Scan = scan; + this.IScan = iscan; + this.Neighbors = neighbors; } public short[] Scan { get; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs index 229d2fca58..f3ef2c9e06 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs @@ -8,519 +8,125 @@ internal static class Av1ScanOrderConstants public const int QuantizationMatrixLevelBitCount = 4; public const int QuantizationMatrixLevelCount = 1 << QuantizationMatrixLevelBitCount; - private static readonly Av1ScanOrder[][] ScanOrders = - [ + private static readonly short[] DefaultScan4x4 = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + private static readonly short[] DefaultScan8x8 = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, + 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, + 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]; - // Transform size 4x4 - [ - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), - ], + private static readonly short[] DefaultScan16x16 = [ + 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96, + 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, 113, 98, 83, 68, 53, 38, 23, + 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, + 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, + 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 224, 209, 194, 179, 164, + 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, + 195, 210, 225, 240, 241, 226, 211, 196, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77, + 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, 93, + 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, 185, 170, 155, 140, + 125, 110, 95, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127, + 143, 158, 173, 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, 220, 235, 250, 251, + 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, 223, 239, 254, 255]; - // Transform size 8x8 - [ - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), - ], + private static readonly short[] DefaultScan32x32 = [ + 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160, + 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, 224, 256, 225, 194, 163, 132, 101, + 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103, + 72, 41, 10, 11, 42, 73, 104, 135, 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229, + 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, 199, 230, 261, 292, 323, 354, 385, 416, + 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170, + 201, 232, 263, 294, 325, 356, 387, 418, 449, 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202, + 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, 451, + 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, + 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608, + 640, 609, 578, 547, 516, 485, 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, 20, + 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, 486, 517, 548, 579, 610, 641, + 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, + 84, 53, 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550, + 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, 613, 582, 551, 520, 489, 458, 427, 396, 365, 334, + 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, 56, 87, 118, 149, 180, 211, 242, 273, 304, 335, + 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677, + 646, 615, 584, 553, 522, 491, 460, 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, + 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, 368, 399, 430, 461, 492, 523, 554, 585, 616, 647, + 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493, + 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184, + 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, 556, 587, 618, 649, 680, 711, 742, 773, 804, 835, + 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, 433, + 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248, + 279, 310, 341, 372, 403, 434, 465, 496, 527, 558, 589, 620, 651, 682, 713, 744, 775, 806, 837, 868, 899, + 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, 590, 559, 528, 497, 466, + 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312, + 343, 374, 405, 436, 467, 498, 529, 560, 591, 622, 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963, + 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, 685, 654, 623, 592, 561, 530, 499, 468, 437, 406, + 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, 469, 500, + 531, 562, 593, 624, 655, 686, 717, 748, 779, 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873, + 842, 811, 780, 749, 718, 687, 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, + 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, 626, 657, 688, 719, 750, 781, 812, + 843, 874, 905, 936, 967, 998, 999, 968, 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565, + 534, 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, 535, 566, 597, + 628, 659, 690, 721, 752, 783, 814, 845, 876, 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784, + 753, 722, 691, 660, 629, 598, 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, 382, 413, 444, 475, 506, + 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, 1003, 972, 941, 910, 879, + 848, 817, 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539, + 570, 601, 632, 663, 694, 725, 756, 787, 818, 849, 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850, + 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, 510, 541, 572, 603, 634, 665, 696, + 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697, + 666, 635, 604, 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977, + 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, 730, 699, 668, 637, 606, 575, 607, 638, 669, 700, 731, + 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, 763, 732, 701, 670, + 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796, + 765, 734, 703, 735, 766, 797, 828, 859, 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798, + 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, 986, 955, 924, 893, 862, 831, 863, 894, 925, 956, 987, + 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023]; - // Transform size 16x16 - [ - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), - ], + private static readonly short[] DefaultScan4x8 = [ + 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14, + 17, 20, 15, 18, 21, 24, 19, 22, 25, 28, 23, 26, 29, 27, 30, 31,]; - // Transform size 32x32 - [ - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - ], - [ + private static readonly short[] DefaultScan8x4 = [ + 0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 25, 18, 11, 4, 26, 19, + 12, 5, 27, 20, 13, 6, 28, 21, 14, 7, 29, 22, 15, 30, 23, 31,]; - // Transform size 64X64 - // Half of the coefficients of tx64 at higher frequencies are set to - // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - ], - [ + private static readonly short[] DefaultScan8x16 = [ + 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6, + 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64, + 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74, + 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84, + 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 87, 94, 101, + 108, 115, 122, 95, 102, 109, 116, 123, 103, 110, 117, 124, 111, 118, 125, 119, 126, 127,]; - // Transform size 4X8 - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), - ], - [ + private static readonly short[] DefaultScan16x8 = [ + 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 64, 49, 34, 19, 4, 80, 65, 50, 35, 20, 5, 96, + 81, 66, 51, 36, 21, 6, 112, 97, 82, 67, 52, 37, 22, 7, 113, 98, 83, 68, 53, 38, 23, 8, + 114, 99, 84, 69, 54, 39, 24, 9, 115, 100, 85, 70, 55, 40, 25, 10, 116, 101, 86, 71, 56, 41, + 26, 11, 117, 102, 87, 72, 57, 42, 27, 12, 118, 103, 88, 73, 58, 43, 28, 13, 119, 104, 89, 74, + 59, 44, 29, 14, 120, 105, 90, 75, 60, 45, 30, 15, 121, 106, 91, 76, 61, 46, 31, 122, 107, 92, + 77, 62, 47, 123, 108, 93, 78, 63, 124, 109, 94, 79, 125, 110, 95, 126, 111, 127,]; - // Transform size 8X4 - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), - ], - [ - - // Transform size 8X16 - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), - ], - [ - - // Transform size 16X8 - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), - ], - [ - - // Transform size 16X32 - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - ], - [ - - // Transform size 32X16 - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - ], - [ - - // Transform size 32X64 - // Half of the coefficients of tx64 at higher frequencies are set to - // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - ], - [ - - // Transform size 64X32 - // Half of the coefficients of tx64 at higher frequencies are set to - // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - ], - [ - - // Transform size 4X16 - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), - ], - [ - - // Transform size 16X4 - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), - ], - [ - - // Transform size 8X32 - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), - ], - [ - - // Transform size 32X8 - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), - ], - [ - - // Transform size 16X64 - // Half of the coefficients of tx64 at higher frequencies are set to - // zeros. So tx32's scan order is used. - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - ], - [ - - // Transform size 64X16 - // Half of the coefficients of tx64 at higher frequencies are set to - // zeros. So tx32's scan order is used. - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - ] - ]; - - private static readonly short[] DefaultScan4x4 = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; - private static readonly short[] DefaultScan8x8 = [ - 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, - 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, - 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]; - - private static readonly short[] DefaultScan16x16 = [ - 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96, - 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, 113, 98, 83, 68, 53, 38, 23, - 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, - 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, - 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 224, 209, 194, 179, 164, - 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, - 195, 210, 225, 240, 241, 226, 211, 196, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77, - 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, 93, - 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, 185, 170, 155, 140, - 125, 110, 95, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127, - 143, 158, 173, 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, 220, 235, 250, 251, - 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, 223, 239, 254, 255]; - - private static readonly short[] DefaultScan32x32 = [ - 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160, - 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, 224, 256, 225, 194, 163, 132, 101, - 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103, - 72, 41, 10, 11, 42, 73, 104, 135, 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229, - 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, 199, 230, 261, 292, 323, 354, 385, 416, - 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170, - 201, 232, 263, 294, 325, 356, 387, 418, 449, 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202, - 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, 451, - 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, 111, 80, 49, - 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608, - 640, 609, 578, 547, 516, 485, 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, 20, - 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, 486, 517, 548, 579, 610, 641, - 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, - 84, 53, 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550, - 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, 613, 582, 551, 520, 489, 458, 427, 396, 365, 334, - 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, 56, 87, 118, 149, 180, 211, 242, 273, 304, 335, - 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677, - 646, 615, 584, 553, 522, 491, 460, 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, - 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, 368, 399, 430, 461, 492, 523, 554, 585, 616, 647, - 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493, - 462, 431, 400, 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184, - 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, 556, 587, 618, 649, 680, 711, 742, 773, 804, 835, - 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, 433, - 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248, - 279, 310, 341, 372, 403, 434, 465, 496, 527, 558, 589, 620, 651, 682, 713, 744, 775, 806, 837, 868, 899, - 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, 590, 559, 528, 497, 466, - 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312, - 343, 374, 405, 436, 467, 498, 529, 560, 591, 622, 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963, - 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, 685, 654, 623, 592, 561, 530, 499, 468, 437, 406, - 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, 469, 500, - 531, 562, 593, 624, 655, 686, 717, 748, 779, 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873, - 842, 811, 780, 749, 718, 687, 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, - 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, 626, 657, 688, 719, 750, 781, 812, - 843, 874, 905, 936, 967, 998, 999, 968, 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565, - 534, 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, 535, 566, 597, - 628, 659, 690, 721, 752, 783, 814, 845, 876, 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784, - 753, 722, 691, 660, 629, 598, 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, 382, 413, 444, 475, 506, - 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, 1003, 972, 941, 910, 879, - 848, 817, 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539, - 570, 601, 632, 663, 694, 725, 756, 787, 818, 849, 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850, - 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, 510, 541, 572, 603, 634, 665, 696, - 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697, - 666, 635, 604, 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977, - 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, 730, 699, 668, 637, 606, 575, 607, 638, 669, 700, 731, - 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, 763, 732, 701, 670, - 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796, - 765, 734, 703, 735, 766, 797, 828, 859, 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798, - 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, 986, 955, 924, 893, 862, 831, 863, 894, 925, 956, 987, - 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023]; - - private static readonly short[] DefaultScan4x8 = [ - 0, 1, 4, 2, 5, 8, 3, 6, 9, 12, 7, 10, 13, 16, 11, 14, - 17, 20, 15, 18, 21, 24, 19, 22, 25, 28, 23, 26, 29, 27, 30, 31,]; - - private static readonly short[] DefaultScan8x4 = [ - 0, 8, 1, 16, 9, 2, 24, 17, 10, 3, 25, 18, 11, 4, 26, 19, - 12, 5, 27, 20, 13, 6, 28, 21, 14, 7, 29, 22, 15, 30, 23, 31,]; - - private static readonly short[] DefaultScan8x16 = [ - 0, 1, 8, 2, 9, 16, 3, 10, 17, 24, 4, 11, 18, 25, 32, 5, 12, 19, 26, 33, 40, 6, - 13, 20, 27, 34, 41, 48, 7, 14, 21, 28, 35, 42, 49, 56, 15, 22, 29, 36, 43, 50, 57, 64, - 23, 30, 37, 44, 51, 58, 65, 72, 31, 38, 45, 52, 59, 66, 73, 80, 39, 46, 53, 60, 67, 74, - 81, 88, 47, 54, 61, 68, 75, 82, 89, 96, 55, 62, 69, 76, 83, 90, 97, 104, 63, 70, 77, 84, - 91, 98, 105, 112, 71, 78, 85, 92, 99, 106, 113, 120, 79, 86, 93, 100, 107, 114, 121, 87, 94, 101, - 108, 115, 122, 95, 102, 109, 116, 123, 103, 110, 117, 124, 111, 118, 125, 119, 126, 127,]; - - private static readonly short[] DefaultScan16x8 = [ - 0, 16, 1, 32, 17, 2, 48, 33, 18, 3, 64, 49, 34, 19, 4, 80, 65, 50, 35, 20, 5, 96, - 81, 66, 51, 36, 21, 6, 112, 97, 82, 67, 52, 37, 22, 7, 113, 98, 83, 68, 53, 38, 23, 8, - 114, 99, 84, 69, 54, 39, 24, 9, 115, 100, 85, 70, 55, 40, 25, 10, 116, 101, 86, 71, 56, 41, - 26, 11, 117, 102, 87, 72, 57, 42, 27, 12, 118, 103, 88, 73, 58, 43, 28, 13, 119, 104, 89, 74, - 59, 44, 29, 14, 120, 105, 90, 75, 60, 45, 30, 15, 121, 106, 91, 76, 61, 46, 31, 122, 107, 92, - 77, 62, 47, 123, 108, 93, 78, 63, 124, 109, 94, 79, 125, 110, 95, 126, 111, 127,]; - - private static readonly short[] DefaultScan16x32 = [ - 0, 1, 16, 2, 17, 32, 3, 18, 33, 48, 4, 19, 34, 49, 64, 5, 20, 35, 50, 65, 80, 6, 21, - 36, 51, 66, 81, 96, 7, 22, 37, 52, 67, 82, 97, 112, 8, 23, 38, 53, 68, 83, 98, 113, 128, 9, - 24, 39, 54, 69, 84, 99, 114, 129, 144, 10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 11, 26, 41, - 56, 71, 86, 101, 116, 131, 146, 161, 176, 12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192, 13, - 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 14, 29, 44, 59, 74, 89, 104, 119, 134, 149, - 164, 179, 194, 209, 224, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 31, 46, - 61, 76, 91, 106, 121, 136, 151, 166, 181, 196, 211, 226, 241, 256, 47, 62, 77, 92, 107, 122, 137, 152, 167, - 182, 197, 212, 227, 242, 257, 272, 63, 78, 93, 108, 123, 138, 153, 168, 183, 198, 213, 228, 243, 258, 273, 288, - 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 259, 274, 289, 304, 95, 110, 125, 140, 155, 170, 185, - 200, 215, 230, 245, 260, 275, 290, 305, 320, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 261, 276, 291, 306, - 321, 336, 127, 142, 157, 172, 187, 202, 217, 232, 247, 262, 277, 292, 307, 322, 337, 352, 143, 158, 173, 188, 203, - 218, 233, 248, 263, 278, 293, 308, 323, 338, 353, 368, 159, 174, 189, 204, 219, 234, 249, 264, 279, 294, 309, 324, - 339, 354, 369, 384, 175, 190, 205, 220, 235, 250, 265, 280, 295, 310, 325, 340, 355, 370, 385, 400, 191, 206, 221, - 236, 251, 266, 281, 296, 311, 326, 341, 356, 371, 386, 401, 416, 207, 222, 237, 252, 267, 282, 297, 312, 327, 342, - 357, 372, 387, 402, 417, 432, 223, 238, 253, 268, 283, 298, 313, 328, 343, 358, 373, 388, 403, 418, 433, 448, 239, - 254, 269, 284, 299, 314, 329, 344, 359, 374, 389, 404, 419, 434, 449, 464, 255, 270, 285, 300, 315, 330, 345, 360, - 375, 390, 405, 420, 435, 450, 465, 480, 271, 286, 301, 316, 331, 346, 361, 376, 391, 406, 421, 436, 451, 466, 481, - 496, 287, 302, 317, 332, 347, 362, 377, 392, 407, 422, 437, 452, 467, 482, 497, 303, 318, 333, 348, 363, 378, 393, - 408, 423, 438, 453, 468, 483, 498, 319, 334, 349, 364, 379, 394, 409, 424, 439, 454, 469, 484, 499, 335, 350, 365, - 380, 395, 410, 425, 440, 455, 470, 485, 500, 351, 366, 381, 396, 411, 426, 441, 456, 471, 486, 501, 367, 382, 397, - 412, 427, 442, 457, 472, 487, 502, 383, 398, 413, 428, 443, 458, 473, 488, 503, 399, 414, 429, 444, 459, 474, 489, - 504, 415, 430, 445, 460, 475, 490, 505, 431, 446, 461, 476, 491, 506, 447, 462, 477, 492, 507, 463, 478, 493, 508, - 479, 494, 509, 495, 510, 511,]; + private static readonly short[] DefaultScan16x32 = [ + 0, 1, 16, 2, 17, 32, 3, 18, 33, 48, 4, 19, 34, 49, 64, 5, 20, 35, 50, 65, 80, 6, 21, + 36, 51, 66, 81, 96, 7, 22, 37, 52, 67, 82, 97, 112, 8, 23, 38, 53, 68, 83, 98, 113, 128, 9, + 24, 39, 54, 69, 84, 99, 114, 129, 144, 10, 25, 40, 55, 70, 85, 100, 115, 130, 145, 160, 11, 26, 41, + 56, 71, 86, 101, 116, 131, 146, 161, 176, 12, 27, 42, 57, 72, 87, 102, 117, 132, 147, 162, 177, 192, 13, + 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, 178, 193, 208, 14, 29, 44, 59, 74, 89, 104, 119, 134, 149, + 164, 179, 194, 209, 224, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 31, 46, + 61, 76, 91, 106, 121, 136, 151, 166, 181, 196, 211, 226, 241, 256, 47, 62, 77, 92, 107, 122, 137, 152, 167, + 182, 197, 212, 227, 242, 257, 272, 63, 78, 93, 108, 123, 138, 153, 168, 183, 198, 213, 228, 243, 258, 273, 288, + 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 259, 274, 289, 304, 95, 110, 125, 140, 155, 170, 185, + 200, 215, 230, 245, 260, 275, 290, 305, 320, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 261, 276, 291, 306, + 321, 336, 127, 142, 157, 172, 187, 202, 217, 232, 247, 262, 277, 292, 307, 322, 337, 352, 143, 158, 173, 188, 203, + 218, 233, 248, 263, 278, 293, 308, 323, 338, 353, 368, 159, 174, 189, 204, 219, 234, 249, 264, 279, 294, 309, 324, + 339, 354, 369, 384, 175, 190, 205, 220, 235, 250, 265, 280, 295, 310, 325, 340, 355, 370, 385, 400, 191, 206, 221, + 236, 251, 266, 281, 296, 311, 326, 341, 356, 371, 386, 401, 416, 207, 222, 237, 252, 267, 282, 297, 312, 327, 342, + 357, 372, 387, 402, 417, 432, 223, 238, 253, 268, 283, 298, 313, 328, 343, 358, 373, 388, 403, 418, 433, 448, 239, + 254, 269, 284, 299, 314, 329, 344, 359, 374, 389, 404, 419, 434, 449, 464, 255, 270, 285, 300, 315, 330, 345, 360, + 375, 390, 405, 420, 435, 450, 465, 480, 271, 286, 301, 316, 331, 346, 361, 376, 391, 406, 421, 436, 451, 466, 481, + 496, 287, 302, 317, 332, 347, 362, 377, 392, 407, 422, 437, 452, 467, 482, 497, 303, 318, 333, 348, 363, 378, 393, + 408, 423, 438, 453, 468, 483, 498, 319, 334, 349, 364, 379, 394, 409, 424, 439, 454, 469, 484, 499, 335, 350, 365, + 380, 395, 410, 425, 440, 455, 470, 485, 500, 351, 366, 381, 396, 411, 426, 441, 456, 471, 486, 501, 367, 382, 397, + 412, 427, 442, 457, 472, 487, 502, 383, 398, 413, 428, 443, 458, 473, 488, 503, 399, 414, 429, 444, 459, 474, 489, + 504, 415, 430, 445, 460, 475, 490, 505, 431, 446, 461, 476, 491, 506, 447, 462, 477, 492, 507, 463, 478, 493, 508, + 479, 494, 509, 495, 510, 511,]; private static readonly short[] DefaultScan32x16 = [ 0, 32, 1, 64, 33, 2, 96, 65, 34, 3, 128, 97, 66, 35, 4, 160, 129, 98, 67, 36, 5, 192, 161, @@ -756,26 +362,180 @@ internal static class Av1ScanOrderConstants 230, 238, 246, 254, 7, 15, 23, 31, 39, 47, 55, 63, 71, 79, 87, 95, 103, 111, 119, 127, 135, 143, 151, 159, 167, 175, 183, 191, 199, 207, 215, 223, 231, 239, 247, 255,]; - private static readonly short[] MatrixColumnScan32x8 = [ - 0, 32, 64, 96, 128, 160, 192, 224, 1, 33, 65, 97, 129, 161, 193, 225, 2, 34, 66, 98, 130, 162, 194, 226, - 3, 35, 67, 99, 131, 163, 195, 227, 4, 36, 68, 100, 132, 164, 196, 228, 5, 37, 69, 101, 133, 165, 197, 229, - 6, 38, 70, 102, 134, 166, 198, 230, 7, 39, 71, 103, 135, 167, 199, 231, 8, 40, 72, 104, 136, 168, 200, 232, - 9, 41, 73, 105, 137, 169, 201, 233, 10, 42, 74, 106, 138, 170, 202, 234, 11, 43, 75, 107, 139, 171, 203, 235, - 12, 44, 76, 108, 140, 172, 204, 236, 13, 45, 77, 109, 141, 173, 205, 237, 14, 46, 78, 110, 142, 174, 206, 238, - 15, 47, 79, 111, 143, 175, 207, 239, 16, 48, 80, 112, 144, 176, 208, 240, 17, 49, 81, 113, 145, 177, 209, 241, - 18, 50, 82, 114, 146, 178, 210, 242, 19, 51, 83, 115, 147, 179, 211, 243, 20, 52, 84, 116, 148, 180, 212, 244, - 21, 53, 85, 117, 149, 181, 213, 245, 22, 54, 86, 118, 150, 182, 214, 246, 23, 55, 87, 119, 151, 183, 215, 247, - 24, 56, 88, 120, 152, 184, 216, 248, 25, 57, 89, 121, 153, 185, 217, 249, 26, 58, 90, 122, 154, 186, 218, 250, - 27, 59, 91, 123, 155, 187, 219, 251, 28, 60, 92, 124, 156, 188, 220, 252, 29, 61, 93, 125, 157, 189, 221, 253, - 30, 62, 94, 126, 158, 190, 222, 254, 31, 63, 95, 127, 159, 191, 223, 255,]; + private static readonly short[] MatrixColumnScan32x8 = [ + 0, 32, 64, 96, 128, 160, 192, 224, 1, 33, 65, 97, 129, 161, 193, 225, 2, 34, 66, 98, 130, 162, 194, 226, + 3, 35, 67, 99, 131, 163, 195, 227, 4, 36, 68, 100, 132, 164, 196, 228, 5, 37, 69, 101, 133, 165, 197, 229, + 6, 38, 70, 102, 134, 166, 198, 230, 7, 39, 71, 103, 135, 167, 199, 231, 8, 40, 72, 104, 136, 168, 200, 232, + 9, 41, 73, 105, 137, 169, 201, 233, 10, 42, 74, 106, 138, 170, 202, 234, 11, 43, 75, 107, 139, 171, 203, 235, + 12, 44, 76, 108, 140, 172, 204, 236, 13, 45, 77, 109, 141, 173, 205, 237, 14, 46, 78, 110, 142, 174, 206, 238, + 15, 47, 79, 111, 143, 175, 207, 239, 16, 48, 80, 112, 144, 176, 208, 240, 17, 49, 81, 113, 145, 177, 209, 241, + 18, 50, 82, 114, 146, 178, 210, 242, 19, 51, 83, 115, 147, 179, 211, 243, 20, 52, 84, 116, 148, 180, 212, 244, + 21, 53, 85, 117, 149, 181, 213, 245, 22, 54, 86, 118, 150, 182, 214, 246, 23, 55, 87, 119, 151, 183, 215, 247, + 24, 56, 88, 120, 152, 184, 216, 248, 25, 57, 89, 121, 153, 185, 217, 249, 26, 58, 90, 122, 154, 186, 218, 250, + 27, 59, 91, 123, 155, 187, 219, 251, 28, 60, 92, 124, 156, 188, 220, 252, 29, 61, 93, 125, 157, 189, 221, 253, + 30, 62, 94, 126, 158, 190, 222, 254, 31, 63, 95, 127, 159, 191, 223, 255,]; + + private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + private static readonly short[] MatrixRowScan8x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; + + private static readonly short[] MatrixRowScan16x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + + private static readonly short[] MatrixRowScan32x32 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, + 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, + 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, + 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, + 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, + 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, + 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, + 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, + 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, + 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, + 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, + 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, + 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, + 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, + 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, + 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, + 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, + 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, + 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, + 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, + 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, + 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, + 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, + 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, + 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, + 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, + 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, + 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, + 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, + 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, + 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, + 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, + 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, + 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, + 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, + 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, + 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, + 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, + 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, + 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023,]; + + private static readonly short[] MatrixRowScan4x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + + private static readonly short[] MatrixRowScan8x4 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + + private static readonly short[] MatrixRowScan8x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + + private static readonly short[] MatrixRowScan16x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + + private static readonly short[] MatrixRowScan16x32 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, + 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, + 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, + 506, 507, 508, 509, 510, 511,]; + + private static readonly short[] MatrixRowScan32x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, + 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, + 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, + 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, + 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, + 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, + 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, + 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, + 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, + 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, + 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, + 506, 507, 508, 509, 510, 511,]; + + private static readonly short[] MatrixRowScan4x16 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; - private static readonly short[] MatrixRowScan4x4 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; - private static readonly short[] MatrixRowScan8x8 = [ + private static readonly short[] MatrixRowScan16x4 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]; + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; - private static readonly short[] MatrixRowScan16x16 = [ + private static readonly short[] MatrixRowScan8x32 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, @@ -789,271 +549,511 @@ internal static class Av1ScanOrderConstants 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; - private static readonly short[] MatrixRowScan32x32 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, - 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, - 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, - 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, - 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, - 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, - 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, - 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, - 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, - 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, - 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, - 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, - 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, - 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, - 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, - 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, - 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, - 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, - 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, - 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, - 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, - 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, - 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, - 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, - 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, - 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, - 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, - 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, - 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, - 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, - 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, - 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, - 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, - 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, - 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, - 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, - 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, - 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, - 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, - 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, - 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, - 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, - 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, - 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, - 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, - 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, - 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, - 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, - 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023,]; + private static readonly short[] MatrixRowScan32x8 = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, + 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, + 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, + 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, + 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, + 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + + // IScan is not used (yet) for AVIF coding, leave these arrays empty for now. + private static readonly short[] DefaultIScan4x4 = []; + private static readonly short[] DefaultIScan8x8 = []; + private static readonly short[] DefaultIScan16x16 = []; + private static readonly short[] DefaultIScan32x32 = []; + private static readonly short[] DefaultIScan64x64 = []; + private static readonly short[] DefaultIScan4x8 = []; + private static readonly short[] DefaultIScan8x4 = []; + private static readonly short[] DefaultIScan8x16 = []; + private static readonly short[] DefaultIScan16x8 = []; + private static readonly short[] DefaultIScan16x32 = []; + private static readonly short[] DefaultIScan32x16 = []; + private static readonly short[] DefaultIScan4x16 = []; + private static readonly short[] DefaultIScan16x4 = []; + private static readonly short[] DefaultIScan8x32 = []; + private static readonly short[] DefaultIScan32x8 = []; + + private static readonly short[] MatrixColumnIScan4x4 = []; + private static readonly short[] MatrixColumnIScan8x8 = []; + private static readonly short[] MatrixColumnIScan16x16 = []; + private static readonly short[] MatrixColumnIScan32x32 = []; + private static readonly short[] MatrixColumnIScan64x64 = []; + private static readonly short[] MatrixColumnIScan4x8 = []; + private static readonly short[] MatrixColumnIScan8x4 = []; + private static readonly short[] MatrixColumnIScan8x16 = []; + private static readonly short[] MatrixColumnIScan16x8 = []; + private static readonly short[] MatrixColumnIScan16x32 = []; + private static readonly short[] MatrixColumnIScan32x16 = []; + private static readonly short[] MatrixColumnIScan4x16 = []; + private static readonly short[] MatrixColumnIScan16x4 = []; + private static readonly short[] MatrixColumnIScan8x32 = []; + private static readonly short[] MatrixColumnIScan32x8 = []; + + private static readonly short[] MatrixRowIScan4x4 = []; + private static readonly short[] MatrixRowIScan8x8 = []; + private static readonly short[] MatrixRowIScan16x16 = []; + private static readonly short[] MatrixRowIScan32x32 = []; + private static readonly short[] MatrixRowIScan64x64 = []; + private static readonly short[] MatrixRowIScan4x8 = []; + private static readonly short[] MatrixRowIScan8x4 = []; + private static readonly short[] MatrixRowIScan8x16 = []; + private static readonly short[] MatrixRowIScan16x8 = []; + private static readonly short[] MatrixRowIScan16x32 = []; + private static readonly short[] MatrixRowIScan32x16 = []; + private static readonly short[] MatrixRowIScan4x16 = []; + private static readonly short[] MatrixRowIScan16x4 = []; + private static readonly short[] MatrixRowIScan8x32 = []; + private static readonly short[] MatrixRowIScan32x8 = []; + + // Neighborss are not used (yet) for AVIF coding, leave these arrays empty for now. + private static readonly short[] DefaultScan4x4Neighbors = []; + private static readonly short[] DefaultScan8x8Neighbors = []; + private static readonly short[] DefaultScan16x16Neighbors = []; + private static readonly short[] DefaultScan32x32Neighbors = []; + private static readonly short[] DefaultScan64x64Neighbors = []; + private static readonly short[] DefaultScan4x8Neighbors = []; + private static readonly short[] DefaultScan8x4Neighbors = []; + private static readonly short[] DefaultScan8x16Neighbors = []; + private static readonly short[] DefaultScan16x8Neighbors = []; + private static readonly short[] DefaultScan16x32Neighbors = []; + private static readonly short[] DefaultScan32x16Neighbors = []; + private static readonly short[] DefaultScan4x16Neighbors = []; + private static readonly short[] DefaultScan16x4Neighbors = []; + private static readonly short[] DefaultScan8x32Neighbors = []; + private static readonly short[] DefaultScan32x8Neighbors = []; + + private static readonly short[] MatrixColumnScan4x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x8Neighbors = []; + private static readonly short[] MatrixColumnScan16x16Neighbors = []; + private static readonly short[] MatrixColumnScan32x32Neighbors = []; + private static readonly short[] MatrixColumnScan64x64Neighbors = []; + private static readonly short[] MatrixColumnScan4x8Neighbors = []; + private static readonly short[] MatrixColumnScan8x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x16Neighbors = []; + private static readonly short[] MatrixColumnScan16x8Neighbors = []; + private static readonly short[] MatrixColumnScan16x32Neighbors = []; + private static readonly short[] MatrixColumnScan32x16Neighbors = []; + private static readonly short[] MatrixColumnScan4x16Neighbors = []; + private static readonly short[] MatrixColumnScan16x4Neighbors = []; + private static readonly short[] MatrixColumnScan8x32Neighbors = []; + private static readonly short[] MatrixColumnScan32x8Neighbors = []; + + private static readonly short[] MatrixRowScan4x4Neighbors = []; + private static readonly short[] MatrixRowScan8x8Neighbors = []; + private static readonly short[] MatrixRowScan16x16Neighbors = []; + private static readonly short[] MatrixRowScan32x32Neighbors = []; + private static readonly short[] MatrixRowScan64x64Neighbors = []; + private static readonly short[] MatrixRowScan4x8Neighbors = []; + private static readonly short[] MatrixRowScan8x4Neighbors = []; + private static readonly short[] MatrixRowScan8x16Neighbors = []; + private static readonly short[] MatrixRowScan16x8Neighbors = []; + private static readonly short[] MatrixRowScan16x32Neighbors = []; + private static readonly short[] MatrixRowScan32x16Neighbors = []; + private static readonly short[] MatrixRowScan4x16Neighbors = []; + private static readonly short[] MatrixRowScan16x4Neighbors = []; + private static readonly short[] MatrixRowScan8x32Neighbors = []; + private static readonly short[] MatrixRowScan32x8Neighbors = []; + + private static readonly Av1ScanOrder[][] ScanOrders = + [ + + // Transform size 4x4 + [ + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + ], + + // Transform size 8x8 + [ + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + ], + + // Transform size 16x16 + [ + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + ], - private static readonly short[] MatrixRowScan4x8 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + // Transform size 32x32 + [ + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ - private static readonly short[] MatrixRowScan8x4 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; + // Transform size 64X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ - private static readonly short[] MatrixRowScan8x16 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + // Transform size 4X8 + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + ], + [ - private static readonly short[] MatrixRowScan16x8 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; + // Transform size 8X4 + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + ], + [ - private static readonly short[] MatrixRowScan16x32 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, - 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, - 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, - 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, - 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, - 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, - 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, - 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, - 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, - 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, - 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, - 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, - 506, 507, 508, 509, 510, 511,]; + // Transform size 8X16 + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + ], + [ - private static readonly short[] MatrixRowScan32x16 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, - 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, - 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, - 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, - 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, - 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, - 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, - 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, - 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, - 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, - 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, - 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, - 506, 507, 508, 509, 510, 511,]; + // Transform size 16X8 + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + ], + [ - private static readonly short[] MatrixRowScan4x16 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; + // Transform size 16X32 + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + ], + [ - private static readonly short[] MatrixRowScan16x4 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,]; + // Transform size 32X16 + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + ], + [ - private static readonly short[] MatrixRowScan8x32 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, - 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, - 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, - 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, - 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + // Transform size 32X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ - private static readonly short[] MatrixRowScan32x8 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, - 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, - 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, - 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, - 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, - 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, - 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, - 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, - 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, - 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; + // Transform size 64X32 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), + new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + ], + [ - // IScan is not used (yet) for AVIF coding, leave these arrays empty for now. - private static readonly short[] DefaultIScan4x4 = []; - private static readonly short[] DefaultIScan8x8 = []; - private static readonly short[] DefaultIScan16x16 = []; - private static readonly short[] DefaultIScan32x32 = []; - private static readonly short[] DefaultIScan64x64 = []; - private static readonly short[] DefaultIScan4x8 = []; - private static readonly short[] DefaultIScan8x4 = []; - private static readonly short[] DefaultIScan8x16 = []; - private static readonly short[] DefaultIScan16x8 = []; - private static readonly short[] DefaultIScan16x32 = []; - private static readonly short[] DefaultIScan32x16 = []; - private static readonly short[] DefaultIScan4x16 = []; - private static readonly short[] DefaultIScan16x4 = []; - private static readonly short[] DefaultIScan8x32 = []; - private static readonly short[] DefaultIScan32x8 = []; + // Transform size 4X16 + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + ], + [ - private static readonly short[] MatrixColumnIScan4x4 = []; - private static readonly short[] MatrixColumnIScan8x8 = []; - private static readonly short[] MatrixColumnIScan16x16 = []; - private static readonly short[] MatrixColumnIScan32x32 = []; - private static readonly short[] MatrixColumnIScan64x64 = []; - private static readonly short[] MatrixColumnIScan4x8 = []; - private static readonly short[] MatrixColumnIScan8x4 = []; - private static readonly short[] MatrixColumnIScan8x16 = []; - private static readonly short[] MatrixColumnIScan16x8 = []; - private static readonly short[] MatrixColumnIScan16x32 = []; - private static readonly short[] MatrixColumnIScan32x16 = []; - private static readonly short[] MatrixColumnIScan4x16 = []; - private static readonly short[] MatrixColumnIScan16x4 = []; - private static readonly short[] MatrixColumnIScan8x32 = []; - private static readonly short[] MatrixColumnIScan32x8 = []; + // Transform size 16X4 + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + ], + [ - private static readonly short[] MatrixRowIScan4x4 = []; - private static readonly short[] MatrixRowIScan8x8 = []; - private static readonly short[] MatrixRowIScan16x16 = []; - private static readonly short[] MatrixRowIScan32x32 = []; - private static readonly short[] MatrixRowIScan64x64 = []; - private static readonly short[] MatrixRowIScan4x8 = []; - private static readonly short[] MatrixRowIScan8x4 = []; - private static readonly short[] MatrixRowIScan8x16 = []; - private static readonly short[] MatrixRowIScan16x8 = []; - private static readonly short[] MatrixRowIScan16x32 = []; - private static readonly short[] MatrixRowIScan32x16 = []; - private static readonly short[] MatrixRowIScan4x16 = []; - private static readonly short[] MatrixRowIScan16x4 = []; - private static readonly short[] MatrixRowIScan8x32 = []; - private static readonly short[] MatrixRowIScan32x8 = []; + // Transform size 8X32 + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + ], + [ - // Neighborss are not used (yet) for AVIF coding, leave these arrays empty for now. - private static readonly short[] DefaultScan4x4Neighbors = []; - private static readonly short[] DefaultScan8x8Neighbors = []; - private static readonly short[] DefaultScan16x16Neighbors = []; - private static readonly short[] DefaultScan32x32Neighbors = []; - private static readonly short[] DefaultScan64x64Neighbors = []; - private static readonly short[] DefaultScan4x8Neighbors = []; - private static readonly short[] DefaultScan8x4Neighbors = []; - private static readonly short[] DefaultScan8x16Neighbors = []; - private static readonly short[] DefaultScan16x8Neighbors = []; - private static readonly short[] DefaultScan16x32Neighbors = []; - private static readonly short[] DefaultScan32x16Neighbors = []; - private static readonly short[] DefaultScan4x16Neighbors = []; - private static readonly short[] DefaultScan16x4Neighbors = []; - private static readonly short[] DefaultScan8x32Neighbors = []; - private static readonly short[] DefaultScan32x8Neighbors = []; + // Transform size 32X8 + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + ], + [ - private static readonly short[] MatrixColumnScan4x4Neighbors = []; - private static readonly short[] MatrixColumnScan8x8Neighbors = []; - private static readonly short[] MatrixColumnScan16x16Neighbors = []; - private static readonly short[] MatrixColumnScan32x32Neighbors = []; - private static readonly short[] MatrixColumnScan64x64Neighbors = []; - private static readonly short[] MatrixColumnScan4x8Neighbors = []; - private static readonly short[] MatrixColumnScan8x4Neighbors = []; - private static readonly short[] MatrixColumnScan8x16Neighbors = []; - private static readonly short[] MatrixColumnScan16x8Neighbors = []; - private static readonly short[] MatrixColumnScan16x32Neighbors = []; - private static readonly short[] MatrixColumnScan32x16Neighbors = []; - private static readonly short[] MatrixColumnScan4x16Neighbors = []; - private static readonly short[] MatrixColumnScan16x4Neighbors = []; - private static readonly short[] MatrixColumnScan8x32Neighbors = []; - private static readonly short[] MatrixColumnScan32x8Neighbors = []; + // Transform size 16X64 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), + new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + ], + [ - private static readonly short[] MatrixRowScan4x4Neighbors = []; - private static readonly short[] MatrixRowScan8x8Neighbors = []; - private static readonly short[] MatrixRowScan16x16Neighbors = []; - private static readonly short[] MatrixRowScan32x32Neighbors = []; - private static readonly short[] MatrixRowScan64x64Neighbors = []; - private static readonly short[] MatrixRowScan4x8Neighbors = []; - private static readonly short[] MatrixRowScan8x4Neighbors = []; - private static readonly short[] MatrixRowScan8x16Neighbors = []; - private static readonly short[] MatrixRowScan16x8Neighbors = []; - private static readonly short[] MatrixRowScan16x32Neighbors = []; - private static readonly short[] MatrixRowScan32x16Neighbors = []; - private static readonly short[] MatrixRowScan4x16Neighbors = []; - private static readonly short[] MatrixRowScan16x4Neighbors = []; - private static readonly short[] MatrixRowScan8x32Neighbors = []; - private static readonly short[] MatrixRowScan32x8Neighbors = []; + // Transform size 64X16 + // Half of the coefficients of tx64 at higher frequencies are set to + // zeros. So tx32's scan order is used. + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), + new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + ] + ]; public static Av1ScanOrder GetScanOrder(Av1TransformSize transformSize, Av1TransformType transformType) => ScanOrders[(int)transformSize][(int)transformType]; From a48e271780008ce380480e50f3650170d72f9c53 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 8 Jul 2024 22:14:34 +0200 Subject: [PATCH 106/190] Fully implement Heif Item Location --- .../Formats/Heif/HeifDecoderCore.cs | 60 +++++++++++-------- .../Formats/Heif/HeifEncoderCore.cs | 2 +- src/ImageSharp/Formats/Heif/HeifItem.cs | 2 +- src/ImageSharp/Formats/Heif/HeifLocation.cs | 36 ++++++++++- .../Formats/Heif/HeifLocationOffsetOrigin.cs | 11 ++++ 5 files changed, 83 insertions(+), 28 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index f87dd7f4d1..1c65d6e00c 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -538,8 +538,10 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) int bytesRead = 0; byte version = boxBuffer[bytesRead]; bytesRead += 4; - byte b1 = boxBuffer[bytesRead++]; - byte b2 = boxBuffer[bytesRead++]; + byte b1 = boxBuffer[bytesRead]; + bytesRead++; + byte b2 = boxBuffer[bytesRead]; + bytesRead++; int offsetSize = (b1 >> 4) & 0x0f; int lengthSize = b1 & 0x0f; int baseOffsetSize = (b2 >> 4) & 0x0f; @@ -554,28 +556,31 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) { uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); HeifItem? item = this.FindItemById(itemId); + HeifLocationOffsetOrigin constructionMethod = HeifLocationOffsetOrigin.FileOffset; if (version is 1 or 2) { bytesRead++; - byte b3 = boxBuffer[bytesRead++]; - int constructionMethod = b3 & 0x0f; + byte b3 = boxBuffer[bytesRead]; + bytesRead++; + constructionMethod = (HeifLocationOffsetOrigin)(b3 & 0x0f); } uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; - ulong baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); + long baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint j = 0; j < extentCount; j++) { + uint extentIndex = 0; if (version is 1 or 2 && indexSize > 0) { - _ = ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); + extentIndex = (uint)ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); } - ulong extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); - ulong extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); - HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); + long extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); + long extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); + HeifLocation loc = new(constructionMethod, baseOffset, dataReferenceIndex, extentOffset, extentLength, extentIndex); item?.DataLocations.Add(loc); } } @@ -598,29 +603,36 @@ private static uint ReadUInt16Or32(Span buffer, bool isLarge, ref int byte return result; } - private static ulong ReadUIntVariable(Span buffer, int numBytes, ref int bytesRead) + private static long ReadUIntVariable(Span buffer, int numBytes, ref int bytesRead) { - ulong result = 0UL; - if (numBytes == 8) + long result = 0L; + int shift = 0; + if (numBytes > 8) { - result = BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); - bytesRead += 8; + throw new InvalidImageContentException($"Can't store large integer of {numBytes * 8} bits."); } - else if (numBytes == 4) + else + if (numBytes > 4) + { + result = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); + shift = 8 - numBytes; + } + else if (numBytes > 2) { result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; + shift = 4 - numBytes; } - else if (numBytes == 2) + else if (numBytes > 1) { result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; } else if (numBytes == 1) { - result = buffer[bytesRead++]; + result = buffer[bytesRead]; } + bytesRead += numBytes; + result >>= shift << 3; return result; } @@ -642,11 +654,11 @@ private Image ParseMediaData(BufferedReadStream stream, long box throw new NotImplementedException("No HVC decoding implemented yet"); } - int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; - int thumbFileLength = (int)thumbItem.DataLocations[0].Length; - stream.Skip((int)(thumbFileOffset - stream.Position)); - EnsureBoxBoundary(thumbFileLength, stream); - using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbFileLength); + long thumbPosition = thumbItem.DataLocations[0].GetStreamPosition(stream.Position, stream.Position); + long thumbLength = thumbItem.DataLocations[0].Length; + stream.Skip((int)(thumbPosition - stream.Position)); + EnsureBoxBoundary(thumbLength, stream); + using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbLength); Span thumbSpan = thumbMemory.GetSpan(); HeifMetadata meta = this.metadata.GetHeifMetadata(); diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 4842cfcf8d..8985768112 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -66,7 +66,7 @@ private static void GenerateItems(Image image, byte[] pixels, Li where TPixel : unmanaged, IPixel { HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); - primaryItem.DataLocations.Add(new HeifLocation(0L, pixels.LongLength)); + primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.ItemDataOffset, 0L, 0U, 0L, pixels.LongLength, 0U)); primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; primaryItem.SetExtent(image.Size); diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index 25e6bb2988..d9f355317e 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -71,7 +71,7 @@ internal class HeifItem(Heif4CharCode type, uint id) /// /// Gets the list of data locations for this item. /// - public List DataLocations { get; } = new List(); + public List DataLocations { get; } = []; /// /// Set the image extent. diff --git a/src/ImageSharp/Formats/Heif/HeifLocation.cs b/src/ImageSharp/Formats/Heif/HeifLocation.cs index d739cb633c..afbc3ad035 100644 --- a/src/ImageSharp/Formats/Heif/HeifLocation.cs +++ b/src/ImageSharp/Formats/Heif/HeifLocation.cs @@ -6,10 +6,25 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Location within the file of an . /// -internal class HeifLocation(long offset, long length) +internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, uint dataReferenceIndex, long offset, long length, uint extentIndex) { /// - /// Gets the file offset of this location. + /// Gets the origin of the offsets in this location. + /// + public HeifLocationOffsetOrigin Origin { get; } = method; + + /// + /// Gets the base offset of this location. + /// + public long BaseOffset { get; } = baseOffset; + + /// + /// Gets the data reference index of this location. + /// + public uint DataReferenceInxdex { get; } = dataReferenceIndex; + + /// + /// Gets the offset of this location. /// public long Offset { get; } = offset; @@ -17,4 +32,21 @@ internal class HeifLocation(long offset, long length) /// Gets the length of this location. /// public long Length { get; } = length; + + /// + /// Gets the extent index of this location. + /// + public uint ExtentInxdex { get; } = extentIndex; + + /// + /// Gets the stream position of this location. + /// + /// Stream position of the MediaData box. + /// Stream position of the previous box. + public long GetStreamPosition(long positionOfMediaData, long positionOfItem) => this.Origin switch + { + HeifLocationOffsetOrigin.FileOffset => this.BaseOffset + this.Offset, + HeifLocationOffsetOrigin.ItemDataOffset => positionOfMediaData + this.BaseOffset + this.Offset, + _ => positionOfItem + this.BaseOffset + this.Offset + }; } diff --git a/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs b/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs new file mode 100644 index 0000000000..accceb380b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +internal enum HeifLocationOffsetOrigin +{ + FileOffset = 0, + ItemDataOffset = 1, + ItemOffset = 2 +} From c7713505596458706e40339b45c7c93b6a503cae Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 9 Jul 2024 20:55:26 +0200 Subject: [PATCH 107/190] Prune ITileDecoder interface --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 19 ++-- .../Formats/Heif/Av1/IAv1TileDecoder.cs | 12 --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 93 +++++-------------- 4 files changed, 35 insertions(+), 91 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 8824c957a0..aafe086447 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -25,16 +25,15 @@ public void Decode(Span buffer) this.frameBuffer = this.tileDecoder?.FrameBuffer; } - public void StartDecodeTiles() + public void DecodeTile(Span tileData, int tileNum) { - this.SequenceHeader = this.obuReader.SequenceHeader; - this.FrameHeader = this.obuReader.FrameHeader; - this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!); + if (this.tileDecoder == null) + { + this.SequenceHeader = this.obuReader.SequenceHeader; + this.FrameHeader = this.obuReader.FrameHeader; + this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!); + } + + this.tileDecoder.DecodeTile(tileData, tileNum); } - - public void DecodeTile(Span tileData, int tileNum) - => this.tileDecoder!.DecodeTile(tileData, tileNum); - - public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) - => this.tileDecoder!.FinishDecodeTiles(doCdef, doLoopRestoration); } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs index daac2ef1d9..839f49619c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs @@ -8,11 +8,6 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// internal interface IAv1TileDecoder { - /// - /// Start decoding all tiles of a frame. - /// - void StartDecodeTiles(); - /// /// Decode a single tile. /// @@ -21,11 +16,4 @@ internal interface IAv1TileDecoder /// /// The index of the tile that is to be decoded. void DecodeTile(Span tileData, int tileNum); - - /// - /// Finshed decoding all tiles of a frame. - /// - /// Apply the CDF filter. - /// Apply the loop filters. - void FinishDecodeTiles(bool doCdef, bool doLoopRestoration); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 95736d9dc4..df1d42c923 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1272,7 +1272,7 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decode return; } - decoder.FinishDecodeTiles(doCdef, doLoopRestoration); + // TODO: Share doCdef and doLoopRestoration } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 44d61813b9..eeffbac337 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -24,18 +24,17 @@ internal class Av1TileDecoder : IAv1TileDecoder private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - private bool[][][] blockDecoded = []; private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext; private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext; private int currentQuantizerIndex; - private int[][] segmentIds = []; + private readonly int[][] segmentIds = []; private int deltaLoopFilterResolution = -1; - private bool readDeltas; - private int[][] tusCount; - private int[] firstTransformOffset = new int[2]; - private int[] coefficientIndex = []; + private readonly bool readDeltas; + private readonly int[][] tusCount; + private readonly int[] firstTransformOffset = new int[2]; + private readonly int[] coefficientIndex = []; public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { @@ -55,9 +54,9 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo // Hard code number of threads to 1 for now. int planesCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; int superblockColumnCount = - AlignPowerOfTwo(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperBlockSizeLog2) >> sequenceHeader.SuperBlockSizeLog2; + Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperBlockSizeLog2) >> sequenceHeader.SuperBlockSizeLog2; int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.ModeInfoSize; - modeInfoWideColumnCount = AlignPowerOfTwo(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); + modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.ModeInfoSize); this.tusCount = new int[Av1Constants.MaxPlanes][]; @@ -98,7 +97,6 @@ public void DecodeTile(Span tileData, int tileNum) } } - // TODO: Initialize this.blockDecoded Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameInfo); Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; int superBlock4x4Size = superBlockSize.Get4x4WideCount(); @@ -115,54 +113,23 @@ public void DecodeTile(Span tileData, int tileNum) // this.ClearBlockDecodedFlags(modeInfoLocation, superBlock4x4Size); Point modeInfoLocation = new(column, row); this.FrameBuffer.ClearCdef(superblockPosition); + this.firstTransformOffset[0] = 0; + this.firstTransformOffset[1] = 0; this.ReadLoopRestoration(modeInfoLocation, superBlockSize); this.ParsePartition(ref reader, modeInfoLocation, superBlockSize, superblockInfo, tileInfo); } } } - private static int AlignPowerOfTwo(int value, int n) => (value + ((1 << n) - 1)) & ~((1 << n) - 1); - private void ClearLoopFilterDelta() => this.FrameBuffer.ClearDeltaLoopFilter(); /// /// 5.11.3. Clear block decoded flags function. /// - private void ClearBlockDecodedFlags(Point modeInfoLocation, int superBlock4x4Size) + private static void ClearBlockDecodedFlags(Point modeInfoLocation, int superBlock4x4Size) { - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - this.blockDecoded = new bool[planesCount][][]; - for (int plane = 0; plane < planesCount; plane++) - { - int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; - int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; - int superBlock4x4Width = (this.FrameInfo.ModeInfoColumnCount - modeInfoLocation.X) >> subX; - int superBlock4x4Height = (this.FrameInfo.ModeInfoRowCount - modeInfoLocation.Y) >> subY; - this.blockDecoded[plane] = new bool[(superBlock4x4Size >> subY) + 3][]; - for (int y = -1; y <= superBlock4x4Size >> subY; y++) - { - this.blockDecoded[plane][y] = new bool[(superBlock4x4Size >> subX) + 3]; - for (int x = -1; x <= superBlock4x4Size >> subX; x++) - { - if (y < 0 && x < superBlock4x4Width) - { - this.blockDecoded[plane][y][x] = true; - } - else if (x < 0 && y < superBlock4x4Height) - { - this.blockDecoded[plane][y][x] = true; - } - else - { - this.blockDecoded[plane][y][x] = false; - } - } - } - - int lastIndex = this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1].Length - 1; - this.blockDecoded[plane][(superBlock4x4Size >> subY) - 1][lastIndex] = false; - } + // Nothing to do here. } private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) @@ -178,16 +145,6 @@ private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlock } } - public void StartDecodeTiles() - { - // TODO: Implement - } - - public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) - { - // TODO: Implement - } - /// /// 5.11.4. Decode partition syntax. /// @@ -200,8 +157,6 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, return; } - bool availableUp = this.IsInside(rowIndex - 1, columnIndex); - bool availableLeft = this.IsInside(rowIndex, columnIndex - 1); int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 1; @@ -235,9 +190,9 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, switch (partitionType) { case Av1PartitionType.Split: - Point loc1 = new Point(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); - Point loc2 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); - Point loc3 = new Point(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size); + Point loc1 = new(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); + Point loc2 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); + Point loc3 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size); this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo); this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileInfo); this.ParsePartition(ref reader, loc2, subSize, superblockInfo, tileInfo); @@ -257,7 +212,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, break; case Av1PartitionType.Vertical: this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); - if (hasRows) + if (hasColumns) { Point halfLocation = new(columnIndex + halfBlock4x4Size, rowIndex); this.ParseBlock(ref reader, halfLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.Vertical); @@ -273,7 +228,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, break; case Av1PartitionType.HorizontalB: this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); - Point locHorB1 = new(columnIndex + halfBlock4x4Size, rowIndex); + Point locHorB1 = new(columnIndex, rowIndex + halfBlock4x4Size); this.ParseBlock(ref reader, locHorB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); Point locHorB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); this.ParseBlock(ref reader, locHorB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.HorizontalB); @@ -287,7 +242,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, break; case Av1PartitionType.VerticalB: this.ParseBlock(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); - Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); + Point locVertB1 = new(columnIndex + halfBlock4x4Size, rowIndex); this.ParseBlock(ref reader, locVertB1, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); Point locVertB2 = new(columnIndex + halfBlock4x4Size, rowIndex + halfBlock4x4Size); this.ParseBlock(ref reader, locVertB2, splitSize, superblockInfo, tileInfo, Av1PartitionType.VerticalB); @@ -336,7 +291,9 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; Av1BlockModeInfo blockModeInfo = superblockInfo.GetModeInfo(modeInfoLocation); blockModeInfo.PartitionType = partitionType; - bool hasChroma = this.HasChroma(rowIndex, columnIndex, blockSize); + blockModeInfo.FirstTransformLocation[0] = this.firstTransformOffset[0]; + blockModeInfo.FirstTransformLocation[1] = this.firstTransformOffset[1]; + bool hasChroma = this.HasChroma(modeInfoLocation, blockSize); Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); partitionInfo.ColumnIndex = columnIndex; partitionInfo.RowIndex = rowIndex; @@ -490,14 +447,14 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf } } - private bool HasChroma(int rowIndex, int columnIndex, Av1BlockSize blockSize) + private bool HasChroma(Point modeInfoLocation, Av1BlockSize blockSize) { - int bw = blockSize.Get4x4WideCount(); - int bh = blockSize.Get4x4HighCount(); + int blockWide = blockSize.Get4x4WideCount(); + int blockHigh = blockSize.Get4x4HighCount(); bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; - bool hasChroma = ((rowIndex & 0x01) != 0 || (bh & 0x01) == 0 || !subY) && - ((columnIndex & 0x01) != 0 || (bw & 0x01) == 0 || !subX); + bool hasChroma = ((modeInfoLocation.Y & 0x01) != 0 || (blockHigh & 0x01) == 0 || !subY) && + ((modeInfoLocation.X & 0x01) != 0 || (blockWide & 0x01) == 0 || !subX); return hasChroma; } From be134e89d2888cc00b5e26a9ccad11427f719bd3 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 9 Jul 2024 22:08:02 +0200 Subject: [PATCH 108/190] Various tiling bug fixes --- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 7 ++----- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 8 +++---- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 9 ++++---- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 21 +++++++++---------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 1810bf6fc4..574735528d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -81,13 +81,10 @@ public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, A int n4w = blockSize.Get4x4WideCount(); if (skip) { - transformWidth = n4w * (1 << Av1Constants.ModeInfoSizeLog2); + transformWidth = n4w << Av1Constants.ModeInfoSizeLog2; } - for (int i = 0; i < n4w; i++) - { - this.aboveTransformWidth[startIndex + i] = transformWidth; - } + Array.Fill(this.aboveTransformWidth, transformWidth, startIndex, n4w); } internal void ClearContext(int plane, int offset, int length) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 8743ee329e..f505774746 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -80,13 +80,11 @@ public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo super int n4h = blockSize.Get4x4HighCount(); if (skip) { - transformHeight = n4h * (1 << Av1Constants.ModeInfoSizeLog2); + transformHeight = n4h << Av1Constants.ModeInfoSizeLog2; } - for (int i = 0; i < n4h; i++) - { - this.leftTransformHeight[startIndex + i] = transformHeight; - } + DebugGuard.MustBeLessThanOrEqualTo(startIndex + n4h, this.leftTransformHeight.Length, nameof(startIndex)); + Array.Fill(this.leftTransformHeight, transformHeight, startIndex, n4h); } internal void ClearContext(int plane, int offset, int length) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index 1a73c5eefc..99994f2d94 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -84,10 +84,11 @@ public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInf this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart; this.AvailableAboveForChroma = this.AvailableAbove; this.AvailableLeftForChroma = this.AvailableLeft; - this.modeBlockToLeftEdge = -(this.ColumnIndex << Av1Constants.ModeInfoSizeLog2) << 3; - this.modeBlockToRightEdge = ((frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << Av1Constants.ModeInfoSizeLog2) << 3; - this.modeBlockToTopEdge = -(this.RowIndex << Av1Constants.ModeInfoSizeLog2) << 3; - this.modeBlockToBottomEdge = ((frameInfo.ModeInfoRowCount - bh4 - this.RowIndex) << Av1Constants.ModeInfoSizeLog2) << 3; + int shift = Av1Constants.ModeInfoSizeLog2 + 3; + this.modeBlockToLeftEdge = -this.ColumnIndex << shift; + this.modeBlockToRightEdge = (frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift; + this.modeBlockToTopEdge = -this.RowIndex << shift; + this.modeBlockToBottomEdge = (frameInfo.ModeInfoRowCount - bh4 - this.RowIndex) << shift; } public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index eeffbac337..285e5d13d9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1285,17 +1285,16 @@ private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoL int block4x4Height = blockSize.Get4x4HighCount(); // First condition in spec is for INTER frames, implemented only the INTRA condition. - bool allowSelect = !partitionInfo.ModeInfo.Skip || true; - Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, allowSelect); + Av1TransformSize transformSize = this.ReadTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo, true); this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, false); this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, false); - this.UpdateTransformInfo(partitionInfo, blockSize, transformSize); + this.UpdateTransformInfo(partitionInfo, superblockInfo, blockSize, transformSize); } - private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) + private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) { - int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; - int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; + int transformInfoYIndex = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; + int transformInfoUvIndex = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; Av1TransformInfo lumaTransformInfo = this.FrameBuffer.GetTransformY(transformInfoYIndex); Av1TransformInfo chromaTransformInfo = this.FrameBuffer.GetTransformUv(transformInfoUvIndex); int totalLumaTusCount = 0; @@ -1324,8 +1323,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Block int stepColumn = transformSize.Get4x4WideCount(); int stepRow = transformSize.Get4x4HighCount(); - int unitHeight = Av1Math.Round2(Math.Min(height + idy, maxBlockHigh), 0); - int unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), 0); + int unitHeight = Av1Math.RoundPowerOf2(Math.Min(height + idy, maxBlockHigh), 0); + int unitWidth = Av1Math.RoundPowerOf2(Math.Min(width + idx, maxBlockWide), 0); for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) @@ -1349,8 +1348,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Block stepColumn = transformSizeUv.Get4x4WideCount(); stepRow = transformSizeUv.Get4x4HighCount(); - unitHeight = Av1Math.Round2(Math.Min(height + idx, maxBlockHigh), subY ? 1 : 0); - unitWidth = Av1Math.Round2(Math.Min(width + idx, maxBlockWide), subX ? 1 : 0); + unitHeight = Av1Math.RoundPowerOf2(Math.Min(height + idx, maxBlockHigh), subY ? 1 : 0); + unitWidth = Av1Math.RoundPowerOf2(Math.Min(width + idx, maxBlockWide), subX ? 1 : 0); for (int blockRow = idy; blockRow < unitHeight; blockRow += stepRow) { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) @@ -1382,7 +1381,7 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Block partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTusCount; - this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount; + this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount << 1; } /// From 30d7c09ec327ac514cc2c975d4ae8110687b7f89 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 11 Jul 2024 21:46:58 +0200 Subject: [PATCH 109/190] More bug fixes --- .../Formats/Heif/Av1/Av1Constants.cs | 16 ++-- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 12 +-- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 8 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 4 +- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 2 +- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 47 ++++++------ .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 52 ++++++------- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 75 +++++++------------ 8 files changed, 101 insertions(+), 115 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index b997165d74..2cb64a545b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -145,6 +145,14 @@ internal static class Av1Constants public const int CoefficientBaseRange = 12; + public const int MaxTransformSize = 1 << 6; + + public const int MaxTransformSizeUnit = MaxTransformSize >> 2; + + public const int CoefficientContextBitCount = 6; + + public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1; + public const int TransformPadHorizontalLog2 = 2; public const int TransformPadHorizontal = 1 << TransformPadHorizontalLog2; @@ -153,16 +161,8 @@ internal static class Av1Constants public const int TransformPadEnd = 16; - public const int CoefficientContextBitCount = 6; - - public const int CoefficientContextMask = (1 << CoefficientContextBitCount) - 1; - public const int TransformPad2d = ((MaxTransformSize + TransformPadHorizontal) * (MaxTransformSize + TransformPadVertical)) + TransformPadEnd; - public const int MaxTransformSize = 1 << 6; - - public const int MaxTransformSizeUnit = MaxTransformSize >> 2; - public const int TransformPadTop = 2; public const int BaseRangeSizeMinus1 = 3; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index df1d42c923..269c39810d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -369,10 +369,10 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc } // Video related flags removed - sequenceHeader.Use128x128SuperBlock = reader.ReadBoolean(); - sequenceHeader.SuperBlockSize = sequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - sequenceHeader.ModeInfoSize = sequenceHeader.Use128x128SuperBlock ? 32 : 16; - sequenceHeader.SuperBlockSizeLog2 = sequenceHeader.Use128x128SuperBlock ? 7 : 6; + sequenceHeader.Use128x128Superblock = reader.ReadBoolean(); + sequenceHeader.SuperblockSize = sequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + sequenceHeader.SuperblockModeInfoSize = sequenceHeader.Use128x128Superblock ? 32 : 16; + sequenceHeader.SuperblockSizeLog2 = sequenceHeader.Use128x128Superblock ? 7 : 6; sequenceHeader.EnableFilterIntra = reader.ReadBoolean(); sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); @@ -701,7 +701,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob int superBlockColumnCount; int superBlockRowCount; int superBlockShift; - if (sequenceHeader.Use128x128SuperBlock) + if (sequenceHeader.Use128x128Superblock) { superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; @@ -1579,7 +1579,7 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, if (usesLoopRestoration) { uint loopRestorationShift = reader.ReadLiteral(1); - if (sequenceHeader.Use128x128SuperBlock) + if (sequenceHeader.Use128x128Superblock) { loopRestorationShift++; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index b1afb7c7e3..34ce917634 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -37,13 +37,13 @@ internal class ObuSequenceHeader public int MaxFrameHeight { get; set; } - public bool Use128x128SuperBlock { get; set; } + public bool Use128x128Superblock { get; set; } - public Av1BlockSize SuperBlockSize { get; set; } + public Av1BlockSize SuperblockSize { get; set; } - public int ModeInfoSize { get; set; } + public int SuperblockModeInfoSize { get; set; } - public int SuperBlockSizeLog2 { get; set; } + public int SuperblockSizeLog2 { get; set; } public int FilterIntraLevel { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index aa4b22a03b..4a154da0e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -91,7 +91,7 @@ private static void WriteSequenceHeader(ref Av1BitStreamWriter writer, ObuSequen writer.WriteLiteral((uint)sequenceHeader.MaxFrameHeight - 1, sequenceHeader.FrameHeightBits); // Video related flags removed - writer.WriteBoolean(sequenceHeader.Use128x128SuperBlock); + writer.WriteBoolean(sequenceHeader.Use128x128Superblock); writer.WriteBoolean(sequenceHeader.EnableFilterIntra); writer.WriteBoolean(sequenceHeader.EnableIntraEdgeFilter); @@ -223,7 +223,7 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead int superBlockColumnCount; int superBlockRowCount; int superBlockShift; - if (sequenceHeader.Use128x128SuperBlock) + if (sequenceHeader.Use128x128Superblock) { superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 4c263d072c..29b63928c4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -33,7 +33,7 @@ internal class Av1FrameBuffer public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) { // init_main_frame_ctxt - int superblockSizeLog2 = sequenceHeader.SuperBlockSizeLog2; + int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; int superblockAlignedWidth = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, superblockSizeLog2); int superblockAlignedHeight = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameHeight, superblockSizeLog2); this.superblockColumnCount = superblockAlignedWidth >> superblockSizeLog2; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 574735528d..ee82adcedd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -8,30 +8,24 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseAboveNeighbor4x4Context { - /* Buffer holding the transform sizes of the previous 4x4 block row. */ - private readonly int[] aboveTransformWidth; - - /* Buffer holding the partition context of the previous 4x4 block row. */ - private int[] abovePartitionWidth; - /* Buffer holding the sign of the DC coefficients and the cumulative sum of the coefficient levels of the above 4x4 blocks corresponding to the current super block row. */ - private int[][] aboveContext = new int[Av1Constants.MaxPlanes][]; + private readonly int[][] aboveContext = new int[Av1Constants.MaxPlanes][]; /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ - private int[] aboveSegmentIdPredictionContext; + private readonly int[] aboveSegmentIdPredictionContext; /* Value of base colors for Y, U, and V */ - private int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][]; + private readonly int[][] abovePaletteColors = new int[Av1Constants.MaxPlanes][]; - private int[] aboveCompGroupIndex; + private readonly int[] aboveCompGroupIndex; public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) { int wide64x64Count = Av1BlockSize.Block64x64.Get4x4WideCount(); - this.aboveTransformWidth = new int[modeInfoColumnCount]; - this.abovePartitionWidth = new int[modeInfoColumnCount]; + this.AboveTransformWidth = new int[modeInfoColumnCount]; + this.AbovePartitionWidth = new int[modeInfoColumnCount]; for (int i = 0; i < planesCount; i++) { this.aboveContext[i] = new int[modeInfoColumnCount]; @@ -42,25 +36,32 @@ public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) this.aboveCompGroupIndex = new int[modeInfoColumnCount]; } - public int[] AbovePartitionWidth => this.abovePartitionWidth; + /// + /// Gets a buffer holding the partition context of the previous 4x4 block row. + /// + public int[] AbovePartitionWidth { get; } - public int[] AboveTransformWidth => this.aboveTransformWidth; + /// + /// Gets a buffer holding the transform sizes of the previous 4x4 block row. + /// + public int[] AboveTransformWidth { get; } public int[] GetContext(int plane) => this.aboveContext[plane]; - public void Clear(ObuSequenceHeader sequenceHeader) + public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int modeInfoColumnEnd) { int planeCount = sequenceHeader.ColorConfig.ChannelCount; - Array.Fill(this.aboveTransformWidth, Av1TransformSize.Size64x64.GetWidth()); - Array.Fill(this.abovePartitionWidth, 0); + int width = modeInfoColumnEnd - modeInfoColumnStart; + Array.Fill(this.AboveTransformWidth, Av1TransformSize.Size64x64.GetWidth(), 0, width); + Array.Fill(this.AbovePartitionWidth, 0, 0, width); for (int i = 0; i < planeCount; i++) { - Array.Fill(this.aboveContext[i], 0); - Array.Fill(this.abovePaletteColors[i], 0); + Array.Fill(this.aboveContext[i], 0, 0, width); + Array.Fill(this.abovePaletteColors[i], 0, 0, width); } - Array.Fill(this.aboveSegmentIdPredictionContext, 0); - Array.Fill(this.aboveCompGroupIndex, 0); + Array.Fill(this.aboveSegmentIdPredictionContext, 0, 0, width); + Array.Fill(this.aboveCompGroupIndex, 0, 0, width); } public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize) @@ -70,7 +71,7 @@ public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1Bloc int value = Av1PartitionContext.GetAboveContext(subSize); for (int i = 0; i < bw; i++) { - this.abovePartitionWidth[startIndex + i] = value; + this.AbovePartitionWidth[startIndex + i] = value; } } @@ -84,7 +85,7 @@ public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, A transformWidth = n4w << Av1Constants.ModeInfoSizeLog2; } - Array.Fill(this.aboveTransformWidth, transformWidth, startIndex, n4w); + Array.Fill(this.AboveTransformWidth, transformWidth, startIndex, n4w); } internal void ClearContext(int plane, int offset, int length) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index f505774746..7ce7beb624 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -8,31 +8,23 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal class Av1ParseLeftNeighbor4x4Context { - /* Buffer holding the transform sizes of the left 4x4 blocks corresponding - to the current super block row. */ - private readonly int[] leftTransformHeight; - - /* Buffer holding the partition context of the left 4x4 blocks corresponding - to the current super block row. */ - private int[] leftPartitionHeight; - /* Buffer holding the sign of the DC coefficients and the cumulative sum of the coefficient levels of the left 4x4 blocks corresponding to the current super block row. */ - private int[][] leftContext = new int[Av1Constants.MaxPlanes][]; + private readonly int[][] leftContext = new int[Av1Constants.MaxPlanes][]; /* Buffer holding the seg_id_predicted of the previous 4x4 block row. */ - private int[] leftSegmentIdPredictionContext; + private readonly int[] leftSegmentIdPredictionContext; /* Value of base colors for Y, U, and V */ - private int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][]; + private readonly int[][] leftPaletteColors = new int[Av1Constants.MaxPlanes][]; - private int[] leftCompGroupIndex; + private readonly int[] leftCompGroupIndex; public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSize) { - this.leftTransformHeight = new int[superblockModeInfoSize]; - this.leftPartitionHeight = new int[superblockModeInfoSize]; + this.LeftTransformHeight = new int[superblockModeInfoSize]; + this.LeftPartitionHeight = new int[superblockModeInfoSize]; for (int i = 0; i < planesCount; i++) { this.leftContext[i] = new int[superblockModeInfoSize]; @@ -43,23 +35,33 @@ public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSiz this.leftCompGroupIndex = new int[superblockModeInfoSize]; } - public int[] LeftPartitionHeight => this.leftPartitionHeight; + /// + /// Gets a buffer holding the partition context of the left 4x4 blocks corresponding + /// to the current super block row. + /// + public int[] LeftPartitionHeight { get; } - public int[] LeftTransformHeight => this.leftTransformHeight; + /// + /// Gets a buffer holding the transform sizes of the left 4x4 blocks corresponding + /// to the current super block row. + /// + public int[] LeftTransformHeight { get; } public void Clear(ObuSequenceHeader sequenceHeader) { + int blockCount = sequenceHeader.SuperblockModeInfoSize; int planeCount = sequenceHeader.ColorConfig.ChannelCount; - Array.Fill(this.leftTransformHeight, Av1TransformSize.Size64x64.GetHeight()); - Array.Fill(this.leftPartitionHeight, 0); + int neighbor4x4Count = sequenceHeader.SuperblockModeInfoSize; + Array.Fill(this.LeftTransformHeight, Av1TransformSize.Size64x64.GetHeight(), 0, blockCount); + Array.Fill(this.LeftPartitionHeight, 0, 0, blockCount); for (int i = 0; i < planeCount; i++) { - Array.Fill(this.leftContext[i], 0); - Array.Fill(this.leftPaletteColors[i], 0); + Array.Fill(this.leftContext[i], 0, 0, blockCount); + Array.Fill(this.leftPaletteColors[i], 0, 0, blockCount); } - Array.Fill(this.leftSegmentIdPredictionContext, 0); - Array.Fill(this.leftCompGroupIndex, 0); + Array.Fill(this.leftSegmentIdPredictionContext, 0, 0, blockCount); + Array.Fill(this.leftCompGroupIndex, 0, 0, blockCount); } public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize) @@ -69,7 +71,7 @@ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblock int value = Av1PartitionContext.GetLeftContext(subSize); for (int i = 0; i < bh; i++) { - this.leftPartitionHeight[startIndex + i] = value; + this.LeftPartitionHeight[startIndex + i] = value; } } @@ -83,8 +85,8 @@ public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo super transformHeight = n4h << Av1Constants.ModeInfoSizeLog2; } - DebugGuard.MustBeLessThanOrEqualTo(startIndex + n4h, this.leftTransformHeight.Length, nameof(startIndex)); - Array.Fill(this.leftTransformHeight, transformHeight, startIndex, n4h); + DebugGuard.MustBeLessThanOrEqualTo(startIndex + n4h, this.LeftTransformHeight.Length, nameof(startIndex)); + Array.Fill(this.LeftTransformHeight, transformHeight, startIndex, n4h); } internal void ClearContext(int plane, int offset, int length) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 285e5d13d9..06a41b3156 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -54,11 +54,11 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo // Hard code number of threads to 1 for now. int planesCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; int superblockColumnCount = - Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperBlockSizeLog2) >> sequenceHeader.SuperBlockSizeLog2; - int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.ModeInfoSize; - modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperBlockSizeLog2 - 2); + Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2; + int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.SuperblockModeInfoSize; + modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperblockSizeLog2 - 2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); - this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.ModeInfoSize); + this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.SuperblockModeInfoSize); this.tusCount = new int[Av1Constants.MaxPlanes][]; this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; @@ -78,7 +78,11 @@ public void DecodeTile(Span tileData, int tileNum) int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; - this.aboveNeighborContext.Clear(this.SequenceHeader); + int modeInfoColumnStart = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; + int modeInfoColumnEnd = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; + int modeInfoRowStart = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; + int modeInfoRowEnd = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; + this.aboveNeighborContext.Clear(this.SequenceHeader, modeInfoColumnStart, modeInfoColumnEnd); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; @@ -98,15 +102,15 @@ public void DecodeTile(Span tileData, int tileNum) } Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameInfo); - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; int superBlock4x4Size = superBlockSize.Get4x4WideCount(); - for (int row = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; row < this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; row += this.SequenceHeader.ModeInfoSize) + for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += this.SequenceHeader.SuperblockModeInfoSize) { - int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperblockSizeLog2; this.leftNeighborContext.Clear(this.SequenceHeader); - for (int column = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; column < this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; column += this.SequenceHeader.ModeInfoSize) + for (int column = modeInfoColumnStart; column < modeInfoColumnEnd; column += this.SequenceHeader.SuperblockModeInfoSize) { - int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperBlockSizeLog2; + int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperblockSizeLog2; Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); @@ -162,6 +166,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, int quarterBlock4x4Size = halfBlock4x4Size >> 1; bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; + int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); Av1PartitionType partitionType = Av1PartitionType.Split; if (blockSize < Av1BlockSize.Block8x8) { @@ -169,20 +174,17 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } else if (hasRows && hasColumns) { - int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); partitionType = reader.ReadPartitionType(ctx); } else if (hasColumns) { - int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); - bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); - partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); + partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } else if (hasRows) { - int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); - bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); - partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Vertical; + bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); + partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical; } Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); @@ -1204,27 +1206,6 @@ private static int GetEntropyContext(Av1TransformSize transformSize, int[] above return (aboveEntropyContext ? 1 : 0) + (leftEntropyContext ? 1 : 0); } - private void Reconstruct(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private int Coefficients(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - private void PredictChromaFromLuma(int plane, int startX, int startY, Av1TransformSize transformSize) => throw new NotImplementedException(); - - /// - /// 7.11.2. Intra prediction process. - /// - private void PredictIntra(int plane, int startX, int startY, bool leftAvailable, bool upAvailable, bool haveAboveRight, bool haveBelowLeft, Av1PredictionMode mode, int log2Width, int log2Height) => throw new NotImplementedException(); - - private void PredictPalette(int plane, int startX, int startY, int x, int y, Av1TransformSize transformSize) => throw new NotImplementedException(); - - /// - /// Page 65, below 5.11.5. Decode block syntax. - /// - private static void ResetBlockContext(int rowIndex, int columnIndex, Av1BlockSize blockSize) - { - // TODO: Do we still need this method. - } - /// /// 5.11.15. TX size syntax. /// @@ -1236,7 +1217,7 @@ private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1Parti return Av1TransformSize.Size4x4; } - if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameInfo.TransformMode == Transform.Av1TransformMode.Select) + if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameInfo.TransformMode == Av1TransformMode.Select) { return this.ReadSelectedTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo); } @@ -1719,7 +1700,7 @@ private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf if (partitionInfo.CdefStrength[r][c] == -1) { partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); - if (this.SequenceHeader.SuperBlockSize == Av1BlockSize.Block128x128) + if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128) { int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); int h4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); @@ -1736,7 +1717,7 @@ private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent || (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { @@ -1790,14 +1771,14 @@ private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128SuperBlock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; if (!this.FrameInfo.DeltaQParameters.IsPresent || (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperBlockSize || !partitionInfo.ModeInfo.Skip) + if (partitionInfo.ModeInfo.BlockSize != this.SequenceHeader.SuperblockSize || !partitionInfo.ModeInfo.Skip) { int deltaQuantizerAbsolute = reader.ReadDeltaQuantizerAbsolute(); if (deltaQuantizerAbsolute == Av1Constants.DeltaQuantizerSmall) @@ -1834,15 +1815,17 @@ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blo return xPos && yPos; }*/ - private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo) + private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo) { // Maximum partition point is 8x8. Offset the log value occordingly. - int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileLoc.ModeInfoColumnStart]; + int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileInfo.ModeInfoColumnStart]; int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask]; int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); int above = (aboveCtx >> blockSizeLog) & 0x1; int left = (leftCtx >> blockSizeLog) & 0x1; - return (left * 2) + above + (blockSizeLog * PartitionProbabilitySet); + DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square"); + DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog)); + return ((left << 1) + above) + (blockSizeLog * PartitionProbabilitySet); } private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) From a4ba77bc658661b414fdd4d92599f3aa8d5bc82f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 12 Jul 2024 13:05:16 +0200 Subject: [PATCH 110/190] Implement mode info indexing in frame buffer --- .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 22 +++++-- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 38 +++++------ .../Heif/Av1/Tiling/Av1FrameModeInfoMap.cs | 63 +++++++++++++++++++ .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 5 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 21 ++++--- 5 files changed, 117 insertions(+), 32 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index a78df4428e..8b8d449ec3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -9,19 +9,22 @@ internal class Av1BlockModeInfo { private int[] paletteSize; - public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) + public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInSuperblock) { this.BlockSize = blockSize; - this.PositionInSuperblock = position; + this.PositionInSuperblock = positionInSuperblock; this.AngleDelta = new int[numPlanes - 1]; this.paletteSize = new int[numPlanes - 1]; this.FilterIntraModeInfo = new(); this.FirstTransformLocation = new int[numPlanes - 1]; - this.TusCount = new int[numPlanes - 1]; + this.TransformUnitsCount = new int[numPlanes - 1]; } public Av1BlockSize BlockSize { get; } + /// + /// Gets or sets the for the luminance channel. + /// public Av1PredictionMode YMode { get; set; } public bool Skip { get; set; } @@ -32,6 +35,9 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) public int SegmentId { get; set; } + /// + /// Gets or sets the for the chroma channels. + /// public Av1PredictionMode UvMode { get; set; } public bool UseUltraBlockCopy { get; set; } @@ -42,13 +48,19 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point position) public int[] AngleDelta { get; set; } - public Point PositionInSuperblock { get; set; } + /// + /// Gets the position relative to the Superblock, counted in mode info (4x4 pixels). + /// + public Point PositionInSuperblock { get; } public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } + /// + /// Gets the index of the first of this Mode Info in the . + /// public int[] FirstTransformLocation { get; } - public int[] TusCount { get; internal set; } + public int[] TransformUnitsCount { get; internal set; } public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 29b63928c4..3d8194190d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -1,13 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Reflection.Metadata.Ecma335; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1FrameBuffer +internal partial class Av1FrameBuffer { // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4). public const int CoefficientCountPerModeInfo = 1 + 16; @@ -22,6 +23,7 @@ internal class Av1FrameBuffer private readonly int subsamplingFactor; private readonly Av1SuperblockInfo[] superblockInfos; private readonly Av1BlockModeInfo[] modeInfos; + private readonly Av1FrameModeInfoMap modeInfoMap; private readonly Av1TransformInfo[] transformInfosY; private readonly Av1TransformInfo[] transformInfosUv; private readonly int[] deltaQ; @@ -46,30 +48,18 @@ public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) // Allocate the arrays. this.superblockInfos = new Av1SuperblockInfo[superblockCount]; this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock]; + this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoCountPerSuperblock * this.superblockColumnCount, this.modeInfoCountPerSuperblock * this.superblockRowCount), superblockSizeLog2); this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock]; // Initialize the arrays. int i = 0; - int j = 0; - int k = 0; for (int y = 0; y < this.superblockRowCount; y++) { for (int x = 0; x < this.superblockColumnCount; x++) { Point point = new(x, y); this.superblockInfos[i] = new(this, point); - for (int u = 0; u < this.modeInfoSizePerSuperblock; u++) - { - for (int v = 0; v < this.modeInfoSizePerSuperblock; v++) - { - this.modeInfos[k] = new Av1BlockModeInfo(numPlanes, Av1BlockSize.Block4x4, new Point(u, v)); - k++; - } - - j++; - } - i++; } } @@ -110,10 +100,9 @@ public Av1BlockModeInfo GetModeInfo(Point superblockIndex) public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) { - Span span = this.modeInfos; - int superblock = (superblockIndex.Y * this.superblockColumnCount) + superblockIndex.X; - int modeInfo = (modeInfoIndex.Y * this.modeInfoSizePerSuperblock) + modeInfoIndex.X; - return span[(superblock * this.modeInfoCountPerSuperblock) + modeInfo]; + Point location = this.GetModeInfoPosition(superblockIndex, modeInfoIndex); + int index = this.modeInfoMap[location]; + return this.modeInfos[index]; } public ref Av1TransformInfo GetTransformY(int index) @@ -216,4 +205,17 @@ public Span GetDeltaLoopFilter(Point index) 1 or 2 => this.GetTransformUv(transformInfoIndex), _ => null, }; + + public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo) + { + this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo; + this.modeInfoMap.Update(this.GetModeInfoPosition(superblockInfo.Position, modeInfo.PositionInSuperblock), modeInfo.BlockSize); + } + + private Point GetModeInfoPosition(Point superblockPosition, Point positionInSuperblock) + { + int x = (superblockPosition.X * this.modeInfoCountPerSuperblock) + positionInSuperblock.X; + int y = (superblockPosition.Y * this.modeInfoCountPerSuperblock) + positionInSuperblock.Y; + return new Point(x, y); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs new file mode 100644 index 0000000000..b6f6a0c73d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -0,0 +1,63 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; + +internal partial class Av1FrameBuffer +{ + /// + /// Mapping of instances, from position to index into the . + /// + /// + /// For a visual representation of how this map looks in practice, see + /// + public class Av1FrameModeInfoMap + { + private readonly int[] offsets; + private Size alignedModeInfoCount; + + public Av1FrameModeInfoMap(Size modeInfoCount, int superblockSizeLog2) + { + this.alignedModeInfoCount = new Size( + modeInfoCount.Width * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)), + modeInfoCount.Height * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2))); + this.NextIndex = 0; + this.offsets = new int[this.alignedModeInfoCount.Width * this.alignedModeInfoCount.Height]; + } + + /// + /// Gets the next index to use. + /// + public int NextIndex { get; private set; } + + /// + /// Gets the mapped index for the given location. + /// + public int this[Point location] + { + get + { + int index = (location.Y * this.alignedModeInfoCount.Width) + location.X; + return this.offsets[index]; + } + } + + public void Update(Point modeInfoLocation, Av1BlockSize blockSize) + { + // Equivalent in SVT-Av1: EbDecNbr.c svt_aom_update_block_nbrs + int bw4 = blockSize.Get4x4WideCount(); + int bh4 = blockSize.Get4x4HighCount(); + DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.Y, 0, nameof(modeInfoLocation)); + DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.Y + bh4, this.alignedModeInfoCount.Height, nameof(modeInfoLocation)); + DebugGuard.MustBeGreaterThanOrEqualTo(modeInfoLocation.X, 0, nameof(modeInfoLocation)); + DebugGuard.MustBeLessThanOrEqualTo(modeInfoLocation.X + bw4, this.alignedModeInfoCount.Width, nameof(modeInfoLocation)); + /* Update 4x4 nbr offset map */ + for (int i = modeInfoLocation.Y; i < modeInfoLocation.Y + bh4; i++) + { + Array.Fill(this.offsets, this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4); + } + + this.NextIndex++; + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 5b6b9c79df..6fe0f7cc7d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -13,11 +13,14 @@ public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) this.frameBuffer = frameBuffer; } + /// + /// Gets the position of this superblock inside the tile, counted in superblocks. + /// public Point Position { get; } public ref int SuperblockDeltaQ => ref this.frameBuffer.GetDeltaQuantizationIndex(this.Position); - public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(default); + public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0)); public Span CoefficientsY => this.frameBuffer.GetCoefficientsY(this.Position); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 06a41b3156..bf6ce38bba 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -115,12 +115,12 @@ public void DecodeTile(Span tileData, int tileNum) Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); // this.ClearBlockDecodedFlags(modeInfoLocation, superBlock4x4Size); - Point modeInfoLocation = new(column, row); + Point modeInfoPosition = new(column, row); this.FrameBuffer.ClearCdef(superblockPosition); this.firstTransformOffset[0] = 0; this.firstTransformOffset[1] = 0; - this.ReadLoopRestoration(modeInfoLocation, superBlockSize); - this.ParsePartition(ref reader, modeInfoLocation, superBlockSize, superblockInfo, tileInfo); + this.ReadLoopRestoration(modeInfoPosition, superBlockSize); + this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo); } } } @@ -291,7 +291,9 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; - Av1BlockModeInfo blockModeInfo = superblockInfo.GetModeInfo(modeInfoLocation); + Point superblockLocation = superblockInfo.Position * this.SequenceHeader.SuperblockModeInfoSize; + Point locationInSuperblock = new Point(modeInfoLocation.X - superblockLocation.X, modeInfoLocation.Y - superblockLocation.Y); + Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize, locationInSuperblock); blockModeInfo.PartitionType = partitionType; blockModeInfo.FirstTransformLocation[0] = this.firstTransformOffset[0]; blockModeInfo.FirstTransformLocation[1] = this.firstTransformOffset[1]; @@ -332,6 +334,9 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 } this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize); + + // Update the Frame buffer for this ModeInfo. + this.FrameBuffer.UpdateModeInfo(blockModeInfo, superblockInfo); } private void ResetSkipContext(Av1PartitionInfo partitionInfo) @@ -367,7 +372,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf bool isLossless = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); - int chromaTusCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv]; + int chromaTusCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv]; int[] transformInfoIndices = new int[3]; transformInfoIndices[0] = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; transformInfoIndices[1] = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; @@ -400,7 +405,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf } else { - totalTusCount = partitionInfo.ModeInfo.TusCount[Math.Min(1, plane)]; + totalTusCount = partitionInfo.ModeInfo.TransformUnitsCount[Math.Min(1, plane)]; tusCount = this.tusCount[plane][forceSplitCount]; DebugGuard.IsFalse(totalTusCount == 0, nameof(totalTusCount), string.Empty); @@ -1358,8 +1363,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super } } - partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Y] = totalLumaTusCount; - partitionInfo.ModeInfo.TusCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; + partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Y] = totalLumaTusCount; + partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTusCount; this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount << 1; From 471e3d31658b324991fb05285055d6e9d19de773 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Jul 2024 17:45:51 +0200 Subject: [PATCH 111/190] Fix issue in ReadLoopRestorationParameters(): LoopRestorationParameters[i] was not initialized --- .../Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 269c39810d..d5e4ae4cc5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1565,7 +1565,11 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, bool usesChromaLoopRestoration = false; for (int i = 0; i < planesCount; i++) { - frameInfo.LoopRestorationParameters[i].Type = (ObuRestorationType)reader.ReadLiteral(2); + frameInfo.LoopRestorationParameters[i] = new ObuLoopRestorationParameters + { + Type = (ObuRestorationType)reader.ReadLiteral(2) + }; + if (frameInfo.LoopRestorationParameters[i].Type != ObuRestorationType.None) { usesLoopRestoration = true; From c44572900e1c28ab1d81407f0312ff5e32528cfe Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 12 Jul 2024 18:16:39 +0200 Subject: [PATCH 112/190] Fix issue in coefficient parsing --- src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index bf6ce38bba..66484a06a7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -677,8 +677,8 @@ private static void ReadCoefficientsReverse2d(ref Av1SymbolDecoder reader, Av1Tr for (int c = endSi; c >= startSi; --c) { int pos = scan[c]; - int coeff_ctx = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); - int level = reader.ReadCoefficientsBase(pos, transformSizeContext, planeType); + int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); + int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); From d527d0e7b270c38eba02fd04071a3adb875eb9de Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 13 Jul 2024 09:17:45 +0200 Subject: [PATCH 113/190] Rename ChannelCount property to PlaneCount --- .../Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs | 11 +++++++++-- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 4 ++-- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 1 + .../Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs | 2 +- .../Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 14 +++++++------- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index 83dddd2fe4..3c7349b199 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -7,8 +7,15 @@ internal class ObuColorConfig { public bool IsColorDescriptionPresent { get; set; } - public int ChannelCount { get; set; } - + /// + /// Gets or sets the number of color channels in this image. + /// + public int PlaneCount { get; set; } + + /// + /// Gets or sets a value indicating whether the image has a single greyscale plane, will have + /// color planes otherwise. + /// public bool IsMonochrome { get; set; } public ObuColorPrimaries ColorPrimaries { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index d5e4ae4cc5..fc3b19d319 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -465,7 +465,7 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu colorConfig.IsMonochrome = reader.ReadBoolean(); } - colorConfig.ChannelCount = colorConfig.IsMonochrome ? 1 : 3; + colorConfig.PlaneCount = colorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; @@ -1191,7 +1191,7 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i /// private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { - int planeCount = this.SequenceHeader!.ColorConfig.IsMonochrome ? 1 : 3; + int planeCount = this.SequenceHeader!.ColorConfig.PlaneCount; int startBitPosition = reader.BitPosition; this.ReadUncompressedFrameHeader(ref reader, header, planeCount); if (trailingBit) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 4a154da0e7..1b704309d1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -112,6 +112,7 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH writer.WriteBoolean(colorConfig.IsMonochrome); } + colorConfig.PlaneCount = colorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent if (colorConfig.IsMonochrome) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index ee82adcedd..111deae1fd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -50,7 +50,7 @@ public Av1ParseAboveNeighbor4x4Context(int planesCount, int modeInfoColumnCount) public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int modeInfoColumnEnd) { - int planeCount = sequenceHeader.ColorConfig.ChannelCount; + int planeCount = sequenceHeader.ColorConfig.PlaneCount; int width = modeInfoColumnEnd - modeInfoColumnStart; Array.Fill(this.AboveTransformWidth, Av1TransformSize.Size64x64.GetWidth(), 0, width); Array.Fill(this.AbovePartitionWidth, 0, 0, width); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 7ce7beb624..8e034f6e90 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -50,7 +50,7 @@ public Av1ParseLeftNeighbor4x4Context(int planesCount, int superblockModeInfoSiz public void Clear(ObuSequenceHeader sequenceHeader) { int blockCount = sequenceHeader.SuperblockModeInfoSize; - int planeCount = sequenceHeader.ColorConfig.ChannelCount; + int planeCount = sequenceHeader.ColorConfig.PlaneCount; int neighbor4x4Count = sequenceHeader.SuperblockModeInfoSize; Array.Fill(this.LeftTransformHeight, Av1TransformSize.Size64x64.GetHeight(), 0, blockCount); Array.Fill(this.LeftPartitionHeight, 0, 0, blockCount); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 66484a06a7..5cb054682e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -52,7 +52,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo // reallocate_parse_context_memory // Hard code number of threads to 1 for now. - int planesCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; + int planesCount = sequenceHeader.ColorConfig.PlaneCount; int superblockColumnCount = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2; int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.SuperblockModeInfoSize; @@ -84,7 +84,7 @@ public void DecodeTile(Span tileData, int tileNum) int modeInfoRowEnd = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; this.aboveNeighborContext.Clear(this.SequenceHeader, modeInfoColumnStart, modeInfoColumnEnd); this.ClearLoopFilterDelta(); - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; // Default initialization of Wiener and SGR Filter. this.referenceSgrXqd = new int[planesCount][]; @@ -138,7 +138,7 @@ private static void ClearBlockDecodedFlags(Point modeInfoLocation, int superBloc private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) { - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int plane = 0; plane < planesCount; plane++) { if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None) @@ -290,7 +290,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 int columnIndex = modeInfoLocation.X; int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); - int planesCount = this.SequenceHeader.ColorConfig.ChannelCount; + int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; Point superblockLocation = superblockInfo.Position * this.SequenceHeader.SuperblockModeInfoSize; Point locationInSuperblock = new Point(modeInfoLocation.X - superblockLocation.X, modeInfoLocation.Y - superblockLocation.Y); Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize, locationInSuperblock); @@ -341,7 +341,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 private void ResetSkipContext(Av1PartitionInfo partitionInfo) { - int planesCount = this.SequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; + int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) { bool subX = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX; @@ -368,7 +368,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf int modeUnitBlocksHigh = maxUnitSize.GetHeight() >> 2; modeUnitBlocksWide = Math.Min(maxBlocksWide, modeUnitBlocksWide); modeUnitBlocksHigh = Math.Min(maxBlocksHigh, modeUnitBlocksHigh); - int planeCount = this.SequenceHeader.ColorConfig.ChannelCount; + int planeCount = this.SequenceHeader.ColorConfig.PlaneCount; bool isLossless = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); @@ -1734,7 +1734,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p int frameLoopFilterCount = 1; if (this.FrameInfo.DeltaLoopFilterParameters.IsMulti) { - frameLoopFilterCount = this.SequenceHeader.ColorConfig.ChannelCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; + frameLoopFilterCount = this.SequenceHeader.ColorConfig.PlaneCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; } Span currentDeltaLoopFilter = partitionInfo.SuperblockInfo.SuperblockDeltaLoopFilter; From 68be92331b62825d684f0d06a837b5440b65a205 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 13 Jul 2024 09:23:29 +0200 Subject: [PATCH 114/190] PlaneCount calculation inside ObuColorConfig --- .../Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs | 16 +++++++++++++--- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 1 - .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 1 - 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index 3c7349b199..7e169f43fd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -5,18 +5,28 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuColorConfig { + private bool isMonochrome; + public bool IsColorDescriptionPresent { get; set; } /// - /// Gets or sets the number of color channels in this image. + /// Gets the number of color channels in this image. Can have the value 1 or 3. /// - public int PlaneCount { get; set; } + public int PlaneCount { get; private set; } /// /// Gets or sets a value indicating whether the image has a single greyscale plane, will have /// color planes otherwise. /// - public bool IsMonochrome { get; set; } + public bool IsMonochrome + { + get => this.isMonochrome; + set + { + this.PlaneCount = value ? 1 : Av1Constants.MaxPlanes; + this.isMonochrome = value; + } + } public ObuColorPrimaries ColorPrimaries { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index fc3b19d319..01e1a50c0f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -465,7 +465,6 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu colorConfig.IsMonochrome = reader.ReadBoolean(); } - colorConfig.PlaneCount = colorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; colorConfig.IsColorDescriptionPresent = reader.ReadBoolean(); colorConfig.ColorPrimaries = ObuColorPrimaries.Unspecified; colorConfig.TransferCharacteristics = ObuTransferCharacteristics.Unspecified; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 1b704309d1..4a154da0e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -112,7 +112,6 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH writer.WriteBoolean(colorConfig.IsMonochrome); } - colorConfig.PlaneCount = colorConfig.IsMonochrome ? 1 : Av1Constants.MaxPlanes; writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent if (colorConfig.IsMonochrome) { From a6a32d40951e6183c086797fb3cc09472c4e363e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 13 Jul 2024 10:20:46 +0200 Subject: [PATCH 115/190] TransformInfo retrieval changes --- .../Formats/Heif/Av1/Av1BlockSize.cs | 3 +- .../Heif/Av1/Av1BlockSizeExtensions.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 39 ++++--------------- .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 4 ++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 35 ++++++++++------- 5 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index 92a651d60f..c38f620c41 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -72,7 +72,8 @@ internal enum Av1BlockSize : byte /// A block of samples, 64 samples wide and 16 samples high. Block64x16 = 21, - Invalid = 22, + SizesAll = 22, SizeS = Block4x16, + Invalid = 255, Largest = SizeS - 1, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 6f1490975e..3c5ce9e596 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -104,7 +104,7 @@ public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize { Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); Av1TransformSize uvTransformSize = Av1TransformSize.Invalid; - if (planeBlockSize < Av1BlockSize.SizeS) + if (planeBlockSize < Av1BlockSize.SizesAll) { uvTransformSize = planeBlockSize.GetMaximumTransformSize(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 3d8194190d..1d1e9d162b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -105,35 +105,20 @@ public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) return this.modeInfos[index]; } - public ref Av1TransformInfo GetTransformY(int index) + public Span GetSuperblockTransformY(Point index) { Span span = this.transformInfosY; - return ref span[index]; + int offset = ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock; + int length = this.modeInfoCountPerSuperblock; + return span.Slice(offset, length); } - public void SetTransformY(int index, Av1TransformInfo transformInfo) - { - Span span = this.transformInfosY; - span[index] = transformInfo; - } - - public ref Av1TransformInfo GetTransformY(Point index) - { - Span span = this.transformInfosY; - int i = (index.Y * this.superblockColumnCount) + index.X; - return ref span[i * this.modeInfoCountPerSuperblock]; - } - - public ref Av1TransformInfo GetTransformUv(int index) + public Span GetSuperblockTransformUv(Point index) { Span span = this.transformInfosUv; - return ref span[index]; - } - - public void SetTransformUv(int index, Av1TransformInfo transformInfo) - { - Span span = this.transformInfosUv; - span[index] = transformInfo; + int offset = (((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock) << 1; + int length = this.modeInfoCountPerSuperblock << 1; + return span.Slice(offset, length); } public Span GetCoefficients(int plane) => @@ -198,14 +183,6 @@ public Span GetDeltaLoopFilter(Point index) public void ClearDeltaLoopFilter() => Array.Fill(this.deltaLoopFilter, 0); - public Av1TransformInfo? GetTransform(int plane, int transformInfoIndex) => - plane switch - { - 0 => this.GetTransformY(transformInfoIndex), - 1 or 2 => this.GetTransformUv(transformInfoIndex), - _ => null, - }; - public void UpdateModeInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo) { this.modeInfos[this.modeInfoMap.NextIndex] = modeInfo; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 6fe0f7cc7d..b76b8c87d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -36,5 +36,9 @@ public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) public int TransformInfoIndexUv { get; internal set; } + public Span GetTransformInfoY() => this.frameBuffer.GetSuperblockTransformY(this.Position); + + public Span GetTransformInfoUv() => this.frameBuffer.GetSuperblockTransformUv(this.Position); + public Av1BlockModeInfo GetModeInfo(Point index) => this.frameBuffer.GetModeInfo(this.Position, index); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 5cb054682e..16e39555f2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -373,6 +373,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); int chromaTusCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv]; + int[] transformInfoIndices = new int[3]; transformInfoIndices[0] = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; transformInfoIndices[1] = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; @@ -409,16 +410,20 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf tusCount = this.tusCount[plane][forceSplitCount]; DebugGuard.IsFalse(totalTusCount == 0, nameof(totalTusCount), string.Empty); - DebugGuard.IsTrue(totalTusCount == this.tusCount[plane][0] + this.tusCount[plane][1] + this.tusCount[plane][2] + this.tusCount[plane][3], nameof(totalTusCount), string.Empty); + + // DebugGuard.IsTrue(totalTusCount == this.tusCount[plane][0] + this.tusCount[plane][1] + this.tusCount[plane][2] + this.tusCount[plane][3], nameof(totalTusCount), string.Empty); } DebugGuard.IsFalse(tusCount == 0, nameof(tusCount), string.Empty); - + Span transformInfos = plane == 0 ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv(); for (int tu = 0; tu < tusCount; tu++) { + Av1TransformInfo transformInfo = transformInfos[transformInfoIndices[plane]]; + DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetX, maxBlocksWide, nameof(transformInfo)); + DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetY, maxBlocksHigh, nameof(transformInfo)); + int coefficientIndex = this.coefficientIndex[plane]; int endOfBlock = 0; - Av1TransformInfo transformInfo = this.FrameBuffer.GetTransform(plane, transformInfoIndices[plane])!; int blockColumn = transformInfo.OffsetX; int blockRow = transformInfo.OffsetY; int startX = (partitionInfo.ColumnIndex >> subX) + blockColumn; @@ -432,12 +437,12 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf if (!partitionInfo.ModeInfo.Skip) { - endOfBlock = this.TransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.Size, subX != 0, subY != 0); + endOfBlock = this.ParseTransformBlock(ref reader, partitionInfo, coefficientIndex, transformInfo, plane, blockColumn, blockRow, startX, startY, transformInfo.Size, subX != 0, subY != 0); } if (endOfBlock != 0) { - this.coefficientIndex[plane] += 2; + this.coefficientIndex[plane] += endOfBlock + 1; transformInfo.CodeBlockFlag = true; } else @@ -487,7 +492,7 @@ private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) /// /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. /// - private int TransformBlock( + private int ParseTransformBlock( ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int coefficientIndex, @@ -1279,10 +1284,10 @@ private void ReadBlockTransformSize(ref Av1SymbolDecoder reader, Point modeInfoL private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1BlockSize blockSize, Av1TransformSize transformSize) { - int transformInfoYIndex = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; - int transformInfoUvIndex = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; - Av1TransformInfo lumaTransformInfo = this.FrameBuffer.GetTransformY(transformInfoYIndex); - Av1TransformInfo chromaTransformInfo = this.FrameBuffer.GetTransformUv(transformInfoUvIndex); + int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; + int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; + Span lumaTransformInfo = superblockInfo.GetTransformInfoY(); + Span chromaTransformInfo = superblockInfo.GetTransformInfoUv(); int totalLumaTusCount = 0; int totalChromaTusCount = 0; int forceSplitCount = 0; @@ -1315,8 +1320,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - this.FrameBuffer.SetTransformY(transformInfoYIndex, new Av1TransformInfo( - transformSize, blockColumn, blockRow)); + lumaTransformInfo[transformInfoYIndex] = new Av1TransformInfo( + transformSize, blockColumn, blockRow); transformInfoYIndex++; lumaTusCount++; totalLumaTusCount++; @@ -1340,8 +1345,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - this.FrameBuffer.SetTransformUv(transformInfoUvIndex, new Av1TransformInfo( - transformSizeUv, blockColumn, blockRow)); + chromaTransformInfo[transformInfoUvIndex] = new Av1TransformInfo( + transformSizeUv, blockColumn, blockRow); transformInfoUvIndex++; chromaTusCount++; totalChromaTusCount++; @@ -1359,7 +1364,7 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super int originalIndex = transformInfoUvIndex - totalChromaTusCount; for (int i = 0; i < totalChromaTusCount; i++) { - this.FrameBuffer.SetTransformUv(transformInfoUvIndex + i, this.FrameBuffer.GetTransformUv(originalIndex + i)); + chromaTransformInfo[transformInfoUvIndex + i] = chromaTransformInfo[originalIndex + i]; } } From d7622071e8877cfbf2e8aa34e615fc725d376a82 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 13 Jul 2024 14:38:16 +0200 Subject: [PATCH 116/190] Transform Unit Count variable rename --- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 84 ++++++++++--------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 16e39555f2..bcb082a721 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -32,7 +32,7 @@ internal class Av1TileDecoder : IAv1TileDecoder private readonly int[][] segmentIds = []; private int deltaLoopFilterResolution = -1; private readonly bool readDeltas; - private readonly int[][] tusCount; + private readonly int[][] transformUnitCount; private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; @@ -59,10 +59,10 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperblockSizeLog2 - 2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.SuperblockModeInfoSize); - this.tusCount = new int[Av1Constants.MaxPlanes][]; - this.tusCount[0] = new int[this.FrameBuffer.ModeInfoCount]; - this.tusCount[1] = new int[this.FrameBuffer.ModeInfoCount]; - this.tusCount[2] = new int[this.FrameBuffer.ModeInfoCount]; + this.transformUnitCount = new int[Av1Constants.MaxPlanes][]; + this.transformUnitCount[0] = new int[this.FrameBuffer.ModeInfoCount]; + this.transformUnitCount[1] = new int[this.FrameBuffer.ModeInfoCount]; + this.transformUnitCount[2] = new int[this.FrameBuffer.ModeInfoCount]; this.coefficientIndex = new int[Av1Constants.MaxPlanes]; } @@ -372,12 +372,12 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf bool isLossless = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); - int chromaTusCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv]; + int chromaTransformUnitCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv]; int[] transformInfoIndices = new int[3]; transformInfoIndices[0] = superblockInfo.TransformInfoIndexY + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; transformInfoIndices[1] = superblockInfo.TransformInfoIndexUv + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; - transformInfoIndices[2] = transformInfoIndices[1] + chromaTusCount; + transformInfoIndices[2] = transformInfoIndices[1] + chromaTransformUnitCount; int forceSplitCount = 0; for (int row = 0; row < maxBlocksHigh; row += modeUnitBlocksHigh) @@ -386,37 +386,41 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf { for (int plane = 0; plane < planeCount; ++plane) { - int totalTusCount; - int tusCount; - int subX = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; - int subY = plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; + int totalTransformUnitCount; + int transformUnitCount; + int subX = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0; + int subY = (plane > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0; if (plane != 0 && !partitionInfo.IsChroma) { continue; } + Span transformInfos = plane == 0 ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv(); if (isLosslessBlock) { // TODO: Implement. int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0); int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0); - totalTusCount = 0; - tusCount = ((unitWidth - column) * (unitHeight - row)) >> (subX + subY); + DebugGuard.IsTrue(transformInfos[transformInfoIndices[plane]].Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4."); + transformUnitCount = ((unitWidth - column) * (unitHeight - row)) >> (subX + subY); } else { - totalTusCount = partitionInfo.ModeInfo.TransformUnitsCount[Math.Min(1, plane)]; - tusCount = this.tusCount[plane][forceSplitCount]; - - DebugGuard.IsFalse(totalTusCount == 0, nameof(totalTusCount), string.Empty); - - // DebugGuard.IsTrue(totalTusCount == this.tusCount[plane][0] + this.tusCount[plane][1] + this.tusCount[plane][2] + this.tusCount[plane][3], nameof(totalTusCount), string.Empty); + totalTransformUnitCount = partitionInfo.ModeInfo.TransformUnitsCount[Math.Min(1, plane)]; + transformUnitCount = this.transformUnitCount[plane][forceSplitCount]; + + DebugGuard.IsFalse(totalTransformUnitCount == 0, nameof(totalTransformUnitCount), string.Empty); + DebugGuard.IsTrue( + totalTransformUnitCount == + this.transformUnitCount[plane][0] + this.transformUnitCount[plane][1] + + this.transformUnitCount[plane][2] + this.transformUnitCount[plane][3], + nameof(totalTransformUnitCount), + string.Empty); } - DebugGuard.IsFalse(tusCount == 0, nameof(tusCount), string.Empty); - Span transformInfos = plane == 0 ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv(); - for (int tu = 0; tu < tusCount; tu++) + DebugGuard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), string.Empty); + for (int tu = 0; tu < transformUnitCount; tu++) { Av1TransformInfo transformInfo = transformInfos[transformInfoIndices[plane]]; DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetX, maxBlocksWide, nameof(transformInfo)); @@ -1288,8 +1292,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; Span lumaTransformInfo = superblockInfo.GetTransformInfoY(); Span chromaTransformInfo = superblockInfo.GetTransformInfoUv(); - int totalLumaTusCount = 0; - int totalChromaTusCount = 0; + int totalLumaTransformUnitCount = 0; + int totalChromaTransformUnitCount = 0; int forceSplitCount = 0; bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; @@ -1307,8 +1311,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { for (int idx = 0; idx < maxBlockWide; idx += width, forceSplitCount++) { - int lumaTusCount = 0; - int chromaTusCount = 0; + int lumaTransformUnitCount = 0; + int chromaTransformUnitCount = 0; // Update Luminance Transform Info. int stepColumn = transformSize.Get4x4WideCount(); @@ -1323,12 +1327,12 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super lumaTransformInfo[transformInfoYIndex] = new Av1TransformInfo( transformSize, blockColumn, blockRow); transformInfoYIndex++; - lumaTusCount++; - totalLumaTusCount++; + lumaTransformUnitCount++; + totalLumaTransformUnitCount++; } } - this.tusCount[(int)Av1Plane.Y][forceSplitCount] = lumaTusCount; + this.transformUnitCount[(int)Av1Plane.Y][forceSplitCount] = lumaTransformUnitCount; if (this.SequenceHeader.ColorConfig.IsMonochrome || !partitionInfo.IsChroma) { @@ -1348,31 +1352,31 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super chromaTransformInfo[transformInfoUvIndex] = new Av1TransformInfo( transformSizeUv, blockColumn, blockRow); transformInfoUvIndex++; - chromaTusCount++; - totalChromaTusCount++; + chromaTransformUnitCount++; + totalChromaTransformUnitCount++; } } - this.tusCount[(int)Av1Plane.U][forceSplitCount] = lumaTusCount; - this.tusCount[(int)Av1Plane.V][forceSplitCount] = lumaTusCount; + this.transformUnitCount[(int)Av1Plane.U][forceSplitCount] = lumaTransformUnitCount; + this.transformUnitCount[(int)Av1Plane.V][forceSplitCount] = lumaTransformUnitCount; } } // Cr Transform Info Update from Cb. - if (totalChromaTusCount != 0) + if (totalChromaTransformUnitCount != 0) { - int originalIndex = transformInfoUvIndex - totalChromaTusCount; - for (int i = 0; i < totalChromaTusCount; i++) + int originalIndex = transformInfoUvIndex - totalChromaTransformUnitCount; + for (int i = 0; i < totalChromaTransformUnitCount; i++) { chromaTransformInfo[transformInfoUvIndex + i] = chromaTransformInfo[originalIndex + i]; } } - partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Y] = totalLumaTusCount; - partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv] = totalChromaTusCount; + partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Y] = totalLumaTransformUnitCount; + partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv] = totalChromaTransformUnitCount; - this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTusCount; - this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTusCount << 1; + this.firstTransformOffset[(int)Av1PlaneType.Y] += totalLumaTransformUnitCount; + this.firstTransformOffset[(int)Av1PlaneType.Uv] += totalChromaTransformUnitCount << 1; } /// From cb736d7bd8cabe319b6fd8ebde12ba04ab8570c6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 14 Jul 2024 15:45:13 +0200 Subject: [PATCH 117/190] Continue implement case !IsReducedStillPictureHeader in ReadUncompressedFrameHeader() --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 01e1a50c0f..cf3aa451d5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -894,22 +894,49 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade if (frameInfo.ShowExistingFrame) { frameInfo.FrameToShowMapIdx = reader.ReadLiteral(3); + + if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) + { + // 5.9.31. Temporal point info syntax. + frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); + } + + if (sequenceHeader.IsFrameIdNumbersPresent) + { + frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); + } + + // TODO: This is incomplete here, not sure how we can display an already decoded frame here or if this is really relevent for still pictures. + throw new NotImplementedException("ShowExistingFrame is not yet implemented"); } - if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) + frameInfo.FrameType = (ObuFrameType)reader.ReadLiteral(2); + bool frameIsIntra = frameInfo.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame; + frameInfo.ShowFrame = reader.ReadBoolean(); + + if (frameInfo.ShowFrame && !sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) { // 5.9.31. Temporal point info syntax. frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); } - // int refreshFrameFlags = 0; - if (sequenceHeader.IsFrameIdNumbersPresent) + if (frameInfo.ShowFrame) { - frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); + frameInfo.ShowableFrame = frameInfo.FrameType != ObuFrameType.KeyFrame; + } + else + { + frameInfo.ShowableFrame = reader.ReadBoolean(); } - // TODO: This is incomplete here, not sure how we can display an already decoded frame here. - throw new NotImplementedException("ShowExistingFrame is not yet implemented"); + if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + { + frameInfo.ErrorResilientMode = true; + } + else + { + frameInfo.ErrorResilientMode = reader.ReadBoolean(); + } } if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) @@ -1080,6 +1107,10 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade { frameInfo.DisableFrameEndUpdateCdf = true; } + else + { + frameInfo.DisableFrameEndUpdateCdf = reader.ReadBoolean(); + } if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { From 541ab35b4b819152af10f0d9bce31a226422c47b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 15 Jul 2024 18:56:13 +0200 Subject: [PATCH 118/190] Fix issue in Transform Unit Count --- src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index bcb082a721..10fdcdf6cf 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1357,14 +1357,18 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super } } - this.transformUnitCount[(int)Av1Plane.U][forceSplitCount] = lumaTransformUnitCount; - this.transformUnitCount[(int)Av1Plane.V][forceSplitCount] = lumaTransformUnitCount; + this.transformUnitCount[(int)Av1Plane.U][forceSplitCount] = chromaTransformUnitCount; + this.transformUnitCount[(int)Av1Plane.V][forceSplitCount] = chromaTransformUnitCount; } } // Cr Transform Info Update from Cb. if (totalChromaTransformUnitCount != 0) { + DebugGuard.IsTrue( + (transformInfoUvIndex - totalChromaTransformUnitCount) == + partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv], + nameof(totalChromaTransformUnitCount)); int originalIndex = transformInfoUvIndex - totalChromaTransformUnitCount; for (int i = 0; i < totalChromaTransformUnitCount; i++) { From 441949fe682160ae209b3adc2c6c586357663ae7 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 15 Jul 2024 19:40:59 +0200 Subject: [PATCH 119/190] Simplify code by using constants --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 27 +++++-------------- .../OpenBitstreamUnit/ObuSequenceHeader.cs | 20 +++++++++++--- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 9 ------- 3 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index cf3aa451d5..feb9cfc9cd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -370,9 +370,6 @@ private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenc // Video related flags removed sequenceHeader.Use128x128Superblock = reader.ReadBoolean(); - sequenceHeader.SuperblockSize = sequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - sequenceHeader.SuperblockModeInfoSize = sequenceHeader.Use128x128Superblock ? 32 : 16; - sequenceHeader.SuperblockSizeLog2 = sequenceHeader.Use128x128Superblock ? 7 : 6; sequenceHeader.EnableFilterIntra = reader.ReadBoolean(); sequenceHeader.EnableIntraEdgeFilter = reader.ReadBoolean(); @@ -699,25 +696,15 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob ObuTileGroupHeader tileInfo = new(); int superBlockColumnCount; int superBlockRowCount; - int superBlockShift; - if (sequenceHeader.Use128x128Superblock) - { - superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; - superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; - superBlockShift = 5; - } - else - { - superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4; - superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4; - superBlockShift = 4; - } + int superBlockShift = sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; + superBlockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superBlockShift; + superBlockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superBlockShift; - int superBlockSize = superBlockShift + 2; - int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); + int superBlockSizeLog2 = superBlockShift + 2; + int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSizeLog2); - tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSize; - tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; + tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSizeLog2; + tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSizeLog2; tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs index 34ce917634..ac87eefe74 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuSequenceHeader.cs @@ -5,6 +5,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuSequenceHeader { + private bool use128x128Superblock; + public bool EnableFilterIntra { get; set; } public bool EnableCdef { get; set; } @@ -37,13 +39,23 @@ internal class ObuSequenceHeader public int MaxFrameHeight { get; set; } - public bool Use128x128Superblock { get; set; } + public bool Use128x128Superblock + { + get => this.use128x128Superblock; + set + { + this.use128x128Superblock = value; + this.SuperblockSize = value ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; + this.SuperblockSizeLog2 = value ? 7 : 6; + this.SuperblockModeInfoSize = 1 << (this.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2); + } + } - public Av1BlockSize SuperblockSize { get; set; } + public Av1BlockSize SuperblockSize { get; private set; } - public int SuperblockModeInfoSize { get; set; } + public int SuperblockModeInfoSize { get; private set; } - public int SuperblockSizeLog2 { get; set; } + public int SuperblockSizeLog2 { get; private set; } public int FilterIntraLevel { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 10fdcdf6cf..a05aa63a04 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -114,7 +114,6 @@ public void DecodeTile(Span tileData, int tileNum) Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); - // this.ClearBlockDecodedFlags(modeInfoLocation, superBlock4x4Size); Point modeInfoPosition = new(column, row); this.FrameBuffer.ClearCdef(superblockPosition); this.firstTransformOffset[0] = 0; @@ -128,14 +127,6 @@ public void DecodeTile(Span tileData, int tileNum) private void ClearLoopFilterDelta() => this.FrameBuffer.ClearDeltaLoopFilter(); - /// - /// 5.11.3. Clear block decoded flags function. - /// - private static void ClearBlockDecodedFlags(Point modeInfoLocation, int superBlock4x4Size) - { - // Nothing to do here. - } - private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) { int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; From 5d41465ef815eba95a36e0b6916033265c23e1a1 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 16 Jul 2024 21:49:07 +0200 Subject: [PATCH 120/190] Renaming Superblock variables --- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 4 ++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 72 +++++++++---------- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 8 +-- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 8 +-- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 2848dac8ad..6b0c6fadf5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1Math @@ -141,4 +142,7 @@ internal static int AlignPowerOf2(int value, int n) internal static int Clamp(int value, int low, int high) => value < low ? low : (value > high ? high : value); + + internal static int DivideLog2Ceiling(int value, int n) + => (value + (1 << n) - 1) >> n; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index feb9cfc9cd..aedaf26572 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -694,21 +694,21 @@ private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverride private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { ObuTileGroupHeader tileInfo = new(); - int superBlockColumnCount; - int superBlockRowCount; - int superBlockShift = sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; - superBlockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superBlockShift; - superBlockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superBlockShift; - - int superBlockSizeLog2 = superBlockShift + 2; - int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSizeLog2); - - tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSizeLog2; - tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSizeLog2; - tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); - tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); - tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); - tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); + int superblockColumnCount; + int superblockRowCount; + int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; + int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; + superblockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + + int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1); + + tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superblockSizeLog2; + tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2; + tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superblockColumnCount); + tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount)); + tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount)); tileInfo.HasUniformTileSpacing = reader.ReadBoolean(); if (tileInfo.HasUniformTileSpacing) { @@ -725,17 +725,13 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob } } - int tileWidthSuperBlock = (superBlockColumnCount + (1 << tileInfo.TileColumnCountLog2) - 1) >> tileInfo.TileColumnCountLog2; - if (tileWidthSuperBlock > tileInfo.MaxTileWidthSuperBlock) - { - throw new ImageFormatException("Invalid tile width specified."); - } - + int tileWidthSuperblock = Av1Math.DivideLog2Ceiling(superblockColumnCount, tileInfo.TileColumnCountLog2); + DebugGuard.MustBeLessThanOrEqualTo(tileWidthSuperblock, tileInfo.MaxTileWidthSuperBlock, nameof(tileWidthSuperblock)); int i = 0; - tileInfo.TileColumnStartModeInfo = new int[superBlockColumnCount + 1]; - for (int startSuperBlock = 0; startSuperBlock < superBlockColumnCount; startSuperBlock += tileWidthSuperBlock) + tileInfo.TileColumnStartModeInfo = new int[superblockColumnCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superblockColumnCount; startSuperBlock += tileWidthSuperblock) { - tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superblockShift; i++; } @@ -756,17 +752,17 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob } } - int tileHeightSuperBlock = (superBlockRowCount + (1 << tileInfo.TileRowCountLog2) - 1) >> tileInfo.TileRowCountLog2; + int tileHeightSuperBlock = Av1Math.DivideLog2Ceiling(superblockRowCount, tileInfo.TileRowCountLog2); if (tileHeightSuperBlock > tileInfo.MaxTileHeightSuperBlock) { throw new ImageFormatException("Invalid tile height specified."); } i = 0; - tileInfo.TileRowStartModeInfo = new int[superBlockRowCount + 1]; - for (int startSuperBlock = 0; startSuperBlock < superBlockRowCount; startSuperBlock += tileHeightSuperBlock) + tileInfo.TileRowStartModeInfo = new int[superblockRowCount + 1]; + for (int startSuperBlock = 0; startSuperBlock < superblockRowCount; startSuperBlock += tileHeightSuperBlock) { - tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superblockShift; i++; } @@ -778,16 +774,16 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob uint widestTileSuperBlock = 0U; int startSuperBlock = 0; int i = 0; - for (; startSuperBlock < superBlockColumnCount; i++) + for (; startSuperBlock < superblockColumnCount; i++) { - tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superBlockShift; - uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superblockShift; + uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); uint widthInSuperBlocks = reader.ReadNonSymmetric(maxWidth) + 1; widestTileSuperBlock = Math.Max(widthInSuperBlocks, widestTileSuperBlock); startSuperBlock += (int)widthInSuperBlocks; } - if (startSuperBlock != superBlockColumnCount) + if (startSuperBlock != superblockColumnCount) { throw new ImageFormatException("Super block tiles width does not add up to total width."); } @@ -797,26 +793,26 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob tileInfo.TileColumnCountLog2 = TileLog2(1, tileInfo.TileColumnCount); if (tileInfo.MinLog2TileCount > 0) { - maxTileAreaOfSuperBlock = (superBlockRowCount * superBlockColumnCount) >> (tileInfo.MinLog2TileCount + 1); + maxTileAreaOfSuperBlock = (superblockRowCount * superblockColumnCount) >> (tileInfo.MinLog2TileCount + 1); } else { - maxTileAreaOfSuperBlock = superBlockRowCount * superBlockColumnCount; + maxTileAreaOfSuperBlock = superblockRowCount * superblockColumnCount; } DebugGuard.MustBeGreaterThan(widestTileSuperBlock, 0U, nameof(widestTileSuperBlock)); tileInfo.MaxTileHeightSuperBlock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1); startSuperBlock = 0; - for (i = 0; startSuperBlock < superBlockRowCount; i++) + for (i = 0; startSuperBlock < superblockRowCount; i++) { - tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superBlockShift; - uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superblockShift; + uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); uint heightInSuperBlocks = reader.ReadNonSymmetric(maxHeight) + 1; startSuperBlock += (int)heightInSuperBlocks; } - if (startSuperBlock != superBlockRowCount) + if (startSuperBlock != superblockRowCount) { throw new ImageFormatException("Super block tiles height does not add up to total height."); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 111deae1fd..db5ab07343 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -69,10 +69,9 @@ public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1Bloc int startIndex = modeInfoLocation.X - tileLoc.ModeInfoColumnStart; int bw = blockSize.Get4x4WideCount(); int value = Av1PartitionContext.GetAboveContext(subSize); - for (int i = 0; i < bw; i++) - { - this.AbovePartitionWidth[startIndex + i] = value; - } + + DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.AboveTransformWidth.Length - bw, nameof(startIndex)); + Array.Fill(this.AbovePartitionWidth, value, startIndex, bw); } public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) @@ -85,6 +84,7 @@ public void UpdateTransformation(Point modeInfoLocation, Av1TileInfo tileInfo, A transformWidth = n4w << Av1Constants.ModeInfoSizeLog2; } + DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.AboveTransformWidth.Length - n4w, nameof(startIndex)); Array.Fill(this.AboveTransformWidth, transformWidth, startIndex, n4w); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 8e034f6e90..5b9d18d36f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -69,10 +69,8 @@ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblock int startIndex = (modeInfoLocation.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask; int bh = blockSize.Get4x4HighCount(); int value = Av1PartitionContext.GetLeftContext(subSize); - for (int i = 0; i < bh; i++) - { - this.LeftPartitionHeight[startIndex + i] = value; - } + DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - bh, nameof(startIndex)); + Array.Fill(this.LeftPartitionHeight, value, startIndex, bh); } public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) @@ -85,7 +83,7 @@ public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo super transformHeight = n4h << Av1Constants.ModeInfoSizeLog2; } - DebugGuard.MustBeLessThanOrEqualTo(startIndex + n4h, this.LeftTransformHeight.Length, nameof(startIndex)); + DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - n4h, nameof(startIndex)); Array.Fill(this.LeftTransformHeight, transformHeight, startIndex, n4h); } From 4e1aca91b8903468ac879b9a36af744e82e2244b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 19 Jul 2024 15:26:29 +0200 Subject: [PATCH 121/190] More Superblock renaming --- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 1 - .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 30 ++++++++----------- .../OpenBitstreamUnit/ObuTileGroupHeader.cs | 4 +-- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 10 +++---- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 6b0c6fadf5..43e5717036 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. - namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1Math diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index aedaf26572..d0a79d65d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -703,9 +703,9 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1); - tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superblockSizeLog2; - tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2; - tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperBlock, superblockColumnCount); + tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2; + tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2; + tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount); tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount)); tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount)); @@ -726,12 +726,12 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob } int tileWidthSuperblock = Av1Math.DivideLog2Ceiling(superblockColumnCount, tileInfo.TileColumnCountLog2); - DebugGuard.MustBeLessThanOrEqualTo(tileWidthSuperblock, tileInfo.MaxTileWidthSuperBlock, nameof(tileWidthSuperblock)); + DebugGuard.MustBeLessThanOrEqualTo(tileWidthSuperblock, tileInfo.MaxTileWidthSuperblock, nameof(tileWidthSuperblock)); int i = 0; tileInfo.TileColumnStartModeInfo = new int[superblockColumnCount + 1]; - for (int startSuperBlock = 0; startSuperBlock < superblockColumnCount; startSuperBlock += tileWidthSuperblock) + for (int startSuperblock = 0; startSuperblock < superblockColumnCount; startSuperblock += tileWidthSuperblock) { - tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superblockShift; + tileInfo.TileColumnStartModeInfo[i] = startSuperblock << superblockShift; i++; } @@ -752,17 +752,13 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob } } - int tileHeightSuperBlock = Av1Math.DivideLog2Ceiling(superblockRowCount, tileInfo.TileRowCountLog2); - if (tileHeightSuperBlock > tileInfo.MaxTileHeightSuperBlock) - { - throw new ImageFormatException("Invalid tile height specified."); - } - + int tileHeightSuperblock = Av1Math.DivideLog2Ceiling(superblockRowCount, tileInfo.TileRowCountLog2); + DebugGuard.MustBeLessThanOrEqualTo(tileHeightSuperblock, tileInfo.MaxTileHeightSuperblock, nameof(tileHeightSuperblock)); i = 0; tileInfo.TileRowStartModeInfo = new int[superblockRowCount + 1]; - for (int startSuperBlock = 0; startSuperBlock < superblockRowCount; startSuperBlock += tileHeightSuperBlock) + for (int startSuperblock = 0; startSuperblock < superblockRowCount; startSuperblock += tileHeightSuperblock) { - tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superblockShift; + tileInfo.TileRowStartModeInfo[i] = startSuperblock << superblockShift; i++; } @@ -777,7 +773,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob for (; startSuperBlock < superblockColumnCount; i++) { tileInfo.TileColumnStartModeInfo[i] = startSuperBlock << superblockShift; - uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock); uint widthInSuperBlocks = reader.ReadNonSymmetric(maxWidth) + 1; widestTileSuperBlock = Math.Max(widthInSuperBlocks, widestTileSuperBlock); startSuperBlock += (int)widthInSuperBlocks; @@ -801,13 +797,13 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob } DebugGuard.MustBeGreaterThan(widestTileSuperBlock, 0U, nameof(widestTileSuperBlock)); - tileInfo.MaxTileHeightSuperBlock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1); + tileInfo.MaxTileHeightSuperblock = Math.Max(maxTileAreaOfSuperBlock / (int)widestTileSuperBlock, 1); startSuperBlock = 0; for (i = 0; startSuperBlock < superblockRowCount; i++) { tileInfo.TileRowStartModeInfo[i] = startSuperBlock << superblockShift; - uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock); uint heightInSuperBlocks = reader.ReadNonSymmetric(maxHeight) + 1; startSuperBlock += (int)heightInSuperBlocks; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs index 848fef55b6..a5f1867b66 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuTileGroupHeader.cs @@ -5,9 +5,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuTileGroupHeader { - internal int MaxTileWidthSuperBlock { get; set; } + internal int MaxTileWidthSuperblock { get; set; } - internal int MaxTileHeightSuperBlock { get; set; } + internal int MaxTileHeightSuperblock { get; set; } internal int MinLog2TileColumnCount { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 4a154da0e7..85f744dc15 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -239,9 +239,9 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead int superBlockSize = superBlockShift + 2; int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); - tileInfo.MaxTileWidthSuperBlock = Av1Constants.MaxTileWidth >> superBlockSize; - tileInfo.MaxTileHeightSuperBlock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; - tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperBlock, superBlockColumnCount); + tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize; + tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; + tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superBlockColumnCount); tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); @@ -276,7 +276,7 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead for (; startSuperBlock < superBlockColumnCount; i++) { uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superBlockShift) - startSuperBlock); - uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperBlock); + uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock); writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth); startSuperBlock += (int)widthInSuperBlocks; } @@ -290,7 +290,7 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead for (i = 0; startSuperBlock < superBlockRowCount; i++) { uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superBlockShift) - startSuperBlock); - uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperBlock); + uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock); writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight); startSuperBlock += (int)heightInSuperBlocks; } From 1812983af7fe0348b88a59db9cdd8291f812427f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 19 Jul 2024 22:05:27 +0200 Subject: [PATCH 122/190] Improvements to pretty print routines for OBU headers --- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index a56daacc6e..a965857d14 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Collections; using System.Reflection; using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; @@ -101,24 +102,62 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader.TilesInfo), PrettyPrintProperties(obuReader2.FrameHeader.TilesInfo)); } - private static string PrettyPrintProperties(object obj) + private static readonly char[] spaces = " ".ToCharArray(); + + private static string PrettyPrintProperties(object obj, int indent = 0) { StringBuilder builder = new(); builder.Append(obj.GetType().Name); builder.AppendLine("{"); + indent += 2; MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null); foreach (MemberInfo member in properties) { + builder.Append(spaces, 0, indent); if (member is PropertyInfo property) { builder.Append(property.Name); builder.Append(" = "); object value = property.GetValue(obj) ?? "NULL"; - builder.AppendLine(value.ToString()); + PrettyPrintValue(builder, value, indent); } } + indent -= 2; + builder.Append(spaces, 0, indent); builder.AppendLine("}"); return builder.ToString(); } + + private static void PrettyPrintValue(StringBuilder builder, object value, int indent) + { + if (value.GetType() == typeof(string)) + { + builder.AppendLine(value.ToString()); + } + else if (value.GetType().IsArray) + { + builder.AppendLine("["); + indent += 2; + builder.Append(spaces, 0, indent); + Type elementType = value.GetType().GetElementType(); + IList list = value as IList; + foreach (object item in list) + { + PrettyPrintValue(builder, item, indent); + } + + indent -= 2; + builder.Append(spaces, 0, indent); + builder.AppendLine("]"); + } + else if (value.GetType().IsClass) + { + builder.AppendLine(PrettyPrintProperties(value, indent)); + } + else + { + builder.AppendLine(value.ToString()); + } + } } From 62728b6f6c56e0dbcf2e0f376875173990b800e8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 14:34:23 +0200 Subject: [PATCH 123/190] Add test bitreader test ReadLiteral32BitsWithMsbSet() --- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 8c1c12b0c3..6015d26696 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -41,6 +41,32 @@ public void ReadAsLiteral(uint expected, int bitCount) Assert.Equal(expected, actual); } + [Fact] + public void ReadLiteral32BitsWithMsbSet() + { + // arrange + // Three 32-bit values with MSB set. + byte[] buffer = { + 0xff, 0xff, 0xff, 0xff, // 4294967295 + 0x80, 0xff, 0xee, 0xdd, // 2164256477 + 0xa0, 0xaa, 0xbb, 0xcc // 2695543756 + }; + uint expected0 = 4294967295; + uint expected1 = 2164256477; + uint expected2 = 2695543756; + Av1BitStreamReader reader = new(buffer); + + // act + uint actual0 = reader.ReadLiteral(32); + uint actual1 = reader.ReadLiteral(32); + uint actual2 = reader.ReadLiteral(32); + + // assert + Assert.Equal(expected0, actual0); + Assert.Equal(expected1, actual1); + Assert.Equal(expected2, actual2); + } + [Theory] [InlineData(new bool[] { false, false, true, false, true, false, true, false })] [InlineData(new bool[] { false, true, false, true })] From 38a6f43bf9f3641f06aea075c652452c1d8b6439 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 14:48:57 +0200 Subject: [PATCH 124/190] Add suggestion howto change ReadLiteral() --- .../Formats/Heif/Av1/Av1BitStreamReader2.cs | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs new file mode 100644 index 0000000000..2c0c36af40 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs @@ -0,0 +1,173 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal ref struct Av1BitStreamReader2 +{ + public const int WordSize = 1 << WordSizeLog2; + private const int WordSizeLog2 = 5; + + private readonly Span data; + private int wordPosition = 0; + private int bitOffset = 0; + + public Av1BitStreamReader2(Span data) => this.data = data; + + public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; + + /// + /// Gets the number of bytes in the readers buffer. + /// + public readonly int Length => this.data.Length; + + public void Reset() + { + this.wordPosition = 0; + this.bitOffset = 0; + } + + public void Skip(int bitCount) + { + this.bitOffset += bitCount; + while (this.bitOffset >= WordSize) + { + this.bitOffset -= WordSize; + this.wordPosition++; + } + } + + public uint ReadLiteral(int bitCount) + { + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); + + uint literal = 0; + for (int bit = bitCount - 1; bit >= 0; bit--) + { + literal |= this.ReadBit() << bit; + } + + return literal; + } + + internal uint ReadBit() + { + int byteOffset = DivideBy8(this.bitOffset, false); + byte shift = (byte)(7 - Mod8(this.bitOffset)); + this.bitOffset++; + return (uint)((this.data[byteOffset] >> shift) & 0x01); + } + + internal bool ReadBoolean() => this.ReadLiteral(1) > 0; + + public ulong ReadLittleEndianBytes128(out int length) + { + // See section 4.10.5 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); + + ulong value = 0; + length = 0; + for (int i = 0; i < 56; i += 7) + { + uint leb128Byte = this.ReadLiteral(8); + value |= (leb128Byte & 0x7FUL) << i; + length++; + if ((leb128Byte & 0x80U) == 0) + { + break; + } + } + + return value; + } + + public uint ReadUnsignedVariableLength() + { + // See section 4.10.3 of the AV1-Specification + int leadingZerosCount = 0; + while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) + { + leadingZerosCount++; + } + + if (leadingZerosCount == 32) + { + return uint.MaxValue; + } + + uint basis = (1U << leadingZerosCount) - 1U; + uint value = this.ReadLiteral(leadingZerosCount); + return basis + value; + } + + public uint ReadNonSymmetric(uint n) + { + // See section 4.10.7 of the AV1-Specification + if (n <= 1) + { + return 0; + } + + int w = (int)(Av1Math.FloorLog2(n) + 1); + uint m = (uint)((1 << w) - n); + uint v = this.ReadLiteral(w - 1); + if (v < m) + { + return v; + } + + return (v << 1) - m + this.ReadLiteral(1); + } + + public int ReadSignedFromUnsigned(int n) + { + // See section 4.10.6 of the AV1-Specification + int signedValue; + uint value = this.ReadLiteral(n); + uint signMask = 1U << (n - 1); + if ((value & signMask) == signMask) + { + // Prevent overflow by casting to long; + signedValue = (int)((long)value - (signMask << 1)); + } + else + { + signedValue = (int)value; + } + + return signedValue; + } + + public uint ReadLittleEndian(int n) + { + // See section 4.10.4 of the AV1-Specification + DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian value only allowed on byte alignment"); + + uint t = 0; + for (int i = 0; i < 8 * n; i += 8) + { + t += this.ReadLiteral(8) << i; + } + + return t; + } + + public Span GetSymbolReader(int tileDataSize) + { + DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); + + throw new NotImplementedException("GetSymbolReader is not implemented yet / needs to be reviewd again"); + + // TODO: Pass exact byte iso Word start. + // TODO: This needs to be reviewed again, due to the change in how ReadLiteral() works now! + /*int spanLength = tileDataSize >> WordSizeInBytesLog2; + Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); + this.Skip(tileDataSize << Log2Of8); + return MemoryMarshal.Cast(span);*/ + } + + // Last 3 bits are the value of mod 8. + internal static int Mod8(int n) => n & 0x07; + + internal static int DivideBy8(int n, bool ceil) => (n + (ceil ? 7 : 0)) >> 3; +} From 72c4263fc4d70a90eeb20177be468189ad69d96b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 15:53:09 +0200 Subject: [PATCH 125/190] Add tests for ReadSignedFromUnsigned and ReadLittleEndian --- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 6015d26696..0f91bd4c90 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; +using System.IO; using SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -233,6 +234,39 @@ public void ReadSignedRainbowArray(int bitCount) Assert.Equal(values, actuals); } + [Fact] + public void ReadSignedFromUnsigned() + { + // arrange + byte[] buffer = { 0xd2, 0xa4 }; + Av1BitStreamReader reader = new(buffer); + int expected0 = -23; + int expected1 = 41; + + // act + int actual0 = reader.ReadSignedFromUnsigned(7); + int actual1 = reader.ReadSignedFromUnsigned(7); + + Assert.Equal(expected0, actual0); + Assert.Equal(expected1, actual1); + } + + [Theory] + [InlineData(new byte[] { 0x01 }, 1, 1)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1, 1)] // One byte value with leading bytes. + [InlineData(new byte[] { 0xD9, 0x01 }, 473, 2)] // Two bytes. + [InlineData(new byte[] { 0xD9, 0x01, 0x00, 0x00 }, 473, 2)] // Two byte value with leading bytes. + public void ReadLittleEndian(byte[] buffer, uint expected, int n) + { + // arrange + Av1BitStreamReader reader = new(buffer); + + // act + uint actual = reader.ReadLittleEndian(n); + + Assert.Equal(expected, actual); + } + [Theory] [InlineData(5, 6, 4, -7, -2)] [InlineData(7, 26, -8, -19, -26)] From 0313bdf549dbd5f1b242fa1ca81a0dde6a2129c4 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 20 Jul 2024 17:48:36 +0200 Subject: [PATCH 126/190] Replace Av1BitStreamReader implementation --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 93 +++------- .../Formats/Heif/Av1/Av1BitStreamReader2.cs | 173 ------------------ src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 8 + .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 4 +- 4 files changed, 37 insertions(+), 241 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index c5bd837fdb..f4a1af2b7c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -7,70 +7,40 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader { - public const int WordSize = 1 << WordSizeLog2; - private const int WordSizeLog2 = 5; - private const int WordSizeInBytesLog2 = WordSizeLog2 - Log2Of8; - private const int Log2Of8 = 3; - - private readonly Span data; - private uint currentWord; - private uint nextWord; - private int wordPosition = 0; - private int bitOffset = 0; - - public Av1BitStreamReader(Span data) - { - this.data = MemoryMarshal.Cast(data); - this.wordPosition = -1; - this.AdvanceToNextWord(); - this.AdvanceToNextWord(); - } + private readonly Span data; + + public Av1BitStreamReader(Span data) => this.data = data; - public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; + public int BitPosition { get; private set; } = 0; /// /// Gets the number of bytes in the readers buffer. /// public readonly int Length => this.data.Length; - public void Reset() - { - this.wordPosition = 0; - this.bitOffset = 0; - } + public void Reset() => this.BitPosition = 0; - public void Skip(int bitCount) - { - this.bitOffset += bitCount; - while (this.bitOffset >= WordSize) - { - this.bitOffset -= WordSize; - this.wordPosition++; - } - } + public void Skip(int bitCount) => this.BitPosition += bitCount; public uint ReadLiteral(int bitCount) { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); + DebugGuard.MustBeBetweenOrEqualTo(bitCount, 0, 32, nameof(bitCount)); - uint bits = (this.currentWord << this.bitOffset) >> (WordSize - bitCount); - this.bitOffset += bitCount; - if (this.bitOffset > WordSize) + uint literal = 0; + for (int bit = bitCount - 1; bit >= 0; bit--) { - int overshoot = WordSize + WordSize - this.bitOffset; - if (overshoot < 32) - { - bits |= this.nextWord >> overshoot; - } + literal |= this.ReadBit() << bit; } - if (this.bitOffset >= WordSize) - { - this.AdvanceToNextWord(); - this.bitOffset -= WordSize; - } + return literal; + } - return bits; + internal uint ReadBit() + { + int byteOffset = Av1Math.DivideBy8Floor(this.BitPosition); + byte shift = (byte)(7 - Av1Math.Modulus8(this.BitPosition)); + this.BitPosition++; + return (uint)((this.data[byteOffset] >> shift) & 0x01); } internal bool ReadBoolean() => this.ReadLiteral(1) > 0; @@ -78,7 +48,7 @@ public uint ReadLiteral(int bitCount) public ulong ReadLittleEndianBytes128(out int length) { // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); + DebugGuard.IsTrue((this.BitPosition & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); ulong value = 0; length = 0; @@ -100,7 +70,7 @@ public uint ReadUnsignedVariableLength() { // See section 4.10.3 of the AV1-Specification int leadingZerosCount = 0; - while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) + while (leadingZerosCount < 32 && !this.ReadBoolean()) { leadingZerosCount++; } @@ -156,7 +126,7 @@ public int ReadSignedFromUnsigned(int n) public uint ReadLittleEndian(int n) { // See section 4.10.4 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian value only allowed on byte alignment"); + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Reading of Little Endian value only allowed on byte alignment"); uint t = 0; for (int i = 0; i < 8 * n; i += 8) @@ -167,22 +137,13 @@ public uint ReadLittleEndian(int n) return t; } - public void AdvanceToNextWord() - { - this.currentWord = this.nextWord; - this.wordPosition++; - uint temp = this.data[this.wordPosition]; - this.nextWord = (temp << 24) | ((temp & 0x0000ff00U) << 8) | ((temp & 0x00ff0000U) >> 8) | (temp >> 24); - } - public Span GetSymbolReader(int tileDataSize) { - DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); - - // TODO: Pass exact byte iso Word start. - int spanLength = tileDataSize >> WordSizeInBytesLog2; - Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); - this.Skip(tileDataSize << Log2Of8); - return MemoryMarshal.Cast(span); + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary."); + int bytesRead = Av1Math.DivideBy8Floor(this.BitPosition); + int spanLength = tileDataSize - bytesRead; + Span span = this.data.Slice(bytesRead, spanLength); + this.Skip(tileDataSize << 3); + return span; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs deleted file mode 100644 index 2c0c36af40..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader2.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1; - -internal ref struct Av1BitStreamReader2 -{ - public const int WordSize = 1 << WordSizeLog2; - private const int WordSizeLog2 = 5; - - private readonly Span data; - private int wordPosition = 0; - private int bitOffset = 0; - - public Av1BitStreamReader2(Span data) => this.data = data; - - public readonly int BitPosition => ((this.wordPosition - 1) * WordSize) + this.bitOffset; - - /// - /// Gets the number of bytes in the readers buffer. - /// - public readonly int Length => this.data.Length; - - public void Reset() - { - this.wordPosition = 0; - this.bitOffset = 0; - } - - public void Skip(int bitCount) - { - this.bitOffset += bitCount; - while (this.bitOffset >= WordSize) - { - this.bitOffset -= WordSize; - this.wordPosition++; - } - } - - public uint ReadLiteral(int bitCount) - { - DebugGuard.MustBeBetweenOrEqualTo(bitCount, 1, 32, nameof(bitCount)); - - uint literal = 0; - for (int bit = bitCount - 1; bit >= 0; bit--) - { - literal |= this.ReadBit() << bit; - } - - return literal; - } - - internal uint ReadBit() - { - int byteOffset = DivideBy8(this.bitOffset, false); - byte shift = (byte)(7 - Mod8(this.bitOffset)); - this.bitOffset++; - return (uint)((this.data[byteOffset] >> shift) & 0x01); - } - - internal bool ReadBoolean() => this.ReadLiteral(1) > 0; - - public ulong ReadLittleEndianBytes128(out int length) - { - // See section 4.10.5 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & 0x07) == 0, $"Reading of Little Endian 128 value only allowed on byte alignment (offset {this.BitPosition})."); - - ulong value = 0; - length = 0; - for (int i = 0; i < 56; i += 7) - { - uint leb128Byte = this.ReadLiteral(8); - value |= (leb128Byte & 0x7FUL) << i; - length++; - if ((leb128Byte & 0x80U) == 0) - { - break; - } - } - - return value; - } - - public uint ReadUnsignedVariableLength() - { - // See section 4.10.3 of the AV1-Specification - int leadingZerosCount = 0; - while (leadingZerosCount < 32 && this.ReadLiteral(1) == 0U) - { - leadingZerosCount++; - } - - if (leadingZerosCount == 32) - { - return uint.MaxValue; - } - - uint basis = (1U << leadingZerosCount) - 1U; - uint value = this.ReadLiteral(leadingZerosCount); - return basis + value; - } - - public uint ReadNonSymmetric(uint n) - { - // See section 4.10.7 of the AV1-Specification - if (n <= 1) - { - return 0; - } - - int w = (int)(Av1Math.FloorLog2(n) + 1); - uint m = (uint)((1 << w) - n); - uint v = this.ReadLiteral(w - 1); - if (v < m) - { - return v; - } - - return (v << 1) - m + this.ReadLiteral(1); - } - - public int ReadSignedFromUnsigned(int n) - { - // See section 4.10.6 of the AV1-Specification - int signedValue; - uint value = this.ReadLiteral(n); - uint signMask = 1U << (n - 1); - if ((value & signMask) == signMask) - { - // Prevent overflow by casting to long; - signedValue = (int)((long)value - (signMask << 1)); - } - else - { - signedValue = (int)value; - } - - return signedValue; - } - - public uint ReadLittleEndian(int n) - { - // See section 4.10.4 of the AV1-Specification - DebugGuard.IsTrue((this.bitOffset & (WordSize - 1)) == 0, "Reading of Little Endian value only allowed on byte alignment"); - - uint t = 0; - for (int i = 0; i < 8 * n; i += 8) - { - t += this.ReadLiteral(8) << i; - } - - return t; - } - - public Span GetSymbolReader(int tileDataSize) - { - DebugGuard.IsTrue((this.bitOffset & 0x7) == 0, "Symbol reading needs to start on byte boundary."); - - throw new NotImplementedException("GetSymbolReader is not implemented yet / needs to be reviewd again"); - - // TODO: Pass exact byte iso Word start. - // TODO: This needs to be reviewed again, due to the change in how ReadLiteral() works now! - /*int spanLength = tileDataSize >> WordSizeInBytesLog2; - Span span = this.data.Slice(this.bitOffset >> WordSizeLog2, spanLength); - this.Skip(tileDataSize << Log2Of8); - return MemoryMarshal.Cast(span);*/ - } - - // Last 3 bits are the value of mod 8. - internal static int Mod8(int n) => n & 0x07; - - internal static int DivideBy8(int n, bool ceil) => (n + (ceil ? 7 : 0)) >> 3; -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 43e5717036..936186a3e0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -142,6 +142,14 @@ internal static int AlignPowerOf2(int value, int n) internal static int Clamp(int value, int low, int high) => value < low ? low : (value > high ? high : value); + internal static int DivideLog2Floor(int value, int n) + => value >> n; + internal static int DivideLog2Ceiling(int value, int n) => (value + (1 << n) - 1) >> n; + + // Last 3 bits are the value of mod 8. + internal static int Modulus8(int value) => value & 0x07; + + internal static int DivideBy8Floor(int value) => value >> 3; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index a965857d14..51533ba6fc 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -33,8 +33,8 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) Assert.NotNull(obuReader.SequenceHeader); Assert.NotNull(obuReader.FrameHeader); Assert.NotNull(obuReader.FrameHeader.TilesInfo); - Assert.Equal(reader.Length * Av1BitStreamReader.WordSize, reader.BitPosition); - Assert.Equal(reader.Length * 4, blockSize); + Assert.Equal(reader.Length * 8, reader.BitPosition); + Assert.Equal(reader.Length, blockSize); } /* From cb3f51b9af3cf5490283edd95d3dfb3c5b88fb44 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 19:01:46 +0200 Subject: [PATCH 127/190] Add tests for ReadUnsignedVariableLength() --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 19 +++++++++++++++---- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index f4a1af2b7c..8e034da35b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -70,8 +70,14 @@ public uint ReadUnsignedVariableLength() { // See section 4.10.3 of the AV1-Specification int leadingZerosCount = 0; - while (leadingZerosCount < 32 && !this.ReadBoolean()) + while (leadingZerosCount < 32) { + uint bit = this.ReadLiteral(1); + if (bit == 1) + { + break; + } + leadingZerosCount++; } @@ -80,9 +86,14 @@ public uint ReadUnsignedVariableLength() return uint.MaxValue; } - uint basis = (1U << leadingZerosCount) - 1U; - uint value = this.ReadLiteral(leadingZerosCount); - return basis + value; + if (leadingZerosCount != 0) + { + uint basis = (1U << leadingZerosCount) - 1U; + uint value = this.ReadLiteral(leadingZerosCount); + return basis + value; + } + + return 0; } public uint ReadNonSymmetric(uint n) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 0f91bd4c90..5cfe0acace 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -267,6 +267,23 @@ public void ReadLittleEndian(byte[] buffer, uint expected, int n) Assert.Equal(expected, actual); } + [Theory] + [InlineData(new byte[] { 0x80 }, 0)] // Zero bit value. + [InlineData(new byte[] { 0x60 }, 2)] // One bit value, 011. + [InlineData(new byte[] { 0x38 }, 6)] // Two bit value, 00111. + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE }, uint.MaxValue - 1)] // 31 bit value. + public void ReadUnsignedVariableLength(byte[] buffer, uint expected) + { + // arrange + Av1BitStreamReader reader = new(buffer); + + // act + uint actual = reader.ReadUnsignedVariableLength(); + + // assert + Assert.Equal(expected, actual); + } + [Theory] [InlineData(5, 6, 4, -7, -2)] [InlineData(7, 26, -8, -19, -26)] From 64f424131a773a06a7097a480b748560724c0e9a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 19:10:27 +0200 Subject: [PATCH 128/190] Additional tests for ReadLittleEndianBytes128() --- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 5cfe0acace..9575864760 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -315,6 +315,24 @@ public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int Assert.Equal(values, actuals); } + [Theory] + [InlineData(new byte[] { 0x01 }, 1)] // One byte value. + [InlineData(new byte[] { 0x81, 0x80, 0x80, 0x00 }, 1)] // One byte value with trailing bytes. + [InlineData(new byte[] { 0xD9, 0x01 }, 217)] // Two byte value. + [InlineData(new byte[] { 0xD9, 0x81, 0x80, 0x80, 0x00 }, 217)] // Two byte value with trailing bytes. + public void ReadLittleEndianBytes128(byte[] buffer, ulong expected) + { + // arrange + Av1BitStreamReader reader = new(buffer); + + // act + ulong actual = reader.ReadLittleEndianBytes128(out int length); + + // assert + Assert.Equal(expected, actual); + Assert.NotEqual(0UL, actual); + } + [Theory] [InlineData(4, 6, 7, 9, 14)] [InlineData(8, 42, 8, 189, 63)] From e8b76829973c3fc9abafc7391f7e48942928323f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Jul 2024 19:34:53 +0200 Subject: [PATCH 129/190] Also assert bit position --- .../Formats/Heif/Av1/Av1BitStreamReader.cs | 2 -- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 34 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index 8e034da35b..552f0a80b8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.InteropServices; - namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal ref struct Av1BitStreamReader diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index 9575864760..ce51300f24 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -2,7 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; -using System.IO; using SixLabors.ImageSharp.Formats.Heif.Av1; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -252,11 +251,11 @@ public void ReadSignedFromUnsigned() } [Theory] - [InlineData(new byte[] { 0x01 }, 1, 1)] - [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1, 1)] // One byte value with leading bytes. - [InlineData(new byte[] { 0xD9, 0x01 }, 473, 2)] // Two bytes. - [InlineData(new byte[] { 0xD9, 0x01, 0x00, 0x00 }, 473, 2)] // Two byte value with leading bytes. - public void ReadLittleEndian(byte[] buffer, uint expected, int n) + [InlineData(new byte[] { 0x01 }, 1, 1, 8)] + [InlineData(new byte[] { 0x01, 0x00, 0x00, 0x00 }, 1, 4, 32)] // One byte value with leading bytes. + [InlineData(new byte[] { 0xD9, 0x01 }, 473, 2, 16)] // Two bytes. + [InlineData(new byte[] { 0xD9, 0x01, 0x00, 0x00 }, 473, 4, 32)] // Two byte value with leading bytes. + public void ReadLittleEndian(byte[] buffer, uint expected, int n, int expectedBitPosition) { // arrange Av1BitStreamReader reader = new(buffer); @@ -265,14 +264,15 @@ public void ReadLittleEndian(byte[] buffer, uint expected, int n) uint actual = reader.ReadLittleEndian(n); Assert.Equal(expected, actual); + Assert.Equal(expectedBitPosition, reader.BitPosition); } [Theory] - [InlineData(new byte[] { 0x80 }, 0)] // Zero bit value. - [InlineData(new byte[] { 0x60 }, 2)] // One bit value, 011. - [InlineData(new byte[] { 0x38 }, 6)] // Two bit value, 00111. - [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE }, uint.MaxValue - 1)] // 31 bit value. - public void ReadUnsignedVariableLength(byte[] buffer, uint expected) + [InlineData(new byte[] { 0x80 }, 0, 1)] // Zero bit value. + [InlineData(new byte[] { 0x60 }, 2, 3)] // One bit value, 011. + [InlineData(new byte[] { 0x38 }, 6, 5)] // Two bit value, 00111. + [InlineData(new byte[] { 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE }, uint.MaxValue - 1, 63)] // 31 bit value. + public void ReadUnsignedVariableLength(byte[] buffer, uint expected, int expectedBitPosition) { // arrange Av1BitStreamReader reader = new(buffer); @@ -282,6 +282,7 @@ public void ReadUnsignedVariableLength(byte[] buffer, uint expected) // assert Assert.Equal(expected, actual); + Assert.Equal(expectedBitPosition, reader.BitPosition); } [Theory] @@ -316,11 +317,11 @@ public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int } [Theory] - [InlineData(new byte[] { 0x01 }, 1)] // One byte value. - [InlineData(new byte[] { 0x81, 0x80, 0x80, 0x00 }, 1)] // One byte value with trailing bytes. - [InlineData(new byte[] { 0xD9, 0x01 }, 217)] // Two byte value. - [InlineData(new byte[] { 0xD9, 0x81, 0x80, 0x80, 0x00 }, 217)] // Two byte value with trailing bytes. - public void ReadLittleEndianBytes128(byte[] buffer, ulong expected) + [InlineData(new byte[] { 0x01 }, 1, 8)] // One byte value. + [InlineData(new byte[] { 0x81, 0x80, 0x80, 0x00 }, 1, 32)] // One byte value with trailing bytes. + [InlineData(new byte[] { 0xD9, 0x01 }, 217, 16)] // Two byte value. + [InlineData(new byte[] { 0xD9, 0x81, 0x80, 0x80, 0x00 }, 217, 40)] // Two byte value with trailing bytes. + public void ReadLittleEndianBytes128(byte[] buffer, ulong expected, int expectedBitPosition) { // arrange Av1BitStreamReader reader = new(buffer); @@ -330,6 +331,7 @@ public void ReadLittleEndianBytes128(byte[] buffer, ulong expected) // assert Assert.Equal(expected, actual); + Assert.Equal(expectedBitPosition, reader.BitPosition); Assert.NotEqual(0UL, actual); } From c524346dea8c99ec029a1e6d9967963d0b1c89b0 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 20 Jul 2024 22:23:36 +0200 Subject: [PATCH 130/190] OBU reader bug fixes --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index d0a79d65d8..71ad70f595 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -610,11 +610,13 @@ private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) (frameInfo.FrameSize.SuperResolutionDenominator / 2)) / frameInfo.FrameSize.SuperResolutionDenominator; + /* if (frameInfo.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator) { int manWidth = Math.Min(16, frameInfo.FrameSize.SuperResolutionUpscaledWidth); frameInfo.FrameSize.FrameWidth = Math.Max(manWidth, frameInfo.FrameSize.FrameWidth); } + */ } /// @@ -890,7 +892,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade } frameInfo.FrameType = (ObuFrameType)reader.ReadLiteral(2); - bool frameIsIntra = frameInfo.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame; + isIntraFrame = frameInfo.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame; frameInfo.ShowFrame = reader.ReadBoolean(); if (frameInfo.ShowFrame && !sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) @@ -1010,14 +1012,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameSizeOverrideFlag = reader.ReadBoolean(); } - if (sequenceHeader.OrderHintInfo.OrderHintBits > 0) - { - frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); - } - else - { - frameInfo.OrderHint = 0; - } + frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); if (isIntraFrame || frameInfo.ErrorResilientMode) { @@ -1093,8 +1088,21 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { + // InitConCoefficientCdfs(); SetupPastIndependence(frameInfo); } + else + { + // LoadCdfs(frameInfo.PrimaryReferenceFrame); + // LoadPrevious(); + throw new NotImplementedException(); + } + + if (frameInfo.UseReferenceFrameMotionVectors) + { + // MotionFieldEstimations(); + throw new NotImplementedException(); + } // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); frameInfo.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameInfo); @@ -1108,6 +1116,11 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade { // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); } + else + { + // LoadPreviousSegmentIds(); + throw new NotImplementedException(); + } int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; frameInfo.CodedLossless = true; @@ -1175,7 +1188,8 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade private static void SetupPastIndependence(ObuFrameHeader frameInfo) { - // TODO: Initialize the loop filter parameters. + // TODO: Implement. + // The current frame can be decoded without dependencies on previous coded frame. } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) @@ -1456,9 +1470,9 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob } else { - for (int i = 0; i < MaxSegments; i++) + for (int i = 0; i < Av1Constants.MaxSegmentCount; i++) { - for (int j = 0; j < SegLvlMax; j++) + for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++) { frameInfo.SegmentationParameters.FeatureEnabled[i, j] = false; frameInfo.SegmentationParameters.FeatureData[i, j] = 0; @@ -1468,9 +1482,9 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob frameInfo.SegmentationParameters.SegmentIdPrecedesSkip = false; frameInfo.SegmentationParameters.LastActiveSegmentId = 0; - for (int i = 0; i < MaxSegments; i++) + for (int i = 0; i < Av1Constants.MaxSegmentCount; i++) { - for (int j = 0; j < SegLvlMax; j++) + for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++) { if (frameInfo.SegmentationParameters.FeatureEnabled[i, j]) { From e45bf57f68cd18b2f202863c6ca35cc426ae42c2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Jul 2024 05:45:52 +0200 Subject: [PATCH 131/190] Combine Delta parameters implementation --- ...aLoopFilterParameters.cs => ObuDeltaParameters.cs} | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs | 11 ----------- .../Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 4 ++-- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 8 +++----- 4 files changed, 6 insertions(+), 19 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/{ObuDeltaLoopFilterParameters.cs => ObuDeltaParameters.cs} (87%) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs similarity index 87% rename from src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs rename to src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs index da39e19f3c..c92485eaf1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaLoopFilterParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaParameters.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -internal class ObuDeltaLoopFilterParameters +internal class ObuDeltaParameters { public bool IsPresent { get; internal set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs deleted file mode 100644 index b82bf5cf96..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuDeltaQParameters.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - -internal class ObuDeltaQParameters -{ - public bool IsPresent { get; set; } - - public int Resolution { get; set; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 7509da20a8..6540ff8afe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -49,9 +49,9 @@ internal class ObuFrameHeader public Av1TransformMode TransformMode { get; set; } - public ObuDeltaLoopFilterParameters DeltaLoopFilterParameters { get; set; } = new ObuDeltaLoopFilterParameters(); + public ObuDeltaParameters DeltaLoopFilterParameters { get; set; } = new ObuDeltaParameters(); - public ObuDeltaQParameters DeltaQParameters { get; set; } = new ObuDeltaQParameters(); + public ObuDeltaParameters DeltaQParameters { get; set; } = new ObuDeltaParameters(); internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index a05aa63a04..278eb4dbb0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -30,8 +30,6 @@ internal class Av1TileDecoder : IAv1TileDecoder private readonly Av1ParseLeftNeighbor4x4Context leftNeighborContext; private int currentQuantizerIndex; private readonly int[][] segmentIds = []; - private int deltaLoopFilterResolution = -1; - private readonly bool readDeltas; private readonly int[][] transformUnitCount; private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; @@ -40,7 +38,6 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; - this.readDeltas = frameInfo.DeltaQParameters.IsPresent; // init_main_frame_ctxt this.FrameBuffer = new(this.SequenceHeader); @@ -1420,7 +1417,7 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf this.ReadCdef(ref reader, partitionInfo); - if (this.readDeltas) + if (this.FrameInfo.DeltaQParameters.IsPresent) { this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); this.ReadDeltaLoopFilter(ref reader, partitionInfo); @@ -1756,7 +1753,8 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p { bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << this.deltaLoopFilterResolution)); + int deltaLoopFilterResolution = this.FrameInfo.DeltaLoopFilterParameters.Resolution; + currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << deltaLoopFilterResolution)); } } } From 6e10cc99607416cf7f9eceead3ace34969ab4aa6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Jul 2024 21:43:43 +0200 Subject: [PATCH 132/190] Few Obu unit tests taken over from libgav1 --- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 198 +++++++++++++----- .../Formats/Heif/Av1/ObuPrettyPrint.cs | 70 +++++++ 3 files changed, 220 insertions(+), 54 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 71ad70f595..38507b28aa 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -169,7 +169,7 @@ private static ObuHeader ReadObuHeader(ref Av1BitStreamReader reader) { header.Size++; header.TemporalId = (int)reader.ReadLiteral(3); - header.SpatialId = (int)reader.ReadLiteral(3); + header.SpatialId = (int)reader.ReadLiteral(2); if (reader.ReadLiteral(3) != 0u) { throw new ImageFormatException("Reserved bits in header extension should be unset."); @@ -708,8 +708,8 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superblockSizeLog2; tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superblockSizeLog2; tileInfo.MinLog2TileColumnCount = TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount); - tileInfo.MaxLog2TileColumnCount = TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount)); - tileInfo.MaxLog2TileRowCount = TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount)); + tileInfo.MaxLog2TileColumnCount = (int)Av1Math.CeilLog2((uint)Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = (int)Av1Math.CeilLog2((uint)Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount)); tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount)); tileInfo.HasUniformTileSpacing = reader.ReadBoolean(); if (tileInfo.HasUniformTileSpacing) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 51533ba6fc..2d91f6f7f2 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -12,6 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class ObuFrameHeaderTests { + private static readonly byte[] DefaultSequenceHeaderBitStream = + [0x0a, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x3e, 0xa7, 0xbd, 0xf7, 0xf9, 0x80, 0x40]; + [Theory] // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)] @@ -96,68 +99,161 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b obuReader2.Read(ref reader2, encodedBuffer.Length, tileDecoder2); // Assert - Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); - Assert.Equal(PrettyPrintProperties(obuReader1.SequenceHeader), PrettyPrintProperties(obuReader2.SequenceHeader)); - Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader), PrettyPrintProperties(obuReader2.FrameHeader)); - Assert.Equal(PrettyPrintProperties(obuReader1.FrameHeader.TilesInfo), PrettyPrintProperties(obuReader2.FrameHeader.TilesInfo)); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader)); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.FrameHeader), ObuPrettyPrint.PrettyPrintProperties(obuReader2.FrameHeader)); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.FrameHeader.TilesInfo), ObuPrettyPrint.PrettyPrintProperties(obuReader2.FrameHeader.TilesInfo)); } - private static readonly char[] spaces = " ".ToCharArray(); + [Fact] + public void DefaultTemporalDelimiter() + { + // Arrange + byte[] bitStream = [0x12, 0x00]; + Av1BitStreamReader reader = new(bitStream); + ObuReader obuReader = new(); + IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + + // Act + obuReader.Read(ref reader, bitStream.Length, tileDecoder); - private static string PrettyPrintProperties(object obj, int indent = 0) + // Assert + Assert.Null(obuReader.SequenceHeader); + Assert.Null(obuReader.FrameHeader); + } + + [Fact] + public void DefaultTemporalDelimiterWithExtension() { - StringBuilder builder = new(); - builder.Append(obj.GetType().Name); - builder.AppendLine("{"); - indent += 2; - MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null); - foreach (MemberInfo member in properties) - { - builder.Append(spaces, 0, indent); - if (member is PropertyInfo property) - { - builder.Append(property.Name); - builder.Append(" = "); - object value = property.GetValue(obj) ?? "NULL"; - PrettyPrintValue(builder, value, indent); - } - } + // Bits Syntax element Value + // 1 obu_forbidden_bit 0 + // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) + // 1 obu_extension_flag 1 + // 1 obu_has_size_field 1 + // 1 obu_reserved_1bit 0 + // 3 temporal_id 6 + // 2 spatial_id 2 + // 3 extension_header_reserved_3bits 0 + // 8 obu_size 0 + + // Arrange + byte[] bitStream = [0x16, 0xd0, 0x00]; + Av1BitStreamReader reader = new(bitStream); + ObuReader obuReader = new(); + IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); - indent -= 2; - builder.Append(spaces, 0, indent); - builder.AppendLine("}"); - return builder.ToString(); + // Act + obuReader.Read(ref reader, bitStream.Length, tileDecoder); + + // Assert + Assert.Null(obuReader.SequenceHeader); + Assert.Null(obuReader.FrameHeader); } - private static void PrettyPrintValue(StringBuilder builder, object value, int indent) + [Fact] + public void DefaultHeaderWithoutSizeField() { - if (value.GetType() == typeof(string)) - { - builder.AppendLine(value.ToString()); - } - else if (value.GetType().IsArray) + // Arrange + byte[] bitStream = [0x10]; + Av1BitStreamReader reader = new(bitStream); + ObuReader obuReader = new(); + IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + + // Act + obuReader.Read(ref reader, bitStream.Length, tileDecoder); + + // Assert + Assert.Null(obuReader.SequenceHeader); + Assert.Null(obuReader.FrameHeader); + } + + [Fact] + public void DefaultSequenceHeader() + { + // Offset Bits Syntax element Value + // 0 3 seq_profile 0 + // 3 1 still_picture 0 + // 4 1 reduced_still_picture_header 0 + // 5 1 timing_info_present_flag 0 + // 6 1 initial_display_delay_present_flag 0 + // 7 5 operating_points_cnt_minus_1 0 + // 12 12 operating_point_idc[ 0 ] 0 + // 24 5 seq_level_idx[ 0 ] 0 + // 29 4 frame_width_bits_minus_1 8 + // 33 4 frame_height_bits_minus_1 7 + // 37 9 max_frame_width_minus_1 425 + // 46 8 max_frame_height_minus_1 239 + // 54 1 frame_id_numbers_present_flag 0 + // 55 1 use_128x128_superblock 1 + // 56 1 enable_filter_intra 1 + // 57 1 enable_intra_edge_filter 1 + // 58 1 enable_interintra_compound 1 + // 59 1 enable_masked_compound 1 + // 60 1 enable_warped_motion 0 + // 61 1 enable_dual_filter 1 + // 62 1 enable_order_hint 1 + // 63 1 enable_jnt_comp 1 + // 64 1 enable_ref_frame_mvs 1 + // 65 1 seq_choose_screen_content_tools 1 + // 66 1 seq_choose_integer_mv 1 + // 67 3 order_hint_bits_minus_1 6 + // 70 1 enable_superres 0 + // 71 1 enable_cdef 1 + // 72 1 enable_restoration 1 + // ... + + // Arrange + byte[] bitStream = DefaultSequenceHeaderBitStream; + Av1BitStreamReader reader = new(bitStream); + ObuReader obuReader = new(); + IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + ObuSequenceHeader expected = new() { - builder.AppendLine("["); - indent += 2; - builder.Append(spaces, 0, indent); - Type elementType = value.GetType().GetElementType(); - IList list = value as IList; - foreach (object item in list) + SequenceProfile = 0, + IsStillPicture = false, + IsReducedStillPictureHeader = false, + TimingInfoPresentFlag = false, + InitialDisplayDelayPresentFlag = false, + FrameWidthBits = 8 + 1, + FrameHeightBits = 7 + 1, + MaxFrameWidth = 425 + 1, + MaxFrameHeight = 239 + 1, + IsFrameIdNumbersPresent = false, + Use128x128Superblock = true, + EnableFilterIntra = true, + EnableIntraEdgeFilter = true, + EnableInterIntraCompound = true, + EnableMaskedCompound = true, + EnableWarpedMotion = false, + EnableDualFilter = true, + EnableOrderHint = true, + OperatingPoint = [new()], + + // EnableJountCompound = true, + // EnableReferenceFrameMotionVectors = true, + ForceScreenContentTools = 2, + ForceIntegerMotionVector = 2, + EnableSuperResolution = false, + EnableCdef = true, + EnableRestoration = true, + ColorConfig = new() { - PrettyPrintValue(builder, item, indent); + IsMonochrome = false, + ColorPrimaries = ObuColorPrimaries.Unspecified, + TransferCharacteristics = ObuTransferCharacteristics.Unspecified, + MatrixCoefficients = ObuMatrixCoefficients.Unspecified, + SubSamplingX = true, + SubSamplingY = true, + BitDepth = 8, } + }; - indent -= 2; - builder.Append(spaces, 0, indent); - builder.AppendLine("]"); - } - else if (value.GetType().IsClass) - { - builder.AppendLine(PrettyPrintProperties(value, indent)); - } - else - { - builder.AppendLine(value.ToString()); - } + // Act + obuReader.Read(ref reader, bitStream.Length, tileDecoder); + + // Assert + Assert.NotNull(obuReader.SequenceHeader); + Assert.Null(obuReader.FrameHeader); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader)); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs new file mode 100644 index 0000000000..7730383146 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs @@ -0,0 +1,70 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Collections; +using System.Reflection; +using System.Text; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class ObuPrettyPrint +{ + private static readonly char[] spaces = " ".ToCharArray(); + + public static string PrettyPrintProperties(object obj, int indent = 0) + { + StringBuilder builder = new(); + builder.Append(obj.GetType().Name); + builder.AppendLine("{"); + indent += 2; + MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null); + foreach (MemberInfo member in properties) + { + builder.Append(spaces, 0, indent); + if (member is PropertyInfo property) + { + builder.Append(property.Name); + builder.Append(" = "); + object value = property.GetValue(obj) ?? "NULL"; + PrettyPrintValue(builder, value, indent); + } + } + + indent -= 2; + builder.Append(spaces, 0, indent); + builder.AppendLine("}"); + return builder.ToString(); + } + + private static void PrettyPrintValue(StringBuilder builder, object value, int indent) + { + if (value.GetType() == typeof(string)) + { + builder.AppendLine(value.ToString()); + } + else if (value.GetType().IsArray) + { + builder.AppendLine("["); + indent += 2; + builder.Append(spaces, 0, indent); + Type elementType = value.GetType().GetElementType(); + IList list = value as IList; + foreach (object item in list) + { + PrettyPrintValue(builder, item, indent); + } + + indent -= 2; + builder.Append(spaces, 0, indent); + builder.AppendLine("]"); + } + else if (value.GetType().IsClass) + { + builder.AppendLine(PrettyPrintProperties(value, indent)); + } + else + { + builder.AppendLine(value.ToString()); + } + } +} From 4c019f5056eca26eb70af70a165bb158e00aef51 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Jul 2024 21:44:28 +0200 Subject: [PATCH 133/190] Bug fixes in TileDecoder, inspired by libgav1 --- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 278eb4dbb0..830f3c6454 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -99,15 +99,15 @@ public void DecodeTile(Span tileData, int tileNum) } Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameInfo); - Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - int superBlock4x4Size = superBlockSize.Get4x4WideCount(); - for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += this.SequenceHeader.SuperblockModeInfoSize) + Av1BlockSize superBlockSize = this.SequenceHeader.SuperblockSize; + int superBlock4x4Size = this.SequenceHeader.SuperblockSizeLog2; + for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += superBlock4x4Size) { - int superBlockRow = row << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperblockSizeLog2; + int superBlockRow = (row << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size; this.leftNeighborContext.Clear(this.SequenceHeader); - for (int column = modeInfoColumnStart; column < modeInfoColumnEnd; column += this.SequenceHeader.SuperblockModeInfoSize) + for (int column = modeInfoColumnStart; column < modeInfoColumnEnd; column += superBlock4x4Size) { - int superBlockColumn = column << Av1Constants.ModeInfoSizeLog2 >> this.SequenceHeader.SuperblockSizeLog2; + int superBlockColumn = (column << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size; Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); From 22b7c726c0313856f5ccc069e61c9aedc908f17d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 21 Jul 2024 23:25:29 +0200 Subject: [PATCH 134/190] Implement Read Chroma from Luma syntax --- .../Formats/Heif/Av1/Av1Constants.cs | 5 +++++ .../Av1/Tiling/Av1DefaultDistributions.cs | 12 ++++++++++ .../Heif/Av1/Tiling/Av1Distribution.cs | 5 +++++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 22 +++++++++++++++++++ .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 21 ++++++++++++++---- 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 2cb64a545b..fb8203ae70 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -168,4 +168,9 @@ internal static class Av1Constants public const int BaseRangeSizeMinus1 = 3; public const int MaxBaseRange = 15; + + /// + /// Log2 of number of values for ChromaFromLuma Alpha U and ChromaFromLuma Alpha V. + /// + public const int ChromaFromLumaAlphabetSizeLog2 = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index 3991b55f5f..afda5388eb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -1994,6 +1994,18 @@ internal static class Av1DefaultDistributions ] ]; + public static Av1Distribution ChromeForLumaSign => new(1418, 2123, 13340, 18405, 26972, 28343, 32294); + + public static Av1Distribution[] ChromeForLumaAlpha => + [ + new(7637, 20719, 31401, 32481, 32657, 32688, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724), + new(14365, 23603, 28135, 31168, 32167, 32395, 32487, 32573, 32620, 32647, 32668, 32672, 32676, 32680, 32684), + new(11532, 22380, 28445, 31360, 32349, 32523, 32584, 32649, 32673, 32677, 32681, 32685, 32689, 32693, 32697), + new(26990, 31402, 32282, 32571, 32692, 32696, 32700, 32704, 32708, 32712, 32716, 32720, 32724, 32728, 32732), + new(17248, 26058, 28904, 30608, 31305, 31877, 32126, 32321, 32394, 32464, 32516, 32560, 32576, 32593, 32622), + new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660) + ]; + public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) { int qContext = GetQContext(baseQIndex); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs index 241b6ec4e1..9e4a6d3edd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs @@ -79,6 +79,11 @@ public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uin { } + public Av1Distribution(uint p0, uint p1, uint p2, uint p3, uint p4, uint p5, uint p6, uint p7, uint p8, uint p9, uint p10, uint p11, uint p12, uint p13, uint p14) + : this([p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, 0], 2) + { + } + private Av1Distribution(uint[] props, int speed) { // this.probabilities = props; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 01226aef26..dde2e72e48 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -29,6 +29,8 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][][] coefficientsBaseRange; private readonly Av1Distribution[][] transformBlockSkip; private readonly Av1Distribution[][][] endOfBlockExtra; + private readonly Av1Distribution chromeForLumaSign = Av1DefaultDistributions.ChromeForLumaSign; + private readonly Av1Distribution[] chromeForLumaAlpha = Av1DefaultDistributions.ChromeForLumaAlpha; private Av1SymbolReader reader; public Av1SymbolDecoder(Span tileData, int qIndex) @@ -236,6 +238,26 @@ public int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transfo return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } + public int ReadChromFromLumaSign() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.chromeForLumaSign); + } + + public int ReadChromaFromLumaAlphaU(int jointSign) + { + ref Av1SymbolReader r = ref this.reader; + int context = jointSign + 1 - 3; + return r.ReadSymbol(this.chromeForLumaAlpha[context]); + } + + public int ReadChromaFromLumaAlphaV(int jointSign) + { + ref Av1SymbolReader r = ref this.reader; + int context = (((jointSign + 1) % 3) * 3) + ((jointSign + 1) / 3) - 3; + return r.ReadSymbol(this.chromeForLumaAlpha[context]); + } + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 830f3c6454..49f2cbd050 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -1449,7 +1449,7 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf partitionInfo.ModeInfo.UvMode = reader.ReadIntraModeUv(partitionInfo.ModeInfo.YMode, this.IsChromaForLumaAllowed(partitionInfo)); if (partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma) { - this.ReadChromaFromLumaAlphas(ref reader, partitionInfo); + ReadChromaFromLumaAlphas(ref reader, partitionInfo.ModeInfo); } // 5.11.43.Intra angle info chroma syntax. @@ -1523,10 +1523,23 @@ private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo parti /// /// 5.11.45. Read CFL alphas syntax. /// - private void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) => + private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo) + { + int jointSign = reader.ReadChromFromLumaSign(); + int index = 0; + if (jointSign + 1 != 0) + { + index = reader.ReadChromaFromLumaAlphaU(jointSign) << Av1Constants.ChromaFromLumaAlphabetSizeLog2; + } - // TODO: Implement. - throw new NotImplementedException(); + if ((jointSign + 1) % 3 != 0) + { + index += reader.ReadChromaFromLumaAlphaV(jointSign); + } + + modeInfo.ChromaFromLumaAlphaSign = jointSign; + modeInfo.ChromaFromLumaAlphaIndex = index; + } /// /// 5.11.42. and 5.11.43. From 43232056c25821d7bd1e46bf1b7d432573f01459 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 22 Jul 2024 01:04:06 +0200 Subject: [PATCH 135/190] Misc tile parsing fixes --- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 9 ++-- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 52 ++++++++++--------- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 11 ++-- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index dde2e72e48..09afb9b752 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -9,6 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; internal ref struct Av1SymbolDecoder { private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; + private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; @@ -244,17 +245,17 @@ public int ReadChromFromLumaSign() return r.ReadSymbol(this.chromeForLumaSign); } - public int ReadChromaFromLumaAlphaU(int jointSign) + public int ReadChromaFromLumaAlphaU(int jointSignPlus1) { ref Av1SymbolReader r = ref this.reader; - int context = jointSign + 1 - 3; + int context = jointSignPlus1 - 3; return r.ReadSymbol(this.chromeForLumaAlpha[context]); } - public int ReadChromaFromLumaAlphaV(int jointSign) + public int ReadChromaFromLumaAlphaV(int jointSignPlus1) { ref Av1SymbolReader r = ref this.reader; - int context = (((jointSign + 1) % 3) * 3) + ((jointSign + 1) / 3) - 3; + int context = AlphaVContexts[jointSignPlus1]; return r.ReadSymbol(this.chromeForLumaAlpha[context]); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 49f2cbd050..569f633b10 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -154,25 +154,29 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, int quarterBlock4x4Size = halfBlock4x4Size >> 1; bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; - int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); - Av1PartitionType partitionType = Av1PartitionType.Split; - if (blockSize < Av1BlockSize.Block8x8) - { - partitionType = Av1PartitionType.None; - } - else if (hasRows && hasColumns) - { - partitionType = reader.ReadPartitionType(ctx); - } - else if (hasColumns) - { - bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); - partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal; - } - else if (hasRows) + Av1PartitionType partitionType = Av1PartitionType.None; + if (blockSize >= Av1BlockSize.Block8x8) { - bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); - partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical; + int ctx = this.GetPartitionPlaneContext(modeInfoLocation, blockSize, tileInfo, superblockInfo); + partitionType = Av1PartitionType.Split; + if (blockSize < Av1BlockSize.Block8x8) + { + partitionType = Av1PartitionType.None; + } + else if (hasRows && hasColumns) + { + partitionType = reader.ReadPartitionType(ctx); + } + else if (hasColumns) + { + bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); + partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + } + else if (hasRows) + { + bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); + partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical; + } } Av1BlockSize subSize = partitionType.GetBlockSubSize(blockSize); @@ -1525,19 +1529,19 @@ private void PaletteModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo parti /// private static void ReadChromaFromLumaAlphas(ref Av1SymbolDecoder reader, Av1BlockModeInfo modeInfo) { - int jointSign = reader.ReadChromFromLumaSign(); + int jointSignPlus1 = reader.ReadChromFromLumaSign() + 1; int index = 0; - if (jointSign + 1 != 0) + if (jointSignPlus1 >= 3) { - index = reader.ReadChromaFromLumaAlphaU(jointSign) << Av1Constants.ChromaFromLumaAlphabetSizeLog2; + index = reader.ReadChromaFromLumaAlphaU(jointSignPlus1) << Av1Constants.ChromaFromLumaAlphabetSizeLog2; } - if ((jointSign + 1) % 3 != 0) + if (jointSignPlus1 % 3 != 0) { - index += reader.ReadChromaFromLumaAlphaV(jointSign); + index += reader.ReadChromaFromLumaAlphaV(jointSignPlus1); } - modeInfo.ChromaFromLumaAlphaSign = jointSign; + modeInfo.ChromaFromLumaAlphaSign = jointSignPlus1 - 1; modeInfo.ChromaFromLumaAlphaIndex = index; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index ce51300f24..ec9d1114da 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -46,11 +46,12 @@ public void ReadLiteral32BitsWithMsbSet() { // arrange // Three 32-bit values with MSB set. - byte[] buffer = { - 0xff, 0xff, 0xff, 0xff, // 4294967295 - 0x80, 0xff, 0xee, 0xdd, // 2164256477 - 0xa0, 0xaa, 0xbb, 0xcc // 2695543756 - }; + byte[] buffer = + [ + 0xff, 0xff, 0xff, 0xff, // 4294967295 + 0x80, 0xff, 0xee, 0xdd, // 2164256477 + 0xa0, 0xaa, 0xbb, 0xcc // 2695543756 + ]; uint expected0 = 4294967295; uint expected1 = 2164256477; uint expected2 = 2695543756; From 984d0ac0c42668ab8cf32f7b1ddb50b900a39bcb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 23 Jul 2024 22:13:33 +0200 Subject: [PATCH 136/190] Additional unit tests for OBU bitstream --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 7 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 44 ++-- .../Heif/Av1/Transform/Av1TransformType.cs | 2 +- .../Formats/Heif/Av1/Av1ScanOrderTests.cs | 44 ++++ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 226 +++++++++--------- 7 files changed, 200 insertions(+), 131 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index 3cac9f2d4a..67ae414159 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -27,7 +27,12 @@ public void Skip(int bitCount) public void Flush() { - this.stream.WriteByte(this.buffer); + if (Av1Math.Modulus8(this.bitOffset) != 0) + { + // Flush a partial byte also. + this.stream.WriteByte(this.buffer); + } + this.bitOffset = 0; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index aafe086447..7721f6d39c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -21,7 +21,7 @@ internal class Av1Decoder : IAv1TileDecoder public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); - this.obuReader.Read(ref reader, buffer.Length, this, false); + this.obuReader.ReadAll(ref reader, buffer.Length, this, false); this.frameBuffer = this.tileDecoder?.FrameBuffer; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 38507b28aa..36c9b0c15a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -45,7 +45,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public void Read(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) + public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) { bool seenFrameHeader = false; bool frameDecodingFinished = false; @@ -258,7 +258,7 @@ ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrame /// /// 5.5.1. General sequence header OBU syntax. /// - private static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) + internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { sequenceHeader.SequenceProfile = (ObuSequenceProfile)reader.ReadLiteral(3); if (sequenceHeader.SequenceProfile > Av1Constants.MaxSequenceProfile) @@ -1212,7 +1212,7 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i /// /// 5.9.1. General frame header OBU syntax. /// - private void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) + internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { int planeCount = this.SequenceHeader!.ColorConfig.PlaneCount; int startBitPosition = reader.BitPosition; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 85f744dc15..ea38b28975 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -10,25 +10,37 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public static void Write(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + public static void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); - WriteSequenceHeader(ref writer, sequenceHeader); - writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); + if (sequenceHeader != null) + { + WriteSequenceHeader(ref writer, sequenceHeader); + int bytesWritten = (writer.BitPosition + 7) >> 3; + writer.Flush(); + WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), bytesWritten); + } - bufferStream.Position = 0; - WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); - writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), (int)bufferStream.Position); + if (frameInfo != null && sequenceHeader != null) + { + bufferStream.Position = 0; + WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); + int bytesWritten = (writer.BitPosition + 7) >> 3; + writer.Flush(); + WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), bytesWritten); + } - bufferStream.Position = 0; - WriteTileGroup(ref writer, frameInfo.TilesInfo); - writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), (int)bufferStream.Position); + if (frameInfo?.TilesInfo != null) + { + bufferStream.Position = 0; + WriteTileGroup(ref writer, frameInfo.TilesInfo); + int bytesWritten = (writer.BitPosition + 7) >> 3; + writer.Flush(); + WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), bytesWritten); + } } private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type) @@ -58,7 +70,10 @@ private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span /// Number of Transform types. /// - TransformTypes, + AllTransformTypes, /// /// Invalid value. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs new file mode 100644 index 0000000000..8bceeb136b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1ScanOrderTests +{ + [Theory] + [MemberData(nameof(GetCombinations))] + internal void AllIndicesScannedExactlyOnce(int s, int t) + { + // Assign + HashSet visitedScans = []; + Av1TransformSize transformSize = (Av1TransformSize)s; + Av1TransformType transformType = (Av1TransformType)t; + + // Act + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); + + // Assert + foreach (short scan in scanOrder.Scan) + { + Assert.False(visitedScans.Contains(scan), $"Scan {scan} already visited before."); + visitedScans.Add(scan); + } + } + + public static TheoryData GetCombinations() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) + { + for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; t++) + { + combinations.Add(s, t); + } + } + + return combinations; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 2d91f6f7f2..d17c49a179 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -1,9 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Collections; -using System.Reflection; -using System.Text; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -13,7 +10,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; public class ObuFrameHeaderTests { private static readonly byte[] DefaultSequenceHeaderBitStream = - [0x0a, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x3e, 0xa7, 0xbd, 0xf7, 0xf9, 0x80, 0x40]; + [0x0a, 0x06, 0b001_1_1_000, 0b00_1000_01, 0b11_110101, 0b001_11101, 0b111_1_1_1_0_1, 0b1_0_0_1_1_1_10]; + + // Bits Syntax element Value + // 1 obu_forbidden_bit 0 + // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) + // 1 obu_extension_flag 0 + // 1 obu_has_size_field 1 + // 1 obu_reserved_1bit 0 + // 8 obu_size 0 + private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00]; [Theory] // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] @@ -30,7 +36,7 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) ObuReader obuReader = new(); // Act - obuReader.Read(ref reader, blockSize, decoder); + obuReader.ReadAll(ref reader, blockSize, decoder); // Assert Assert.NotNull(obuReader.SequenceHeader); @@ -54,7 +60,7 @@ public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, ObuReader obuReader = new(); // Act 1 - obuReader.Read(ref reader, blockSize, tileDecoder); + obuReader.ReadAll(ref reader, blockSize, tileDecoder); // Assign 2 MemoryStream encoded = new(); @@ -81,13 +87,13 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b ObuReader obuReader1 = new(); // Act 1 - obuReader1.Read(ref reader, blockSize, tileDecoder); + obuReader1.ReadAll(ref reader, blockSize, tileDecoder); // Assign 2 MemoryStream encoded = new(); // Act 2 - ObuWriter.Write(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader); + ObuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader); // Assign 2 Span encodedBuffer = encoded.ToArray(); @@ -96,7 +102,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b ObuReader obuReader2 = new(); // Act 2 - obuReader2.Read(ref reader2, encodedBuffer.Length, tileDecoder2); + obuReader2.ReadAll(ref reader2, encodedBuffer.Length, tileDecoder2); // Assert Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); @@ -106,16 +112,15 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b } [Fact] - public void DefaultTemporalDelimiter() + public void ReadTemporalDelimiter() { // Arrange - byte[] bitStream = [0x12, 0x00]; - Av1BitStreamReader reader = new(bitStream); + Av1BitStreamReader reader = new(DefaultTemporalDelimiterBitStream); ObuReader obuReader = new(); IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); // Act - obuReader.Read(ref reader, bitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, tileDecoder); // Assert Assert.Null(obuReader.SequenceHeader); @@ -123,27 +128,16 @@ public void DefaultTemporalDelimiter() } [Fact] - public void DefaultTemporalDelimiterWithExtension() + public void ReadHeaderWithoutSizeField() { - // Bits Syntax element Value - // 1 obu_forbidden_bit 0 - // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) - // 1 obu_extension_flag 1 - // 1 obu_has_size_field 1 - // 1 obu_reserved_1bit 0 - // 3 temporal_id 6 - // 2 spatial_id 2 - // 3 extension_header_reserved_3bits 0 - // 8 obu_size 0 - // Arrange - byte[] bitStream = [0x16, 0xd0, 0x00]; + byte[] bitStream = [0x10]; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); // Act - obuReader.Read(ref reader, bitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder); // Assert Assert.Null(obuReader.SequenceHeader); @@ -151,109 +145,119 @@ public void DefaultTemporalDelimiterWithExtension() } [Fact] - public void DefaultHeaderWithoutSizeField() + public void ReadSequenceHeader() { // Arrange - byte[] bitStream = [0x10]; + byte[] bitStream = DefaultSequenceHeaderBitStream; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + ObuSequenceHeader expected = GetDefaultSequenceHeader(); // Act - obuReader.Read(ref reader, bitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder); // Assert - Assert.Null(obuReader.SequenceHeader); + Assert.NotNull(obuReader.SequenceHeader); Assert.Null(obuReader.FrameHeader); + Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader)); } [Fact] - public void DefaultSequenceHeader() + public void WriteTemporalDelimiter() { - // Offset Bits Syntax element Value - // 0 3 seq_profile 0 - // 3 1 still_picture 0 - // 4 1 reduced_still_picture_header 0 - // 5 1 timing_info_present_flag 0 - // 6 1 initial_display_delay_present_flag 0 - // 7 5 operating_points_cnt_minus_1 0 - // 12 12 operating_point_idc[ 0 ] 0 - // 24 5 seq_level_idx[ 0 ] 0 - // 29 4 frame_width_bits_minus_1 8 - // 33 4 frame_height_bits_minus_1 7 - // 37 9 max_frame_width_minus_1 425 - // 46 8 max_frame_height_minus_1 239 - // 54 1 frame_id_numbers_present_flag 0 - // 55 1 use_128x128_superblock 1 - // 56 1 enable_filter_intra 1 - // 57 1 enable_intra_edge_filter 1 - // 58 1 enable_interintra_compound 1 - // 59 1 enable_masked_compound 1 - // 60 1 enable_warped_motion 0 - // 61 1 enable_dual_filter 1 - // 62 1 enable_order_hint 1 - // 63 1 enable_jnt_comp 1 - // 64 1 enable_ref_frame_mvs 1 - // 65 1 seq_choose_screen_content_tools 1 - // 66 1 seq_choose_integer_mv 1 - // 67 3 order_hint_bits_minus_1 6 - // 70 1 enable_superres 0 - // 71 1 enable_cdef 1 - // 72 1 enable_restoration 1 - // ... + // Arrange + using MemoryStream stream = new(2); + // Act + ObuWriter.WriteAll(stream, null, null); + byte[] actual = stream.GetBuffer(); + + // Assert + Assert.Equal(DefaultTemporalDelimiterBitStream, actual); + } + + [Fact] + public void WriteSequenceHeader() + { // Arrange - byte[] bitStream = DefaultSequenceHeaderBitStream; - Av1BitStreamReader reader = new(bitStream); - ObuReader obuReader = new(); - IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); - ObuSequenceHeader expected = new() - { - SequenceProfile = 0, - IsStillPicture = false, - IsReducedStillPictureHeader = false, - TimingInfoPresentFlag = false, - InitialDisplayDelayPresentFlag = false, - FrameWidthBits = 8 + 1, - FrameHeightBits = 7 + 1, - MaxFrameWidth = 425 + 1, - MaxFrameHeight = 239 + 1, - IsFrameIdNumbersPresent = false, - Use128x128Superblock = true, - EnableFilterIntra = true, - EnableIntraEdgeFilter = true, - EnableInterIntraCompound = true, - EnableMaskedCompound = true, - EnableWarpedMotion = false, - EnableDualFilter = true, - EnableOrderHint = true, - OperatingPoint = [new()], - - // EnableJountCompound = true, - // EnableReferenceFrameMotionVectors = true, - ForceScreenContentTools = 2, - ForceIntegerMotionVector = 2, - EnableSuperResolution = false, - EnableCdef = true, - EnableRestoration = true, - ColorConfig = new() - { - IsMonochrome = false, - ColorPrimaries = ObuColorPrimaries.Unspecified, - TransferCharacteristics = ObuTransferCharacteristics.Unspecified, - MatrixCoefficients = ObuMatrixCoefficients.Unspecified, - SubSamplingX = true, - SubSamplingY = true, - BitDepth = 8, - } - }; + using MemoryStream stream = new(10); + ObuSequenceHeader input = GetDefaultSequenceHeader(); // Act - obuReader.Read(ref reader, bitStream.Length, tileDecoder); + ObuWriter.WriteAll(stream, input, null); + byte[] buffer = stream.GetBuffer(); // Assert - Assert.NotNull(obuReader.SequenceHeader); - Assert.Null(obuReader.FrameHeader); - Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(expected), ObuPrettyPrint.PrettyPrintProperties(obuReader.SequenceHeader)); + // Skip over Temporal Delimiter header. + byte[] actual = buffer.AsSpan()[DefaultTemporalDelimiterBitStream.Length..].ToArray(); + Assert.Equal(DefaultSequenceHeaderBitStream, actual); } + + private static ObuSequenceHeader GetDefaultSequenceHeader() + + // Offset Bits Syntax element Value + // 0 3 seq_profile 1 + // 3 1 still_picture 1 + // 4 1 reduced_still_picture_header 1 + // 5 5 seq_level_idx[ 0 ] 0 + // 10 4 frame_width_bits_minus_1 8 + // 14 4 frame_height_bits_minus_1 7 + // 18 9 max_frame_width_minus_1 425 + // 27 8 max_frame_height_minus_1 239 + // 35 1 use_128x128_superblock 1 + // 36 1 enable_filter_intra 1 + // 37 1 enable_intra_edge_filter 1 + // 38 1 enable_superres 0 + // 39 1 enable_cdef 1 + // 40 1 enable_restoration 1 + // 41 1 ColorConfig.BitDepth.HasHighBit 0 + // 42 1 ColorConfig.IsDescriptionPresent 0 + // 43 1 ColorConfig.ColorRange 1 + // 44 1 ColorConfig.HasSeparateUVDelta 1 + // 45 1 film_grain_present 1 + // 47 2 Trailing bits 2 + => new() + { + SequenceProfile = ObuSequenceProfile.High, + IsStillPicture = true, + IsReducedStillPictureHeader = true, + TimingInfoPresentFlag = false, + InitialDisplayDelayPresentFlag = false, + FrameWidthBits = 8 + 1, + FrameHeightBits = 7 + 1, + MaxFrameWidth = 425 + 1, + MaxFrameHeight = 239 + 1, + IsFrameIdNumbersPresent = false, + Use128x128Superblock = true, + EnableFilterIntra = true, + EnableIntraEdgeFilter = true, + EnableInterIntraCompound = false, + EnableMaskedCompound = false, + EnableWarpedMotion = false, + EnableDualFilter = false, + EnableOrderHint = false, + OperatingPoint = [new()], + + // EnableJountCompound = true, + // EnableReferenceFrameMotionVectors = true, + ForceScreenContentTools = 2, + ForceIntegerMotionVector = 2, + EnableSuperResolution = false, + EnableCdef = true, + EnableRestoration = true, + ColorConfig = new() + { + IsMonochrome = false, + ColorPrimaries = ObuColorPrimaries.Unspecified, + TransferCharacteristics = ObuTransferCharacteristics.Unspecified, + MatrixCoefficients = ObuMatrixCoefficients.Unspecified, + SubSamplingX = false, + SubSamplingY = false, + BitDepth = 8, + HasSeparateUvDelta = true, + ColorRange = true, + }, + AreFilmGrainingParametersPresent = true, + }; } From e4743ce418e47de2897d0b99d959a01477295c18 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 23 Jul 2024 22:28:54 +0200 Subject: [PATCH 137/190] Fix build --- tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index c0ce99c3ae..baf77ed488 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -22,7 +22,7 @@ public void ReadFirstTile(string filename, int headerOffset, int headerSize, int Av1BitStreamReader reader = new(headerSpan); IAv1TileDecoder stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); - obuReader.Read(ref reader, headerSize, stub); + obuReader.ReadAll(ref reader, headerSize, stub); Av1TileDecoder decoder = new(obuReader.SequenceHeader, obuReader.FrameHeader); // Act From 26d8c2225df994691913953c60a7f0f140845b35 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 25 Jul 2024 20:14:52 +0200 Subject: [PATCH 138/190] Scan order unit test and fixes --- .../Heif/Av1/Transform/Av1ScanOrder.cs | 8 +- .../Av1/Transform/Av1ScanOrderConstants.cs | 913 +++++++----------- .../Transform/Av1TransformSizeExtensions.cs | 8 +- .../Formats/Heif/Av1/Av1ScanOrderTests.cs | 37 + 4 files changed, 395 insertions(+), 571 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs index 513ef27c61..94650b1766 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -8,20 +8,20 @@ internal readonly struct Av1ScanOrder public Av1ScanOrder(short[] scan) { this.Scan = scan; - this.IScan = []; + this.InverseScan = []; this.Neighbors = []; } - public Av1ScanOrder(short[] scan, short[] iscan, short[] neighbors) + public Av1ScanOrder(short[] scan, short[] inverseScan, short[] neighbors) { this.Scan = scan; - this.IScan = iscan; + this.InverseScan = inverseScan; this.Neighbors = neighbors; } public short[] Scan { get; } - public short[] IScan { get; } + public short[] InverseScan { get; } public short[] Neighbors { get; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs index f3ef2c9e06..5acac18c7f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrderConstants.cs @@ -211,60 +211,6 @@ internal static class Av1ScanOrderConstants 205, 221, 237, 253, 14, 30, 46, 62, 78, 94, 110, 126, 142, 158, 174, 190, 206, 222, 238, 254, 15, 31, 47, 63, 79, 95, 111, 127, 143, 159, 175, 191, 207, 223, 239, 255,]; - private static readonly short[] MatrixColumnScan32x32 = [ - 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 512, 544, 576, 608, - 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1, 33, 65, 97, 129, 161, 193, 225, - 257, 289, 321, 353, 385, 417, 449, 481, 513, 545, 577, 609, 641, 673, 705, 737, 769, 801, 833, 865, - 897, 929, 961, 993, 2, 34, 66, 98, 130, 162, 194, 226, 258, 290, 322, 354, 386, 418, 450, 482, - 514, 546, 578, 610, 642, 674, 706, 738, 770, 802, 834, 866, 898, 930, 962, 994, 3, 35, 67, 99, - 131, 163, 195, 227, 259, 291, 323, 355, 387, 419, 451, 483, 515, 547, 579, 611, 643, 675, 707, 739, - 771, 803, 835, 867, 899, 931, 963, 995, 4, 36, 68, 100, 132, 164, 196, 228, 260, 292, 324, 356, - 388, 420, 452, 484, 516, 548, 580, 612, 644, 676, 708, 740, 772, 804, 836, 868, 900, 932, 964, 996, - 5, 37, 69, 101, 133, 165, 197, 229, 261, 293, 325, 357, 389, 421, 453, 485, 517, 549, 581, 613, - 645, 677, 709, 741, 773, 805, 837, 869, 901, 933, 965, 997, 6, 38, 70, 102, 134, 166, 198, 230, - 262, 294, 326, 358, 390, 422, 454, 486, 518, 550, 582, 614, 646, 678, 710, 742, 774, 806, 838, 870, - 902, 934, 966, 998, 7, 39, 71, 103, 135, 167, 199, 231, 263, 295, 327, 359, 391, 423, 455, 487, - 519, 551, 583, 615, 647, 679, 711, 743, 775, 807, 839, 871, 903, 935, 967, 999, 8, 40, 72, 104, - 136, 168, 200, 232, 264, 296, 328, 360, 392, 424, 456, 488, 520, 552, 584, 616, 648, 680, 712, 744, - 776, 808, 840, 872, 904, 936, 968, 1000, 9, 41, 73, 105, 137, 169, 201, 233, 265, 297, 329, 361, - 393, 425, 457, 489, 521, 553, 585, 617, 649, 681, 713, 745, 777, 809, 841, 873, 905, 937, 969, 1001, - 10, 42, 74, 106, 138, 170, 202, 234, 266, 298, 330, 362, 394, 426, 458, 490, 522, 554, 586, 618, - 650, 682, 714, 746, 778, 810, 842, 874, 906, 938, 970, 1002, 11, 43, 75, 107, 139, 171, 203, 235, - 267, 299, 331, 363, 395, 427, 459, 491, 523, 555, 587, 619, 651, 683, 715, 747, 779, 811, 843, 875, - 907, 939, 971, 1003, 12, 44, 76, 108, 140, 172, 204, 236, 268, 300, 332, 364, 396, 428, 460, 492, - 524, 556, 588, 620, 652, 684, 716, 748, 780, 812, 844, 876, 908, 940, 972, 1004, 13, 45, 77, 109, - 141, 173, 205, 237, 269, 301, 333, 365, 397, 429, 461, 493, 525, 557, 589, 621, 653, 685, 717, 749, - 781, 813, 845, 877, 909, 941, 973, 1005, 14, 46, 78, 110, 142, 174, 206, 238, 270, 302, 334, 366, - 398, 430, 462, 494, 526, 558, 590, 622, 654, 686, 718, 750, 782, 814, 846, 878, 910, 942, 974, 1006, - 15, 47, 79, 111, 143, 175, 207, 239, 271, 303, 335, 367, 399, 431, 463, 495, 527, 559, 591, 623, - 655, 687, 719, 751, 783, 815, 847, 879, 911, 943, 975, 1007, 16, 48, 80, 112, 144, 176, 208, 240, - 272, 304, 336, 368, 400, 432, 464, 496, 528, 560, 592, 624, 656, 688, 720, 752, 784, 816, 848, 880, - 912, 944, 976, 1008, 17, 49, 81, 113, 145, 177, 209, 241, 273, 305, 337, 369, 401, 433, 465, 497, - 529, 561, 593, 625, 657, 689, 721, 753, 785, 817, 849, 881, 913, 945, 977, 1009, 18, 50, 82, 114, - 146, 178, 210, 242, 274, 306, 338, 370, 402, 434, 466, 498, 530, 562, 594, 626, 658, 690, 722, 754, - 786, 818, 850, 882, 914, 946, 978, 1010, 19, 51, 83, 115, 147, 179, 211, 243, 275, 307, 339, 371, - 403, 435, 467, 499, 531, 563, 595, 627, 659, 691, 723, 755, 787, 819, 851, 883, 915, 947, 979, 1011, - 20, 52, 84, 116, 148, 180, 212, 244, 276, 308, 340, 372, 404, 436, 468, 500, 532, 564, 596, 628, - 660, 692, 724, 756, 788, 820, 852, 884, 916, 948, 980, 1012, 21, 53, 85, 117, 149, 181, 213, 245, - 277, 309, 341, 373, 405, 437, 469, 501, 533, 565, 597, 629, 661, 693, 725, 757, 789, 821, 853, 885, - 917, 949, 981, 1013, 22, 54, 86, 118, 150, 182, 214, 246, 278, 310, 342, 374, 406, 438, 470, 502, - 534, 566, 598, 630, 662, 694, 726, 758, 790, 822, 854, 886, 918, 950, 982, 1014, 23, 55, 87, 119, - 151, 183, 215, 247, 279, 311, 343, 375, 407, 439, 471, 503, 535, 567, 599, 631, 663, 695, 727, 759, - 791, 823, 855, 887, 919, 951, 983, 1015, 24, 56, 88, 120, 152, 184, 216, 248, 280, 312, 344, 376, - 408, 440, 472, 504, 536, 568, 600, 632, 664, 696, 728, 760, 792, 824, 856, 888, 920, 952, 984, 1016, - 25, 57, 89, 121, 153, 185, 217, 249, 281, 313, 345, 377, 409, 441, 473, 505, 537, 569, 601, 633, - 665, 697, 729, 761, 793, 825, 857, 889, 921, 953, 985, 1017, 26, 58, 90, 122, 154, 186, 218, 250, - 282, 314, 346, 378, 410, 442, 474, 506, 538, 570, 602, 634, 666, 698, 730, 762, 794, 826, 858, 890, - 922, 954, 986, 1018, 27, 59, 91, 123, 155, 187, 219, 251, 283, 315, 347, 379, 411, 443, 475, 507, - 539, 571, 603, 635, 667, 699, 731, 763, 795, 827, 859, 891, 923, 955, 987, 1019, 28, 60, 92, 124, - 156, 188, 220, 252, 284, 316, 348, 380, 412, 444, 476, 508, 540, 572, 604, 636, 668, 700, 732, 764, - 796, 828, 860, 892, 924, 956, 988, 1020, 29, 61, 93, 125, 157, 189, 221, 253, 285, 317, 349, 381, - 413, 445, 477, 509, 541, 573, 605, 637, 669, 701, 733, 765, 797, 829, 861, 893, 925, 957, 989, 1021, - 30, 62, 94, 126, 158, 190, 222, 254, 286, 318, 350, 382, 414, 446, 478, 510, 542, 574, 606, 638, - 670, 702, 734, 766, 798, 830, 862, 894, 926, 958, 990, 1022, 31, 63, 95, 127, 159, 191, 223, 255, - 287, 319, 351, 383, 415, 447, 479, 511, 543, 575, 607, 639, 671, 703, 735, 767, 799, 831, 863, 895, - 927, 959, 991, 1023,]; - private static readonly short[] MatrixColumnScan4x8 = [ 0, 4, 8, 12, 16, 20, 24, 28, 1, 5, 9, 13, 17, 21, 25, 29, 2, 6, 10, 14, 18, 22, 26, 30, 3, 7, 11, 15, 19, 23, 27, 31,]; @@ -289,55 +235,6 @@ internal static class Av1ScanOrderConstants 11, 27, 43, 59, 75, 91, 107, 123, 12, 28, 44, 60, 76, 92, 108, 124, 13, 29, 45, 61, 77, 93, 109, 125, 14, 30, 46, 62, 78, 94, 110, 126, 15, 31, 47, 63, 79, 95, 111, 127,]; - private static readonly short[] MatrixColumnScan16x32 = [ - 0, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 272, 288, 304, 320, 336, 352, - 368, 384, 400, 416, 432, 448, 464, 480, 496, 1, 17, 33, 49, 65, 81, 97, 113, 129, 145, 161, 177, 193, 209, - 225, 241, 257, 273, 289, 305, 321, 337, 353, 369, 385, 401, 417, 433, 449, 465, 481, 497, 2, 18, 34, 50, 66, - 82, 98, 114, 130, 146, 162, 178, 194, 210, 226, 242, 258, 274, 290, 306, 322, 338, 354, 370, 386, 402, 418, 434, - 450, 466, 482, 498, 3, 19, 35, 51, 67, 83, 99, 115, 131, 147, 163, 179, 195, 211, 227, 243, 259, 275, 291, - 307, 323, 339, 355, 371, 387, 403, 419, 435, 451, 467, 483, 499, 4, 20, 36, 52, 68, 84, 100, 116, 132, 148, - 164, 180, 196, 212, 228, 244, 260, 276, 292, 308, 324, 340, 356, 372, 388, 404, 420, 436, 452, 468, 484, 500, 5, - 21, 37, 53, 69, 85, 101, 117, 133, 149, 165, 181, 197, 213, 229, 245, 261, 277, 293, 309, 325, 341, 357, 373, - 389, 405, 421, 437, 453, 469, 485, 501, 6, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214, 230, - 246, 262, 278, 294, 310, 326, 342, 358, 374, 390, 406, 422, 438, 454, 470, 486, 502, 7, 23, 39, 55, 71, 87, - 103, 119, 135, 151, 167, 183, 199, 215, 231, 247, 263, 279, 295, 311, 327, 343, 359, 375, 391, 407, 423, 439, 455, - 471, 487, 503, 8, 24, 40, 56, 72, 88, 104, 120, 136, 152, 168, 184, 200, 216, 232, 248, 264, 280, 296, 312, - 328, 344, 360, 376, 392, 408, 424, 440, 456, 472, 488, 504, 9, 25, 41, 57, 73, 89, 105, 121, 137, 153, 169, - 185, 201, 217, 233, 249, 265, 281, 297, 313, 329, 345, 361, 377, 393, 409, 425, 441, 457, 473, 489, 505, 10, 26, - 42, 58, 74, 90, 106, 122, 138, 154, 170, 186, 202, 218, 234, 250, 266, 282, 298, 314, 330, 346, 362, 378, 394, - 410, 426, 442, 458, 474, 490, 506, 11, 27, 43, 59, 75, 91, 107, 123, 139, 155, 171, 187, 203, 219, 235, 251, - 267, 283, 299, 315, 331, 347, 363, 379, 395, 411, 427, 443, 459, 475, 491, 507, 12, 28, 44, 60, 76, 92, 108, - 124, 140, 156, 172, 188, 204, 220, 236, 252, 268, 284, 300, 316, 332, 348, 364, 380, 396, 412, 428, 444, 460, 476, - 492, 508, 13, 29, 45, 61, 77, 93, 109, 125, 141, 157, 173, 189, 205, 221, 237, 253, 269, 285, 301, 317, 333, - 349, 365, 381, 397, 413, 429, 445, 461, 477, 493, 509, 14, 30, 46, 62, 78, 94, 110, 126, 142, 158, 174, 190, - 206, 222, 238, 254, 270, 286, 302, 318, 334, 350, 366, 382, 398, 414, 430, 446, 462, 478, 494, 510, 15, 31, 47, - 63, 79, 95, 111, 127, 143, 159,]; - - private static readonly short[] MatrixColumnScan32x16 = [ - 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 480, 1, 33, 65, 97, 129, 161, 193, - 225, 257, 289, 321, 353, 385, 417, 449, 481, 2, 34, 66, 98, 130, 162, 194, 226, 258, 290, 322, 354, 386, 418, - 450, 482, 3, 35, 67, 99, 131, 163, 195, 227, 259, 291, 323, 355, 387, 419, 451, 483, 4, 36, 68, 100, 132, - 164, 196, 228, 260, 292, 324, 356, 388, 420, 452, 484, 5, 37, 69, 101, 133, 165, 197, 229, 261, 293, 325, 357, - 389, 421, 453, 485, 6, 38, 70, 102, 134, 166, 198, 230, 262, 294, 326, 358, 390, 422, 454, 486, 7, 39, 71, - 103, 135, 167, 199, 231, 263, 295, 327, 359, 391, 423, 455, 487, 8, 40, 72, 104, 136, 168, 200, 232, 264, 296, - 328, 360, 392, 424, 456, 488, 9, 41, 73, 105, 137, 169, 201, 233, 265, 297, 329, 361, 393, 425, 457, 489, 10, - 42, 74, 106, 138, 170, 202, 234, 266, 298, 330, 362, 394, 426, 458, 490, 11, 43, 75, 107, 139, 171, 203, 235, - 267, 299, 331, 363, 395, 427, 459, 491, 12, 44, 76, 108, 140, 172, 204, 236, 268, 300, 332, 364, 396, 428, 460, - 492, 13, 45, 77, 109, 141, 173, 205, 237, 269, 301, 333, 365, 397, 429, 461, 493, 14, 46, 78, 110, 142, 174, - 206, 238, 270, 302, 334, 366, 398, 430, 462, 494, 15, 47, 79, 111, 143, 175, 207, 239, 271, 303, 335, 367, 399, - 431, 463, 495, 16, 48, 80, 112, 144, 176, 208, 240, 272, 304, 336, 368, 400, 432, 464, 496, 17, 49, 81, 113, - 145, 177, 209, 241, 273, 305, 337, 369, 401, 433, 465, 497, 18, 50, 82, 114, 146, 178, 210, 242, 274, 306, 338, - 370, 402, 434, 466, 498, 19, 51, 83, 115, 147, 179, 211, 243, 275, 307, 339, 371, 403, 435, 467, 499, 20, 52, - 84, 116, 148, 180, 212, 244, 276, 308, 340, 372, 404, 436, 468, 500, 21, 53, 85, 117, 149, 181, 213, 245, 277, - 309, 341, 373, 405, 437, 469, 501, 22, 54, 86, 118, 150, 182, 214, 246, 278, 310, 342, 374, 406, 438, 470, 502, - 23, 55, 87, 119, 151, 183, 215, 247, 279, 311, 343, 375, 407, 439, 471, 503, 24, 56, 88, 120, 152, 184, 216, - 248, 280, 312, 344, 376, 408, 440, 472, 504, 25, 57, 89, 121, 153, 185, 217, 249, 281, 313, 345, 377, 409, 441, - 473, 505, 26, 58, 90, 122, 154, 186, 218, 250, 282, 314, 346, 378, 410, 442, 474, 506, 27, 59, 91, 123, 155, - 187, 219, 251, 283, 315, 347, 379, 411, 443, 475, 507, 28, 60, 92, 124, 156, 188, 220, 252, 284, 316, 348, 380, - 412, 444, 476, 508, 29, 61, 93, 125, 157, 189, 221, 253, 285, 317, 349, 381, 413, 445, 477, 509, 30, 62, 94, - 126, 158, 190, 222, 254, 286, 318, 350, 382, 414, 446, 478, 510, 31, 63, 95, 127, 159, 191, 223, 255, 287, 319, - 351, 383, 415, 447, 479, 511,]; - private static readonly short[] MatrixColumnScan4x16 = [ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53, 57, 61, 2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, @@ -395,62 +292,6 @@ internal static class Av1ScanOrderConstants 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; - private static readonly short[] MatrixRowScan32x32 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, - 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, - 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, - 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, - 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, - 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, - 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, - 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, - 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, - 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, - 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, - 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, - 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, - 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, - 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, - 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, - 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, - 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, - 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, - 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, - 475, 476, 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, - 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, - 513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, - 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, - 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, - 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, - 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, - 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, - 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, - 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, - 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, - 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699, 700, 701, 702, - 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, - 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733, 734, 735, 736, 737, 738, 739, 740, - 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, - 760, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, - 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, - 798, 799, 800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, - 817, 818, 819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, 834, 835, - 836, 837, 838, 839, 840, 841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, - 855, 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, 867, 868, 869, 870, 871, 872, 873, - 874, 875, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, - 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, - 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, - 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, - 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, - 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, - 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, - 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1020, 1021, 1022, 1023,]; - private static readonly short[] MatrixRowScan4x8 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,]; @@ -475,56 +316,6 @@ internal static class Av1ScanOrderConstants 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,]; - private static readonly short[] MatrixRowScan16x32 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, - 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, - 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, - 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, - 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, - 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, - 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, - 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, - 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, - 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, - 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, - 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, - 506, 507, 508, 509, 510, 511,]; - - private static readonly short[] MatrixRowScan32x16 = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, - 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, - 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, - 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, - 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, - 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, - 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, - 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, - 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, - 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, - 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, - 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, - 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, - 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, - 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, - 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, - 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, - 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, - 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, 480, 481, 482, - 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, - 506, 507, 508, 509, 510, 511,]; - private static readonly short[] MatrixRowScan4x16 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, @@ -563,54 +354,54 @@ internal static class Av1ScanOrderConstants 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,]; - // IScan is not used (yet) for AVIF coding, leave these arrays empty for now. - private static readonly short[] DefaultIScan4x4 = []; - private static readonly short[] DefaultIScan8x8 = []; - private static readonly short[] DefaultIScan16x16 = []; - private static readonly short[] DefaultIScan32x32 = []; - private static readonly short[] DefaultIScan64x64 = []; - private static readonly short[] DefaultIScan4x8 = []; - private static readonly short[] DefaultIScan8x4 = []; - private static readonly short[] DefaultIScan8x16 = []; - private static readonly short[] DefaultIScan16x8 = []; - private static readonly short[] DefaultIScan16x32 = []; - private static readonly short[] DefaultIScan32x16 = []; - private static readonly short[] DefaultIScan4x16 = []; - private static readonly short[] DefaultIScan16x4 = []; - private static readonly short[] DefaultIScan8x32 = []; - private static readonly short[] DefaultIScan32x8 = []; - - private static readonly short[] MatrixColumnIScan4x4 = []; - private static readonly short[] MatrixColumnIScan8x8 = []; - private static readonly short[] MatrixColumnIScan16x16 = []; - private static readonly short[] MatrixColumnIScan32x32 = []; - private static readonly short[] MatrixColumnIScan64x64 = []; - private static readonly short[] MatrixColumnIScan4x8 = []; - private static readonly short[] MatrixColumnIScan8x4 = []; - private static readonly short[] MatrixColumnIScan8x16 = []; - private static readonly short[] MatrixColumnIScan16x8 = []; - private static readonly short[] MatrixColumnIScan16x32 = []; - private static readonly short[] MatrixColumnIScan32x16 = []; - private static readonly short[] MatrixColumnIScan4x16 = []; - private static readonly short[] MatrixColumnIScan16x4 = []; - private static readonly short[] MatrixColumnIScan8x32 = []; - private static readonly short[] MatrixColumnIScan32x8 = []; - - private static readonly short[] MatrixRowIScan4x4 = []; - private static readonly short[] MatrixRowIScan8x8 = []; - private static readonly short[] MatrixRowIScan16x16 = []; - private static readonly short[] MatrixRowIScan32x32 = []; - private static readonly short[] MatrixRowIScan64x64 = []; - private static readonly short[] MatrixRowIScan4x8 = []; - private static readonly short[] MatrixRowIScan8x4 = []; - private static readonly short[] MatrixRowIScan8x16 = []; - private static readonly short[] MatrixRowIScan16x8 = []; - private static readonly short[] MatrixRowIScan16x32 = []; - private static readonly short[] MatrixRowIScan32x16 = []; - private static readonly short[] MatrixRowIScan4x16 = []; - private static readonly short[] MatrixRowIScan16x4 = []; - private static readonly short[] MatrixRowIScan8x32 = []; - private static readonly short[] MatrixRowIScan32x8 = []; + // InverseScan is not used (yet) for AVIF coding, leave these arrays empty for now. + private static readonly short[] DefaultInverseScan4x4 = []; + private static readonly short[] DefaultInverseScan8x8 = []; + private static readonly short[] DefaultInverseScan16x16 = []; + private static readonly short[] DefaultInverseScan32x32 = []; + private static readonly short[] DefaultInverseScan64x64 = []; + private static readonly short[] DefaultInverseScan4x8 = []; + private static readonly short[] DefaultInverseScan8x4 = []; + private static readonly short[] DefaultInverseScan8x16 = []; + private static readonly short[] DefaultInverseScan16x8 = []; + private static readonly short[] DefaultInverseScan16x32 = []; + private static readonly short[] DefaultInverseScan32x16 = []; + private static readonly short[] DefaultInverseScan4x16 = []; + private static readonly short[] DefaultInverseScan16x4 = []; + private static readonly short[] DefaultInverseScan8x32 = []; + private static readonly short[] DefaultInverseScan32x8 = []; + + private static readonly short[] MatrixColumnInverseScan4x4 = []; + private static readonly short[] MatrixColumnInverseScan8x8 = []; + private static readonly short[] MatrixColumnInverseScan16x16 = []; + private static readonly short[] MatrixColumnInverseScan32x32 = []; + private static readonly short[] MatrixColumnInverseScan64x64 = []; + private static readonly short[] MatrixColumnInverseScan4x8 = []; + private static readonly short[] MatrixColumnInverseScan8x4 = []; + private static readonly short[] MatrixColumnInverseScan8x16 = []; + private static readonly short[] MatrixColumnInverseScan16x8 = []; + private static readonly short[] MatrixColumnInverseScan16x32 = []; + private static readonly short[] MatrixColumnInverseScan32x16 = []; + private static readonly short[] MatrixColumnInverseScan4x16 = []; + private static readonly short[] MatrixColumnInverseScan16x4 = []; + private static readonly short[] MatrixColumnInverseScan8x32 = []; + private static readonly short[] MatrixColumnInverseScan32x8 = []; + + private static readonly short[] MatrixRowInverseScan4x4 = []; + private static readonly short[] MatrixRowInverseScan8x8 = []; + private static readonly short[] MatrixRowInverseScan16x16 = []; + private static readonly short[] MatrixRowInverseScan32x32 = []; + private static readonly short[] MatrixRowInverseScan64x64 = []; + private static readonly short[] MatrixRowInverseScan4x8 = []; + private static readonly short[] MatrixRowInverseScan8x4 = []; + private static readonly short[] MatrixRowInverseScan8x16 = []; + private static readonly short[] MatrixRowInverseScan16x8 = []; + private static readonly short[] MatrixRowInverseScan16x32 = []; + private static readonly short[] MatrixRowInverseScan32x16 = []; + private static readonly short[] MatrixRowInverseScan4x16 = []; + private static readonly short[] MatrixRowInverseScan16x4 = []; + private static readonly short[] MatrixRowInverseScan8x32 = []; + private static readonly short[] MatrixRowInverseScan32x8 = []; // Neighborss are not used (yet) for AVIF coding, leave these arrays empty for now. private static readonly short[] DefaultScan4x4Neighbors = []; @@ -666,392 +457,392 @@ internal static class Av1ScanOrderConstants // Transform size 4x4 [ - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(DefaultScan4x4, DefaultIScan4x4, DefaultScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), - new(MatrixRowScan4x4, MatrixRowIScan4x4, MatrixRowScan4x4Neighbors), - new(MatrixColumnScan4x4, MatrixColumnIScan4x4, MatrixColumnScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(DefaultScan4x4, DefaultInverseScan4x4, DefaultScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors), + new(MatrixRowScan4x4, MatrixRowInverseScan4x4, MatrixRowScan4x4Neighbors), + new(MatrixColumnScan4x4, MatrixColumnInverseScan4x4, MatrixColumnScan4x4Neighbors), ], // Transform size 8x8 [ - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(DefaultScan8x8, DefaultIScan8x8, DefaultScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), - new(MatrixRowScan8x8, MatrixRowIScan8x8, MatrixRowScan8x8Neighbors), - new(MatrixColumnScan8x8, MatrixColumnIScan8x8, MatrixColumnScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(DefaultScan8x8, DefaultInverseScan8x8, DefaultScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors), + new(MatrixRowScan8x8, MatrixRowInverseScan8x8, MatrixRowScan8x8Neighbors), + new(MatrixColumnScan8x8, MatrixColumnInverseScan8x8, MatrixColumnScan8x8Neighbors), ], // Transform size 16x16 [ - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(DefaultScan16x16, DefaultIScan16x16, DefaultScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), - new(MatrixRowScan16x16, MatrixRowIScan16x16, MatrixRowScan16x16Neighbors), - new(MatrixColumnScan16x16, MatrixColumnIScan16x16, MatrixColumnScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(DefaultScan16x16, DefaultInverseScan16x16, DefaultScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors), + new(MatrixRowScan16x16, MatrixRowInverseScan16x16, MatrixRowScan16x16Neighbors), + new(MatrixColumnScan16x16, MatrixColumnInverseScan16x16, MatrixColumnScan16x16Neighbors), ], // Transform size 32x32 [ - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), ], [ // Transform size 64X64 // Half of the coefficients of tx64 at higher frequencies are set to // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), ], [ // Transform size 4X8 - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(DefaultScan4x8, DefaultIScan4x8, DefaultScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), - new(MatrixRowScan4x8, MatrixRowIScan4x8, MatrixRowScan4x8Neighbors), - new(MatrixColumnScan4x8, MatrixColumnIScan4x8, MatrixColumnScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(DefaultScan4x8, DefaultInverseScan4x8, DefaultScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors), + new(MatrixRowScan4x8, MatrixRowInverseScan4x8, MatrixRowScan4x8Neighbors), + new(MatrixColumnScan4x8, MatrixColumnInverseScan4x8, MatrixColumnScan4x8Neighbors), ], [ // Transform size 8X4 - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(DefaultScan8x4, DefaultIScan8x4, DefaultScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), - new(MatrixRowScan8x4, MatrixRowIScan8x4, MatrixRowScan8x4Neighbors), - new(MatrixColumnScan8x4, MatrixColumnIScan8x4, MatrixColumnScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(DefaultScan8x4, DefaultInverseScan8x4, DefaultScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors), + new(MatrixRowScan8x4, MatrixRowInverseScan8x4, MatrixRowScan8x4Neighbors), + new(MatrixColumnScan8x4, MatrixColumnInverseScan8x4, MatrixColumnScan8x4Neighbors), ], [ // Transform size 8X16 - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(DefaultScan8x16, DefaultIScan8x16, DefaultScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), - new(MatrixRowScan8x16, MatrixRowIScan8x16, MatrixRowScan8x16Neighbors), - new(MatrixColumnScan8x16, MatrixColumnIScan8x16, MatrixColumnScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(DefaultScan8x16, DefaultInverseScan8x16, DefaultScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors), + new(MatrixRowScan8x16, MatrixRowInverseScan8x16, MatrixRowScan8x16Neighbors), + new(MatrixColumnScan8x16, MatrixColumnInverseScan8x16, MatrixColumnScan8x16Neighbors), ], [ // Transform size 16X8 - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(DefaultScan16x8, DefaultIScan16x8, DefaultScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), - new(MatrixRowScan16x8, MatrixRowIScan16x8, MatrixRowScan16x8Neighbors), - new(MatrixColumnScan16x8, MatrixColumnIScan16x8, MatrixColumnScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(DefaultScan16x8, DefaultInverseScan16x8, DefaultScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors), + new(MatrixRowScan16x8, MatrixRowInverseScan16x8, MatrixRowScan16x8Neighbors), + new(MatrixColumnScan16x8, MatrixColumnInverseScan16x8, MatrixColumnScan16x8Neighbors), ], [ // Transform size 16X32 - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), ], [ // Transform size 32X16 - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), ], [ // Transform size 32X64 // Half of the coefficients of tx64 at higher frequencies are set to // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), ], [ // Transform size 64X32 // Half of the coefficients of tx64 at higher frequencies are set to // zeros. So tx32's scan order is used. - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(DefaultScan32x32, DefaultIScan32x32, DefaultScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), - new(MatrixRowScan32x32, MatrixRowIScan32x32, MatrixRowScan32x32Neighbors), - new(MatrixColumnScan32x32, MatrixColumnIScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, DefaultInverseScan32x32, DefaultScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), + new(DefaultScan32x32, MatrixRowInverseScan32x32, MatrixRowScan32x32Neighbors), + new(DefaultScan32x32, MatrixColumnInverseScan32x32, MatrixColumnScan32x32Neighbors), ], [ // Transform size 4X16 - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(DefaultScan4x16, DefaultIScan4x16, DefaultScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), - new(MatrixRowScan4x16, MatrixRowIScan4x16, MatrixRowScan4x16Neighbors), - new(MatrixColumnScan4x16, MatrixColumnIScan4x16, MatrixColumnScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(DefaultScan4x16, DefaultInverseScan4x16, DefaultScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors), + new(MatrixRowScan4x16, MatrixRowInverseScan4x16, MatrixRowScan4x16Neighbors), + new(MatrixColumnScan4x16, MatrixColumnInverseScan4x16, MatrixColumnScan4x16Neighbors), ], [ // Transform size 16X4 - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(DefaultScan16x4, DefaultIScan16x4, DefaultScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), - new(MatrixRowScan16x4, MatrixRowIScan16x4, MatrixRowScan16x4Neighbors), - new(MatrixColumnScan16x4, MatrixColumnIScan16x4, MatrixColumnScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(DefaultScan16x4, DefaultInverseScan16x4, DefaultScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors), + new(MatrixRowScan16x4, MatrixRowInverseScan16x4, MatrixRowScan16x4Neighbors), + new(MatrixColumnScan16x4, MatrixColumnInverseScan16x4, MatrixColumnScan16x4Neighbors), ], [ // Transform size 8X32 - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(DefaultScan8x32, DefaultIScan8x32, DefaultScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), - new(MatrixRowScan8x32, MatrixRowIScan8x32, MatrixRowScan8x32Neighbors), - new(MatrixColumnScan8x32, MatrixColumnIScan8x32, MatrixColumnScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(DefaultScan8x32, DefaultInverseScan8x32, DefaultScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors), + new(MatrixRowScan8x32, MatrixRowInverseScan8x32, MatrixRowScan8x32Neighbors), + new(MatrixColumnScan8x32, MatrixColumnInverseScan8x32, MatrixColumnScan8x32Neighbors), ], [ // Transform size 32X8 - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(DefaultScan32x8, DefaultIScan32x8, DefaultScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), - new(MatrixRowScan32x8, MatrixRowIScan32x8, MatrixRowScan32x8Neighbors), - new(MatrixColumnScan32x8, MatrixColumnIScan32x8, MatrixColumnScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(DefaultScan32x8, DefaultInverseScan32x8, DefaultScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors), + new(MatrixRowScan32x8, MatrixRowInverseScan32x8, MatrixRowScan32x8Neighbors), + new(MatrixColumnScan32x8, MatrixColumnInverseScan32x8, MatrixColumnScan32x8Neighbors), ], [ // Transform size 16X64 // Half of the coefficients of tx64 at higher frequencies are set to // zeros. So tx32's scan order is used. - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(DefaultScan16x32, DefaultIScan16x32, DefaultScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), - new(MatrixRowScan16x32, MatrixRowIScan16x32, MatrixRowScan16x32Neighbors), - new(MatrixColumnScan16x32, MatrixColumnIScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, DefaultInverseScan16x32, DefaultScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), + new(DefaultScan16x32, MatrixRowInverseScan16x32, MatrixRowScan16x32Neighbors), + new(DefaultScan16x32, MatrixColumnInverseScan16x32, MatrixColumnScan16x32Neighbors), ], [ // Transform size 64X16 // Half of the coefficients of tx64 at higher frequencies are set to // zeros. So tx32's scan order is used. - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(DefaultScan32x16, DefaultIScan32x16, DefaultScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), - new(MatrixRowScan32x16, MatrixRowIScan32x16, MatrixRowScan32x16Neighbors), - new(MatrixColumnScan32x16, MatrixColumnIScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, DefaultInverseScan32x16, DefaultScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), + new(DefaultScan32x16, MatrixRowInverseScan32x16, MatrixRowScan32x16Neighbors), + new(DefaultScan32x16, MatrixColumnInverseScan32x16, MatrixColumnScan32x16Neighbors), ] ]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 53c27e8741..37677a3f4b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -141,13 +141,9 @@ public static int GetScale(this Av1TransformSize size) return (pels > 1024) ? 2 : (pels > 256) ? 1 : 0; } - public static int GetWidth(this Av1TransformSize size) => (int)size; + public static int GetWidth(this Av1TransformSize size) => WideUnit[(int)size] << 2; - public static int GetHeight(this Av1TransformSize size) => (int)size; - - public static int GetWidthLog2(this Av1TransformSize size) => (int)size; - - public static int GetHeightLog2(this Av1TransformSize size) => (int)size; + public static int GetHeight(this Av1TransformSize size) => HighUnit[(int)size] << 2; public static int Get4x4WideCount(this Av1TransformSize size) => WideUnit[(int)size]; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs index 8bceeb136b..9844e2833c 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ScanOrderTests.cs @@ -28,6 +28,43 @@ internal void AllIndicesScannedExactlyOnce(int s, int t) } } + [Theory] + [MemberData(nameof(GetCombinations))] + internal void AllIndicesScannedAreWithinRange(int s, int t) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + Av1TransformType transformType = (Av1TransformType)t; + int lowValue = 0; + + // Act + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); + int highValue = scanOrder.Scan.Length - 1; + + // Assert + foreach (short scan in scanOrder.Scan) + { + Assert.InRange(scan, lowValue, highValue); + } + } + + [Theory] + [MemberData(nameof(GetCombinations))] + internal void CorrectNumberOfIndicesScanned(int s, int t) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + Av1TransformType transformType = (Av1TransformType)t; + int width = Math.Min(transformSize.GetWidth(), 32); + int height = Math.Min(transformSize.GetHeight(), 32); + + // Act + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); + + // Assert + Assert.Equal(width * height, scanOrder.Scan.Length); + } + public static TheoryData GetCombinations() { TheoryData combinations = []; From 73718f11b4b52cbcd0f4cbd4120177e676bdb02e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 25 Jul 2024 21:45:14 +0200 Subject: [PATCH 139/190] Unit tests for block and transform sizes --- .../Formats/Heif/Av1/Av1BlockSize.cs | 2 +- .../Heif/Av1/Av1BlockSizeExtensions.cs | 3 +- .../Transform/Av1TransformTypeExtensions.cs | 2 - .../Formats/Heif/Av1/Av1BlockSizeTests.cs | 119 ++++++++++++++ .../Formats/Heif/Av1/Av1TransformSizeTests.cs | 152 ++++++++++++++++++ 5 files changed, 273 insertions(+), 5 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformSizeTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs index c38f620c41..62c4ff59e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSize.cs @@ -72,7 +72,7 @@ internal enum Av1BlockSize : byte /// A block of samples, 64 samples wide and 16 samples high. Block64x16 = 21, - SizesAll = 22, + AllSizes = 22, SizeS = Block4x16, Invalid = 255, Largest = SizeS - 1, diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs index 3c5ce9e596..6029d4fddc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BlockSizeExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -104,7 +103,7 @@ public static Av1TransformSize GetMaxUvTransformSize(this Av1BlockSize blockSize { Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); Av1TransformSize uvTransformSize = Av1TransformSize.Invalid; - if (planeBlockSize < Av1BlockSize.SizesAll) + if (planeBlockSize < Av1BlockSize.AllSizes) { uvTransformSize = planeBlockSize.GetMaximumTransformSize(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs index 48c14ba2aa..c03b3b4406 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformTypeExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1TransformTypeExtensions diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs new file mode 100644 index 0000000000..79b32e92a8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs @@ -0,0 +1,119 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1BlockSizeTests +{ + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetWidthReturnsCorrectWidth(int s) + { + // Assign + Av1BlockSize blockSize = (Av1BlockSize)s; + int expectedWidth = blockSize switch + { + Av1BlockSize.Block4x4 or Av1BlockSize.Block4x8 or Av1BlockSize.Block4x16 => 4, + Av1BlockSize.Block8x4 or Av1BlockSize.Block8x8 or Av1BlockSize.Block8x16 or Av1BlockSize.Block8x32 => 8, + Av1BlockSize.Block16x4 or Av1BlockSize.Block16x8 or Av1BlockSize.Block16x16 or Av1BlockSize.Block16x32 or Av1BlockSize.Block16x64 => 16, + Av1BlockSize.Block32x8 or Av1BlockSize.Block32x16 or Av1BlockSize.Block32x32 or Av1BlockSize.Block32x64 => 32, + Av1BlockSize.Block64x16 or Av1BlockSize.Block64x32 or Av1BlockSize.Block64x64 or Av1BlockSize.Block64x128 => 64, + Av1BlockSize.Block128x64 or Av1BlockSize.Block128x128 => 128, + _ => -1 + }; + + // Act + int actualWidth = blockSize.GetWidth(); + + // Assert + Assert.Equal(expectedWidth, actualWidth); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetHeightReturnsCorrectHeight(int s) + { + // Assign + Av1BlockSize blockSize = (Av1BlockSize)s; + int expectedHeight = blockSize switch + { + Av1BlockSize.Block4x4 or Av1BlockSize.Block8x4 or Av1BlockSize.Block16x4 => 4, + Av1BlockSize.Block4x8 or Av1BlockSize.Block8x8 or Av1BlockSize.Block16x8 or Av1BlockSize.Block32x8 => 8, + Av1BlockSize.Block4x16 or Av1BlockSize.Block8x16 or Av1BlockSize.Block16x16 or Av1BlockSize.Block32x16 or Av1BlockSize.Block64x16 => 16, + Av1BlockSize.Block8x32 or Av1BlockSize.Block16x32 or Av1BlockSize.Block32x32 or Av1BlockSize.Block64x32 => 32, + Av1BlockSize.Block16x64 or Av1BlockSize.Block32x64 or Av1BlockSize.Block64x64 or Av1BlockSize.Block128x64 => 64, + Av1BlockSize.Block64x128 or Av1BlockSize.Block128x128 => 128, + _ => -1 + }; + + // Act + int actualHeight = blockSize.GetHeight(); + + // Assert + Assert.Equal(expectedHeight, actualHeight); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetSubSampledReturnsCorrectSize(int s) + { + if (s is 0 or 1 or 2 or 16 or 17) + { + // Exceptional values, skip for this generic test. + return; + } + + // Assign + Av1BlockSize blockSize = (Av1BlockSize)s; + int originalWidth = blockSize.GetWidth(); + int originalHeight = blockSize.GetHeight(); + + // Act + Av1BlockSize actualNoNo = blockSize.GetSubsampled(false, false); + Av1BlockSize actualYesNo = blockSize.GetSubsampled(true, false); + Av1BlockSize actualNoYes = blockSize.GetSubsampled(false, true); + Av1BlockSize actualYesYes = blockSize.GetSubsampled(true, true); + + // Assert + Assert.Equal(originalWidth, actualNoNo.GetWidth()); + Assert.Equal(originalHeight, actualNoNo.GetHeight()); + + if (actualYesNo != Av1BlockSize.Invalid) + { + Assert.Equal(originalWidth, actualYesNo.GetWidth() * 2); + Assert.Equal(originalHeight, actualYesNo.GetHeight()); + } + + if (actualNoYes != Av1BlockSize.Invalid) + { + Assert.Equal(originalWidth, actualNoYes.GetWidth()); + Assert.Equal(originalHeight, actualNoYes.GetHeight() * 2); + } + + Assert.Equal(originalWidth, actualYesYes.GetWidth() * 2); + Assert.Equal(originalHeight, actualYesYes.GetHeight() * 2); + } + + public static TheoryData GetAllSizes() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1BlockSize.AllSizes; s++) + { + combinations.Add(s); + } + + return combinations; + } + + private static int GetRatio(Av1BlockSize blockSize) + { + int width = blockSize.GetWidth(); + int height = blockSize.GetHeight(); + int ratio = width > height ? width / height : height / width; + return ratio; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformSizeTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformSizeTests.cs new file mode 100644 index 0000000000..d0740edc3a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformSizeTests.cs @@ -0,0 +1,152 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1TransformSizeTests +{ + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetWidthReturnsCorrectWidth(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int expectedWidth = transformSize switch + { + Av1TransformSize.Size4x4 or Av1TransformSize.Size4x8 or Av1TransformSize.Size4x16 => 4, + Av1TransformSize.Size8x4 or Av1TransformSize.Size8x8 or Av1TransformSize.Size8x16 or Av1TransformSize.Size8x32 => 8, + Av1TransformSize.Size16x4 or Av1TransformSize.Size16x8 or Av1TransformSize.Size16x16 or Av1TransformSize.Size16x32 or Av1TransformSize.Size16x64 => 16, + Av1TransformSize.Size32x8 or Av1TransformSize.Size32x16 or Av1TransformSize.Size32x32 or Av1TransformSize.Size32x64 => 32, + Av1TransformSize.Size64x16 or Av1TransformSize.Size64x32 or Av1TransformSize.Size64x64 => 64, + _ => -1 + }; + + // Act + int actualWidth = transformSize.GetWidth(); + + // Assert + Assert.Equal(expectedWidth, actualWidth); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetHeightReturnsCorrectHeight(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int expectedHeight = transformSize switch + { + Av1TransformSize.Size4x4 or Av1TransformSize.Size8x4 or Av1TransformSize.Size16x4 => 4, + Av1TransformSize.Size4x8 or Av1TransformSize.Size8x8 or Av1TransformSize.Size16x8 or Av1TransformSize.Size32x8 => 8, + Av1TransformSize.Size4x16 or Av1TransformSize.Size8x16 or Av1TransformSize.Size16x16 or Av1TransformSize.Size32x16 or Av1TransformSize.Size64x16 => 16, + Av1TransformSize.Size8x32 or Av1TransformSize.Size16x32 or Av1TransformSize.Size32x32 or Av1TransformSize.Size64x32 => 32, + Av1TransformSize.Size16x64 or Av1TransformSize.Size32x64 or Av1TransformSize.Size64x64 => 64, + _ => -1 + }; + + // Act + int actualHeight = transformSize.GetHeight(); + + // Assert + Assert.Equal(expectedHeight, actualHeight); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetSubSizeReturnsCorrectRatio(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int ratio = GetRatio(transformSize); + int expectedRatio = (ratio == 4) ? 2 : 1; + + // Act + Av1TransformSize actual = transformSize.GetSubSize(); + int actualRatio = GetRatio(actual); + + // Assert + Assert.Equal(expectedRatio, actualRatio); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetSquareSizeReturnsCorrectRatio(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int ratio = GetRatio(transformSize); + int expectedRatio = 1; + int expectedSize = Math.Min(transformSize.GetWidth(), transformSize.GetHeight()); + + // Act + Av1TransformSize actual = transformSize.GetSquareSize(); + int actualRatio = GetRatio(actual); + + // Assert + Assert.Equal(expectedRatio, actualRatio); + Assert.Equal(expectedSize, actual.GetWidth()); + Assert.Equal(expectedSize, actual.GetHeight()); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void GetSquareUpSizeReturnsCorrectRatio(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int ratio = GetRatio(transformSize); + int expectedRatio = 1; + int expectedSize = Math.Max(transformSize.GetWidth(), transformSize.GetHeight()); + + // Act + Av1TransformSize actual = transformSize.GetSquareUpSize(); + int actualRatio = GetRatio(actual); + + // Assert + Assert.Equal(expectedRatio, actualRatio); + Assert.Equal(expectedSize, actual.GetWidth()); + Assert.Equal(expectedSize, actual.GetHeight()); + } + + [Theory] + [MemberData(nameof(GetAllSizes))] + internal void ToBlockSizeReturnsSameWidthAndHeight(int s) + { + // Assign + Av1TransformSize transformSize = (Av1TransformSize)s; + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + + // Act + Av1BlockSize blockSize = transformSize.ToBlockSize(); + int blockWidth = blockSize.GetWidth(); + int blockHeight = blockSize.GetHeight(); + + // Assert + Assert.Equal(transformWidth, blockWidth); + Assert.Equal(transformHeight, blockHeight); + } + + public static TheoryData GetAllSizes() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) + { + combinations.Add(s); + } + + return combinations; + } + + private static int GetRatio(Av1TransformSize transformSize) + { + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + int ratio = width > height ? width / height : height / width; + return ratio; + } +} From 08cbc5ad1163d46c2852ec822f394f71af05ff27 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 26 Jul 2024 16:56:21 +0200 Subject: [PATCH 140/190] Unit test for Partition Type --- .../Formats/Heif/Av1/Av1PartitionType.cs | 90 ++++++++++++++++++- .../Formats/Heif/Av1/Tiling/Av1TileDecoder.cs | 9 +- .../Formats/Heif/Av1/Av1BlockSizeTests.cs | 12 +-- .../Formats/Heif/Av1/Av1PartitionTypeTests.cs | 61 +++++++++++++ 4 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PartitionTypeTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs index 0a6092ce89..11f973a064 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1PartitionType.cs @@ -10,51 +10,139 @@ internal enum Av1PartitionType /// /// Not partitioned any further. /// + /// + /// + /// *** + /// * * + /// *** + /// + /// None = 0, /// /// Horizontally split in 2 partitions. /// + /// + /// + /// *** + /// * * + /// *** + /// * * + /// *** + /// + /// Horizontal = 1, /// /// Vertically split in 2 partitions. /// + /// + /// + /// ***** + /// * * * + /// ***** + /// + /// Vertical = 2, /// /// 4 equally sized partitions. /// + /// + /// + /// ***** + /// * * * + /// ***** + /// * * * + /// ***** + /// + /// Split = 3, /// /// Horizontal split and the top partition is split again. /// + /// + /// + /// ***** + /// * * * + /// ***** + /// * * + /// ***** + /// + /// HorizontalA = 4, /// /// Horizontal split and the bottom partition is split again. /// + /// + /// + /// ***** + /// * * + /// ***** + /// * * * + /// ***** + /// + /// HorizontalB = 5, /// /// Vertical split and the left partition is split again. /// + /// + /// + /// ***** + /// * * * + /// *** * + /// * * * + /// ***** + /// + /// VerticalA = 6, /// - /// Vertical split and the right partitino is split again. + /// Vertical split and the right partition is split again. /// + /// + /// + /// ***** + /// * * * + /// * *** + /// * * * + /// ***** + /// + /// VerticalB = 7, /// /// 4:1 horizontal partition. /// + /// + /// + /// *** + /// * * + /// *** + /// * * + /// *** + /// * * + /// *** + /// * * + /// *** + /// + /// Horizontal4 = 8, /// /// 4:1 vertical partition. /// + /// + /// + /// ********* + /// * * * * * + /// ********* + /// + /// Vertical4 = 9, /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs index 569f633b10..a66a8ee46b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs @@ -336,13 +336,14 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) { - bool subX = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX; - bool subY = i > 0 && this.SequenceHeader.ColorConfig.SubSamplingY; + int subX = (i > 0 && this.SequenceHeader.ColorConfig.SubSamplingX) ? 1 : 0; + int subY = (i > 0 && this.SequenceHeader.ColorConfig.SubSamplingY) ? 1 : 0; Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + DebugGuard.IsTrue(planeBlockSize != Av1BlockSize.Invalid, nameof(planeBlockSize)); int txsWide = planeBlockSize.GetWidth() >> 2; int txsHigh = planeBlockSize.GetHeight() >> 2; - int aboveOffset = (partitionInfo.ColumnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> (subX ? 1 : 0); - int leftOffset = (partitionInfo.RowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> (subY ? 1 : 0); + int aboveOffset = (partitionInfo.ColumnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> subX; + int leftOffset = (partitionInfo.RowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> subY; this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide); this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs index 79b32e92a8..7e3506aaa2 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BlockSizeTests.cs @@ -71,6 +71,8 @@ internal void GetSubSampledReturnsCorrectSize(int s) Av1BlockSize blockSize = (Av1BlockSize)s; int originalWidth = blockSize.GetWidth(); int originalHeight = blockSize.GetHeight(); + int halfWidth = originalWidth / 2; + int halfHeight = originalHeight / 2; // Act Av1BlockSize actualNoNo = blockSize.GetSubsampled(false, false); @@ -84,18 +86,18 @@ internal void GetSubSampledReturnsCorrectSize(int s) if (actualYesNo != Av1BlockSize.Invalid) { - Assert.Equal(originalWidth, actualYesNo.GetWidth() * 2); + Assert.Equal(halfWidth, actualYesNo.GetWidth()); Assert.Equal(originalHeight, actualYesNo.GetHeight()); } if (actualNoYes != Av1BlockSize.Invalid) { Assert.Equal(originalWidth, actualNoYes.GetWidth()); - Assert.Equal(originalHeight, actualNoYes.GetHeight() * 2); + Assert.Equal(halfHeight, actualNoYes.GetHeight()); } - Assert.Equal(originalWidth, actualYesYes.GetWidth() * 2); - Assert.Equal(originalHeight, actualYesYes.GetHeight() * 2); + Assert.Equal(halfWidth, actualYesYes.GetWidth()); + Assert.Equal(halfHeight, actualYesYes.GetHeight()); } public static TheoryData GetAllSizes() @@ -113,7 +115,7 @@ private static int GetRatio(Av1BlockSize blockSize) { int width = blockSize.GetWidth(); int height = blockSize.GetHeight(); - int ratio = width > height ? width / height : height / width; + int ratio = width >= height ? width / height : -height / width; return ratio; } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PartitionTypeTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PartitionTypeTests.cs new file mode 100644 index 0000000000..e7b073264e --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PartitionTypeTests.cs @@ -0,0 +1,61 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1PartitionTypeTests +{ + [Theory] + [MemberData(nameof(GetAllCombinations))] + internal void GetSubBlockSizeReturnsCorrectRatio(int t, int s) + { + // Assign + Av1PartitionType partitionType = (Av1PartitionType)t; + Av1BlockSize blockSize = (Av1BlockSize)s; + int expectedRatio = partitionType switch + { + Av1PartitionType.None or Av1PartitionType.Split => 1, + Av1PartitionType.HorizontalA or Av1PartitionType.HorizontalB or Av1PartitionType.Horizontal => 2, + Av1PartitionType.VerticalA or Av1PartitionType.VerticalB or Av1PartitionType.Vertical => -2, + Av1PartitionType.Horizontal4 => 4, + Av1PartitionType.Vertical4 => -4, + _ => -1 + }; + + // Act + Av1BlockSize subBlockSize = partitionType.GetBlockSubSize(blockSize); + + // Assert + if (subBlockSize != Av1BlockSize.Invalid) + { + int actualRatio = GetRatio(subBlockSize); + Assert.Equal(expectedRatio, actualRatio); + } + } + + public static TheoryData GetAllCombinations() + { + TheoryData combinations = []; + for (int t = 0; t <= (int)Av1PartitionType.Vertical4; t++) + { + for (int s = 0; s < (int)Av1BlockSize.AllSizes; s++) + { + combinations.Add(t, s); + } + } + + return combinations; + } + + private static int GetRatio(Av1BlockSize blockSize) + { + int width = blockSize.GetWidth(); + int height = blockSize.GetHeight(); + int ratio = width >= height ? width / height : -height / width; + return ratio; + } +} From 5d27f30a7e1896186a9b6ca1e8e516658adcb0c8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 26 Jul 2024 19:29:52 +0200 Subject: [PATCH 141/190] Add extremely small AVIF test image --- tests/Images/Input/Heif/Orange4x4.avif | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/Images/Input/Heif/Orange4x4.avif diff --git a/tests/Images/Input/Heif/Orange4x4.avif b/tests/Images/Input/Heif/Orange4x4.avif new file mode 100644 index 0000000000..82f255d118 --- /dev/null +++ b/tests/Images/Input/Heif/Orange4x4.avif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de6d438ed2808d8a622ce5288f683f033eb15426aea0debfe0278f054f8eb2e3 +size 299 From fa2e1b06108febab6542fa703ea05a292f1c1cf5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 26 Jul 2024 19:44:43 +0200 Subject: [PATCH 142/190] Add tests for Orange4x4 image --- ImageSharp.sln | 1 + .../Formats/Heif/Av1/Av1BitStreamReader.cs | 3 +-- .../Formats/Heif/Av1/Av1TilingTests.cs | 14 ++++++++------ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 3 +++ 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ImageSharp.sln b/ImageSharp.sln index e540101e58..1789a8d5d4 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -671,6 +671,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Heif", "Heif", "{BA5D603A-C tests\Images\Input\Heif\IMG-20230508-0053.hif = tests\Images\Input\Heif\IMG-20230508-0053.hif tests\Images\Input\Heif\Irvine_CA.avif = tests\Images\Input\Heif\Irvine_CA.avif tests\Images\Input\Heif\jpeg444_xnconvert.avif = tests\Images\Input\Heif\jpeg444_xnconvert.avif + tests\Images\Input\Heif\Orange4x4.avif = tests\Images\Input\Heif\Orange4x4.avif EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Icon", "Icon", "{95E45DDE-A67D-48AD-BBA8-5FAA151B860D}" diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs index 552f0a80b8..1b96c26d9e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamReader.cs @@ -150,8 +150,7 @@ public Span GetSymbolReader(int tileDataSize) { DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Symbol reading needs to start on byte boundary."); int bytesRead = Av1Math.DivideBy8Floor(this.BitPosition); - int spanLength = tileDataSize - bytesRead; - Span span = this.data.Slice(bytesRead, spanLength); + Span span = this.data.Slice(bytesRead, tileDataSize); this.Skip(tileDataSize << 3); return span; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index baf77ed488..7348e33f3f 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -10,24 +10,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class Av1TilingTests { - // [Theory] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 0x03CC - 18)] - public void ReadFirstTile(string filename, int headerOffset, int headerSize, int tileOffset, int tileSize) + [Theory] + /*[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18)]*/ + [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21)] + public void ReadFirstTile(string filename, int dataOffset, int dataSize, int tileOffset) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); - Span headerSpan = content.AsSpan(headerOffset, headerSize); - Span tileSpan = content.AsSpan(tileOffset, tileSize); + Span headerSpan = content.AsSpan(dataOffset, dataSize); + Span tileSpan = content.AsSpan(tileOffset, dataSize - tileOffset); Av1BitStreamReader reader = new(headerSpan); IAv1TileDecoder stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); - obuReader.ReadAll(ref reader, headerSize, stub); + obuReader.ReadAll(ref reader, dataSize, stub); Av1TileDecoder decoder = new(obuReader.SequenceHeader, obuReader.FrameHeader); // Act decoder.DecodeTile(tileSpan, 0); // Assert + Assert.Equal(dataSize * 8, reader.BitPosition); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index d17c49a179..bb2d99a2fa 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -25,6 +25,7 @@ public class ObuFrameHeaderTests // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)] [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d)] public void ReadFrameHeader(string filename, int fileOffset, int blockSize) { // Assign diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 11096df168..5f88847dbb 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -1144,6 +1144,9 @@ public static class Heif public const string IrvineAvif = "Heif/Irvine_CA.avif"; public const string XnConvert = "Heif/jpeg444_xnconvert.avif"; + + // Extremely small image, 4x4 pixels with a single solid color. + public const string Orange4x4 = "Heif/Orange4x4.avif"; } public static class Ico From 96329f401edb42d6c69ae1a1d43a8aeb660f5d30 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 27 Jul 2024 13:34:33 +0200 Subject: [PATCH 143/190] Rename TileDecoder into TileReader --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 19 +++++++++++-------- .../{IAv1TileDecoder.cs => IAv1TileReader.cs} | 10 +++++----- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 6 +++--- .../{Av1TileDecoder.cs => Av1TileReader.cs} | 10 +++++----- .../Formats/Heif/Av1/Av1TileDecoderStub.cs | 13 +++---------- .../Formats/Heif/Av1/Av1TilingTests.cs | 12 ++++++------ .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 12 ++++++------ 7 files changed, 39 insertions(+), 43 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/{IAv1TileDecoder.cs => IAv1TileReader.cs} (66%) rename src/ImageSharp/Formats/Heif/Av1/Tiling/{Av1TileDecoder.cs => Av1TileReader.cs} (99%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 7721f6d39c..eb6fba7144 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -6,11 +6,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal class Av1Decoder : IAv1TileDecoder +internal class Av1Decoder : IAv1TileReader { private readonly ObuReader obuReader; - private Av1TileDecoder? tileDecoder; - private Av1FrameBuffer? frameBuffer; + private Av1TileReader? tileReader; public Av1Decoder() => this.obuReader = new(); @@ -18,22 +17,26 @@ internal class Av1Decoder : IAv1TileDecoder public ObuSequenceHeader? SequenceHeader { get; private set; } + public Av1FrameBuffer? FrameBuffer { get; private set; } + public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); this.obuReader.ReadAll(ref reader, buffer.Length, this, false); - this.frameBuffer = this.tileDecoder?.FrameBuffer; + this.FrameBuffer = this.tileReader?.FrameBuffer; + + // TODO: Decode the FrameBuffer } - public void DecodeTile(Span tileData, int tileNum) + public void ReadTile(Span tileData, int tileNum) { - if (this.tileDecoder == null) + if (this.tileReader == null) { this.SequenceHeader = this.obuReader.SequenceHeader; this.FrameHeader = this.obuReader.FrameHeader; - this.tileDecoder = new Av1TileDecoder(this.SequenceHeader!, this.FrameHeader!); + this.tileReader = new Av1TileReader(this.SequenceHeader!, this.FrameHeader!); } - this.tileDecoder.DecodeTile(tileData, tileNum); + this.tileReader.ReadTile(tileData, tileNum); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs similarity index 66% rename from src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs index 839f49619c..3bbef50bc7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/IAv1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileReader.cs @@ -4,16 +4,16 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// -/// Interface for decoding of image tiles. +/// Interface for reading of image tiles. /// -internal interface IAv1TileDecoder +internal interface IAv1TileReader { /// - /// Decode a single tile. + /// Read the information for a single tile. /// /// /// The bytes of encoded data in the bitstream dedicated to this tile. /// - /// The index of the tile that is to be decoded. - void DecodeTile(Span tileData, int tileNum); + /// The index of the tile that is to be read. + void ReadTile(Span tileData, int tileNum); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 36c9b0c15a..3ce0fb5ef5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -45,7 +45,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileDecoder decoder, bool isAnnexB = false) + public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileReader decoder, bool isAnnexB = false) { bool seenFrameHeader = false; bool frameDecodingFinished = false; @@ -1232,7 +1232,7 @@ internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, b /// /// 5.11.1. General tile group OBU syntax. /// - private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decoder, ObuHeader header, out bool isLastTileGroup) + private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; ObuFrameHeader frameInfo = this.FrameHeader!; @@ -1287,7 +1287,7 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileDecoder decode } Span tileData = reader.GetSymbolReader(tileDataSize); - decoder.DecodeTile(tileData, tileNum); + decoder.ReadTile(tileData, tileNum); } if (tileGroupEnd != tileCount - 1) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs similarity index 99% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index a66a8ee46b..9202ffd75a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; -internal class Av1TileDecoder : IAv1TileDecoder +internal class Av1TileReader : IAv1TileReader { private static readonly int[] SgrprojXqdMid = [-32, 31]; private static readonly int[] WienerTapsMid = [3, -7, 15]; @@ -34,7 +34,7 @@ internal class Av1TileDecoder : IAv1TileDecoder private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; - public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { this.FrameInfo = frameInfo; this.SequenceHeader = sequenceHeader; @@ -69,7 +69,7 @@ public Av1TileDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo public Av1FrameBuffer FrameBuffer { get; } - public void DecodeTile(Span tileData, int tileNum) + public void ReadTile(Span tileData, int tileNum) { Av1SymbolDecoder reader = new(tileData, this.FrameInfo.QuantizationParameters.BaseQIndex); int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; @@ -740,8 +740,8 @@ private static void ReadCoefficientsReverse(ref Av1SymbolDecoder reader, Av1Tran for (int c = endSi; c >= startSi; --c) { int pos = scan[c]; - int coeff_ctx = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); - int level = reader.ReadCoefficientsBase(coeff_ctx, transformSizeContext, planeType); + int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); + int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index b3346e48a0..67f9b0b3ed 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -5,17 +5,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; -internal class Av1TileDecoderStub : IAv1TileDecoder +internal class Av1TileDecoderStub : IAv1TileReader { - public void StartDecodeTiles() - { - } - - public void DecodeTile(Span tileData, int tileNum) - { - } - - public void FinishDecodeTiles(bool doCdef, bool doLoopRestoration) + public void ReadTile(Span tileData, int tileNum) { + // Intentionally left blank. } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 7348e33f3f..60d62d60a5 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -20,16 +20,16 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til byte[] content = File.ReadAllBytes(filePath); Span headerSpan = content.AsSpan(dataOffset, dataSize); Span tileSpan = content.AsSpan(tileOffset, dataSize - tileOffset); - Av1BitStreamReader reader = new(headerSpan); - IAv1TileDecoder stub = new Av1TileDecoderStub(); + Av1BitStreamReader bitStreamReader = new(headerSpan); + IAv1TileReader stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); - obuReader.ReadAll(ref reader, dataSize, stub); - Av1TileDecoder decoder = new(obuReader.SequenceHeader, obuReader.FrameHeader); + obuReader.ReadAll(ref bitStreamReader, dataSize, stub); + Av1TileReader tileReader = new(obuReader.SequenceHeader, obuReader.FrameHeader); // Act - decoder.DecodeTile(tileSpan, 0); + tileReader.ReadTile(tileSpan, 0); // Assert - Assert.Equal(dataSize * 8, reader.BitPosition); + Assert.Equal(dataSize * 8, bitStreamReader.BitPosition); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index bb2d99a2fa..5b5d828360 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -33,7 +33,7 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); Av1BitStreamReader reader = new(span); - IAv1TileDecoder decoder = new Av1TileDecoderStub(); + IAv1TileReader decoder = new Av1TileDecoderStub(); ObuReader obuReader = new(); // Act @@ -83,7 +83,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); - IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + IAv1TileReader tileDecoder = new Av1TileDecoderStub(); Av1BitStreamReader reader = new(span); ObuReader obuReader1 = new(); @@ -98,7 +98,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b // Assign 2 Span encodedBuffer = encoded.ToArray(); - IAv1TileDecoder tileDecoder2 = new Av1TileDecoderStub(); + IAv1TileReader tileDecoder2 = new Av1TileDecoderStub(); Av1BitStreamReader reader2 = new(span); ObuReader obuReader2 = new(); @@ -118,7 +118,7 @@ public void ReadTemporalDelimiter() // Arrange Av1BitStreamReader reader = new(DefaultTemporalDelimiterBitStream); ObuReader obuReader = new(); - IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, tileDecoder); @@ -135,7 +135,7 @@ public void ReadHeaderWithoutSizeField() byte[] bitStream = [0x10]; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); - IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder); @@ -152,7 +152,7 @@ public void ReadSequenceHeader() byte[] bitStream = DefaultSequenceHeaderBitStream; Av1BitStreamReader reader = new(bitStream); ObuReader obuReader = new(); - IAv1TileDecoder tileDecoder = new Av1TileDecoderStub(); + IAv1TileReader tileDecoder = new Av1TileDecoderStub(); ObuSequenceHeader expected = GetDefaultSequenceHeader(); // Act From 056814f0b387de6fd4a8749a15b9df8ef73fffce Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 27 Jul 2024 13:53:18 +0200 Subject: [PATCH 144/190] Namespace update --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 12 ++++++-- .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 2 +- .../Av1/Tiling/Av1DefaultDistributions.cs | 2 +- .../Heif/Av1/Tiling/Av1Distribution.cs | 2 +- .../Heif/Av1/Tiling/Av1FilterIntraMode.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs | 5 +--- .../Heif/Av1/Tiling/Av1FrameModeInfoMap.cs | 2 +- .../Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1NzMap.cs | 2 +- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 2 +- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 2 +- .../Heif/Av1/Tiling/Av1PartitionContext.cs | 2 +- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1PlaneType.cs | 2 +- .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolEncoder.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolReader.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolWriter.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileInfo.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Av1/Tiling/Av1TransformBlockContext.cs | 2 +- .../Heif/Av1/Tiling/Av1TransformInfo.cs | 2 +- .../Heif/Av1/Transform/Av1FrameDecoder.cs | 28 +++++++++++++++++++ .../Formats/Heif/Av1/Av1TilingTests.cs | 2 +- .../Formats/Heif/Av1/SymbolTest.cs | 2 +- 26 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index eb6fba7144..16897aba40 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -2,7 +2,8 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -10,6 +11,7 @@ internal class Av1Decoder : IAv1TileReader { private readonly ObuReader obuReader; private Av1TileReader? tileReader; + private Av1FrameDecoder? frameDecoder; public Av1Decoder() => this.obuReader = new(); @@ -23,9 +25,13 @@ public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); this.obuReader.ReadAll(ref reader, buffer.Length, this, false); - this.FrameBuffer = this.tileReader?.FrameBuffer; + Guard.NotNull(this.tileReader, nameof(this.tileReader)); + Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); + Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); - // TODO: Decode the FrameBuffer + this.FrameBuffer = this.tileReader.FrameBuffer; + this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameBuffer); + this.frameDecoder.DecodeFrame(); } public void ReadTile(Span tileData, int tileNum) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index 8b8d449ec3..6d742c97c2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1BlockModeInfo { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index afda5388eb..b2c5259e10 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal static class Av1DefaultDistributions { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs index 9e4a6d3edd..1f3cf6916f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// /// Class representing the probability distribution used for symbol coding. diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs index b93b8522df..f00132db33 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal enum Av1FilterIntraMode { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs index 1d1e9d162b..96643183dd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs @@ -1,12 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; -using System.Reflection.Metadata.Ecma335; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal partial class Av1FrameBuffer { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs index b6f6a0c73d..196a409352 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal partial class Av1FrameBuffer { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs index 6adb43dabd..649a069b0b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1IntraFilterModeInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1IntraFilterModeInfo { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs index 4326370ecc..c5505899ad 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal static class Av1NzMap { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index db5ab07343..9cae5c5254 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1ParseAboveNeighbor4x4Context { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index 5b9d18d36f..e61c7b563e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1ParseLeftNeighbor4x4Context { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs index 6d5feafad5..2289d28d78 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; // Generates 5 bit field in which each bit set to 1 represents // a BlockSize partition 11111 means we split 128x128, 64x64, 32x32, 16x16 diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index 99994f2d94..c1e733bfbe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1PartitionInfo { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs index d41e4e6e10..3c790f5092 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PlaneType.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal enum Av1PlaneType : int { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index b76b8c87d8..bb5a90d0d0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SuperblockInfo { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 09afb9b752..62a3894d45 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -4,7 +4,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal ref struct Av1SymbolDecoder { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs index 391cd13605..3da3237c26 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs @@ -3,7 +3,7 @@ using System.Buffers; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SymbolEncoder : IDisposable { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs index 19aed63865..112151b152 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal ref struct Av1SymbolReader { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs index 2752facb4d..8765561800 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs @@ -4,7 +4,7 @@ using System.Buffers; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SymbolWriter : IDisposable { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index 56d5801333..f4058b9183 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1TileInfo { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 9202ffd75a..a27335a6f2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1TileReader : IAv1TileReader { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs index 4e761b545a..6256867e7b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformBlockContext.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1TransformBlockContext { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs index 9f6af30c53..3f79f90244 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformInfo.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; /// /// Information of a single Transform Block. diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs new file mode 100644 index 0000000000..85744a4f6e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1FrameDecoder +{ + private ObuSequenceHeader sequenceHeader; + private ObuFrameHeader frameHeader; + private Av1FrameBuffer frameBuffer; + + public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + this.frameBuffer = frameBuffer; + } + + public void DecodeFrame() + { + Guard.NotNull(this.sequenceHeader); + + // TODO: Implement. + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 60d62d60a5..1c76aa7ec0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs index 8e3147ec7a..33a47cd807 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs @@ -3,7 +3,7 @@ using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.Symbol; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; From ab2ae29dc9c66ca8adb4ef15fc0316cda2b05434 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 11:47:18 +0200 Subject: [PATCH 145/190] ObuWriter improvements --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 21 ++ .../Formats/Heif/Av1/IAv1TileWriter.cs | 19 ++ .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 308 +++++++++++------- .../Heif/Av1/Transform/Av1FrameDecoder.cs | 8 +- .../Formats/Heif/Av1/Av1TileDecoderStub.cs | 11 +- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 92 +++++- 6 files changed, 311 insertions(+), 148 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index 67ae414159..0b447f832d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -113,4 +113,25 @@ private void WriteBit(byte value) this.bitOffset = 0; } } + + public void WriteLittleEndian(uint value, int n) + { + // See section 4.10.4 of the AV1-Specification + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Little Endian value only allowed on byte alignment"); + + uint t = value; + for (int i = 0; i < n; i++) + { + this.WriteLiteral(t & 0xff, 8); + t >>= 8; + } + } + + internal void WriteBlob(Span tileData) + { + DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment"); + + this.stream.Write(tileData); + this.bitOffset += tileData.Length << 3; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs new file mode 100644 index 0000000000..1e2552b8b3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/IAv1TileWriter.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +/// +/// Interface for writing of image tiles. +/// +internal interface IAv1TileWriter +{ + /// + /// Write the information for a single tile. + /// + /// The index of the tile that is to be read. + /// + /// The bytes of encoded data in the bitstream dedicated to this tile. + /// + Span WriteTile(int tileNum); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index ea38b28975..e7025b5cff 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -1,16 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuWriter { + private int[] previousQIndex = []; + private int[] previousDeltaLoopFilter = []; + /// /// Encode a single frame into OBU's. /// - public static void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter) { MemoryStream bufferStream = new(100); Av1BitStreamWriter writer = new(bufferStream); @@ -27,19 +31,15 @@ public static void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, Obu if (frameInfo != null && sequenceHeader != null) { bufferStream.Position = 0; - WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); - int bytesWritten = (writer.BitPosition + 7) >> 3; - writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.FrameHeader, bufferStream.GetBuffer(), bytesWritten); - } + this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); + if (frameInfo.TilesInfo != null) + { + WriteTileGroup(ref writer, frameInfo.TilesInfo, tileWriter); + } - if (frameInfo?.TilesInfo != null) - { - bufferStream.Position = 0; - WriteTileGroup(ref writer, frameInfo.TilesInfo); - int bytesWritten = (writer.BitPosition + 7) >> 3; + int bytesWritten = 5; // (writer.BitPosition + 7) >> 3; writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.TileGroup, bufferStream.GetBuffer(), bytesWritten); + WriteObuHeaderAndSize(stream, ObuType.Frame, bufferStream.GetBuffer(), bytesWritten); } } @@ -234,53 +234,53 @@ private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHea WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); } - private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, ObuTileGroupHeader tileInfo) + private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { - int superBlockColumnCount; - int superBlockRowCount; - int superBlockShift; - if (sequenceHeader.Use128x128Superblock) - { - superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 31) >> 5; - superBlockRowCount = (frameInfo.ModeInfoRowCount + 31) >> 5; - superBlockShift = 5; - } - else - { - superBlockColumnCount = (frameInfo.ModeInfoColumnCount + 15) >> 4; - superBlockRowCount = (frameInfo.ModeInfoRowCount + 15) >> 4; - superBlockShift = 4; - } - - int superBlockSize = superBlockShift + 2; + ObuTileGroupHeader tileInfo = frameInfo.TilesInfo; + int superblockColumnCount; + int superblockRowCount; + int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; + int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; + superblockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + int superBlockSize = superblockShift + 2; int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); tileInfo.MaxTileWidthSuperblock = Av1Constants.MaxTileWidth >> superBlockSize; tileInfo.MaxTileHeightSuperblock = (Av1Constants.MaxTileArea / Av1Constants.MaxTileWidth) >> superBlockSize; - tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superBlockColumnCount); - tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superBlockColumnCount, Av1Constants.MaxTileColumnCount)); - tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superBlockRowCount, Av1Constants.MaxTileRowCount)); - tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superBlockColumnCount * superBlockRowCount)); + tileInfo.MinLog2TileColumnCount = ObuReader.TileLog2(tileInfo.MaxTileWidthSuperblock, superblockColumnCount); + tileInfo.MaxLog2TileColumnCount = ObuReader.TileLog2(1, Math.Min(superblockColumnCount, Av1Constants.MaxTileColumnCount)); + tileInfo.MaxLog2TileRowCount = ObuReader.TileLog2(1, Math.Min(superblockRowCount, Av1Constants.MaxTileRowCount)); + tileInfo.MinLog2TileCount = Math.Max(tileInfo.MinLog2TileColumnCount, ObuReader.TileLog2(maxTileAreaOfSuperBlock, superblockColumnCount * superblockRowCount)); + + int log2TileColumnCount = Av1Math.Log2(tileInfo.TileColumnCount); + int log2TileRowCount = Av1Math.Log2(tileInfo.TileRowCount); writer.WriteBoolean(tileInfo.HasUniformTileSpacing); if (tileInfo.HasUniformTileSpacing) { - for (int i = 0; i < tileInfo.TileColumnCountLog2; i++) + // Uniform spaced tiles with power-of-two number of rows and columns + // tile columns + int ones = log2TileColumnCount - tileInfo.MinLog2TileColumnCount; + while (ones-- > 0) { writer.WriteBoolean(true); } - if (tileInfo.TileColumnCountLog2 < tileInfo.MaxLog2TileColumnCount) + if (log2TileColumnCount < tileInfo.MaxLog2TileColumnCount) { writer.WriteBoolean(false); } - for (int i = 0; i < tileInfo.TileRowCountLog2; i++) + // rows + tileInfo.MinLog2TileRowCount = Math.Min(tileInfo.MinLog2TileCount - log2TileColumnCount, 0); + ones = log2TileRowCount - tileInfo.MinLog2TileRowCount; + while (ones-- > 0) { writer.WriteBoolean(true); } - if (tileInfo.TileRowCountLog2 < tileInfo.MaxLog2TileRowCount) + if (log2TileRowCount < tileInfo.MaxLog2TileRowCount) { writer.WriteBoolean(false); } @@ -289,29 +289,29 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead { int startSuperBlock = 0; int i = 0; - for (; startSuperBlock < superBlockColumnCount; i++) + for (; startSuperBlock < superblockColumnCount; i++) { - uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superBlockShift) - startSuperBlock); - uint maxWidth = (uint)Math.Min(superBlockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock); + uint widthInSuperBlocks = (uint)((tileInfo.TileColumnStartModeInfo[i] >> superblockShift) - startSuperBlock); + uint maxWidth = (uint)Math.Min(superblockColumnCount - startSuperBlock, tileInfo.MaxTileWidthSuperblock); writer.WriteNonSymmetric(widthInSuperBlocks - 1, maxWidth); startSuperBlock += (int)widthInSuperBlocks; } - if (startSuperBlock != superBlockColumnCount) + if (startSuperBlock != superblockColumnCount) { throw new ImageFormatException("Super block tiles width does not add up to total width."); } startSuperBlock = 0; - for (i = 0; startSuperBlock < superBlockRowCount; i++) + for (i = 0; startSuperBlock < superblockRowCount; i++) { - uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superBlockShift) - startSuperBlock); - uint maxHeight = (uint)Math.Min(superBlockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock); + uint heightInSuperBlocks = (uint)((tileInfo.TileRowStartModeInfo[i] >> superblockShift) - startSuperBlock); + uint maxHeight = (uint)Math.Min(superblockRowCount - startSuperBlock, tileInfo.MaxTileHeightSuperblock); writer.WriteNonSymmetric(heightInSuperBlocks - 1, maxHeight); startSuperBlock += (int)heightInSuperBlocks; } - if (startSuperBlock != superBlockRowCount) + if (startSuperBlock != superblockRowCount) { throw new ImageFormatException("Super block tiles height does not add up to total height."); } @@ -322,122 +322,163 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead writer.WriteLiteral(tileInfo.ContextUpdateTileId, tileInfo.TileRowCountLog2 + tileInfo.TileColumnCountLog2); writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2); } + + frameInfo.TilesInfo = tileInfo; } - private static void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - uint previousFrameId = 0; - bool isIntraFrame = true; - int idLength = sequenceHeader.FrameIdLength - 1 + sequenceHeader.DeltaFrameIdLength - 2 + 3; - writer.WriteBoolean(frameInfo.DisableCdfUpdate); - if (frameInfo.AllowScreenContentTools) + // TODO: Make tile count configurable. + int tileCount = 1; + int planesCount = sequenceHeader.ColorConfig.PlaneCount; + writer.WriteBoolean(frameHeader.DisableCdfUpdate); + if (sequenceHeader.ForceScreenContentTools == 2) { - writer.WriteBoolean(frameInfo.AllowScreenContentTools); + writer.WriteBoolean(frameHeader.AllowScreenContentTools); + } + else + { + // Guard.IsTrue(frameInfo.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools); } - if (frameInfo.AllowScreenContentTools) + if (frameHeader.AllowScreenContentTools) { - if (sequenceHeader.ForceIntegerMotionVector == 1) + if (sequenceHeader.ForceIntegerMotionVector == 2) { - writer.WriteBoolean(frameInfo.ForceIntegerMotionVector); + writer.WriteBoolean(frameHeader.ForceIntegerMotionVector); + } + else + { + // Guard.IsTrue(frameInfo.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameInfo.ForceIntegerMotionVector), "Frame and sequence must be in sync"); } } - bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); - if (havePreviousFrameId) + if (frameHeader.FrameType == ObuFrameType.KeyFrame) + { + if (!frameHeader.ShowFrame) + { + throw new NotImplementedException("No support for hidden frames."); + } + } + else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame) { - previousFrameId = frameInfo.CurrentFrameId; + throw new NotImplementedException("No IntraOnly frames supported."); } - if (sequenceHeader.IsFrameIdNumbersPresent) + if (frameHeader.FrameType == ObuFrameType.KeyFrame) { - writer.WriteLiteral(frameInfo.CurrentFrameId, idLength); - if (havePreviousFrameId) + WriteFrameSize(ref writer, sequenceHeader, frameHeader, false); + if (frameHeader.AllowScreenContentTools) { - uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? - frameInfo.CurrentFrameId - previousFrameId : - (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); - if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) - { - throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); - } + writer.WriteBoolean(frameHeader.AllowIntraBlockCopy); + } + } + else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame) + { + WriteFrameSize(ref writer, sequenceHeader, frameHeader, false); + if (frameHeader.AllowScreenContentTools) + { + writer.WriteBoolean(frameHeader.AllowIntraBlockCopy); } + } + else + { + throw new NotImplementedException("Inter frames not applicable for AVIF."); + } - int diffLength = sequenceHeader.DeltaFrameIdLength; - for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) + WriteTileInfo(ref writer, sequenceHeader, frameHeader); + WriteQuantizationParameters(ref writer, frameHeader.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); + WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader, planesCount); + + if (frameHeader.QuantizationParameters.BaseQIndex > 0) + { + writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent); + if (frameHeader.DeltaQParameters.IsPresent) { - if (frameInfo.CurrentFrameId > (1U << diffLength)) + writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2); + for (int tileIndex = 0; tileIndex < tileCount; tileIndex++) { - if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || - frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) - { - frameInfo.ReferenceValid[i] = false; - } + this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex; + } + + if (frameHeader.AllowIntraBlockCopy) + { + Guard.IsFalse( + frameHeader.DeltaLoopFilterParameters.IsPresent, + nameof(frameHeader.DeltaLoopFilterParameters.IsPresent), + "Allow INTRA block copy required Loop Filter."); } - else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && - frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength)))) + else { - frameInfo.ReferenceValid[i] = false; + writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent); } - } - } - - writer.WriteLiteral(frameInfo.OrderHint, sequenceHeader.OrderHintInfo.OrderHintBits); - if (!isIntraFrame && !frameInfo.ErrorResilientMode) - { - writer.WriteLiteral(frameInfo.PrimaryReferenceFrame, Av1Constants.PimaryReferenceBits); + if (frameHeader.DeltaLoopFilterParameters.IsPresent) + { + writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2); + writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti); + int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount; + for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++) + { + this.previousDeltaLoopFilter[loopFilterId] = 0; + } + } + } } - // Skipping, as no decoder info model present - frameInfo.AllowHighPrecisionMotionVector = false; - frameInfo.UseReferenceFrameMotionVectors = false; - frameInfo.AllowIntraBlockCopy = false; - if (frameInfo.FrameType != ObuFrameType.SwitchFrame && !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + if (frameHeader.AllLossless) { - writer.WriteLiteral(frameInfo.RefreshFrameFlags, 8); + throw new NotImplementedException("No entire lossless supported."); } - - if (isIntraFrame) + else { - WriteFrameSize(ref writer, sequenceHeader, frameInfo, false); - WriteRenderSize(ref writer, frameInfo); - if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) + if (!frameHeader.CodedLossless) { - if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) + WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader, planesCount); + if (sequenceHeader.CdefLevel > 0) { - writer.WriteBoolean(frameInfo.AllowIntraBlockCopy); + WriteCdefParameters(ref writer, sequenceHeader, frameHeader, planesCount); } } + + if (sequenceHeader.EnableRestoration) + { + WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader, planesCount); + } } - if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) + writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select); + + // No compound INTER-INTER for AVIF. + if (frameHeader.SkipModeParameters.SkipModeAllowed) { - SetupPastIndependence(frameInfo); + writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag); } - // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); - WriteTileInfo(ref writer, sequenceHeader, frameInfo, frameInfo.TilesInfo); - WriteQuantizationParameters(ref writer, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); - WriteSegmentationParameters(ref writer, sequenceHeader, frameInfo, planesCount); - WriteFrameDeltaQParameters(ref writer, frameInfo); - WriteFrameDeltaLoopFilterParameters(ref writer, frameInfo); - - WriteLoopFilterParameters(ref writer, sequenceHeader, frameInfo, planesCount); - WriteCdefParameters(ref writer, sequenceHeader, frameInfo, planesCount); - WriteLoopRestorationParameters(ref writer, sequenceHeader, frameInfo, planesCount); - WriteTransformMode(ref writer, frameInfo); + if (FrameMightAllowWarpedMotion(sequenceHeader, frameHeader)) + { + writer.WriteBoolean(frameHeader.AllowWarpedMotion); + } + else + { + Guard.IsFalse(frameHeader.AllowWarpedMotion, nameof(frameHeader.AllowWarpedMotion), "No warped motion allowed."); + } - // Not applicable for INTRA frames. - // WriteFrameReferenceMode(ref writer, frameInfo.ReferenceMode, isIntraFrame); - // WriteSkipModeParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); - writer.WriteBoolean(frameInfo.UseReducedTransformSet); + writer.WriteBoolean(frameHeader.UseReducedTransformSet); - // Not applicable for INTRA frames. - // WriteGlobalMotionParameters(ref writer, sequenceHeader, frameInfo, isIntraFrame); - WriteFilmGrainFilterParameters(ref writer, frameInfo.FilmGrainParameters); + // No global motion for AVIF. + if (sequenceHeader.AreFilmGrainingParametersPresent && (frameHeader.ShowFrame || frameHeader.ShowableFrame)) + { + WriteFilmGrainFilterParameters(ref writer, frameHeader.FilmGrainParameters); + } } + private static bool IsSuperResolutionUnscaled(ObuFrameSize frameSize) + => frameSize.FrameWidth == frameSize.SuperResolutionUpscaledWidth; + + private static bool FrameMightAllowWarpedMotion(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) + => false; // !frameHeader.ErrorResilientMode && !FrameIsIntraOnly(sequenceHeader) && scs->enable_warped_motion; + private static void SetupPastIndependence(ObuFrameHeader frameInfo) { // TODO: Initialize the loop filter parameters. @@ -460,24 +501,21 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i } } - private static int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) + private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) { - int planeCount = sequenceHeader.ColorConfig.IsMonochrome ? 1 : 3; int startBitPosition = writer.BitPosition; - WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo, planeCount); + this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo); if (writeTrailingBits) { WriteTrailingBits(ref writer); } - AlignToByteBoundary(ref writer); - int endPosition = writer.BitPosition; int headerBytes = (endPosition - startBitPosition) / 8; return headerBytes; } - private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo) + private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter) { int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = writer.BitPosition; @@ -494,11 +532,29 @@ private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHea } AlignToByteBoundary(ref writer); + + WriteTileData(ref writer, tileInfo, tileWriter); + int endBitPosition = writer.BitPosition; int headerBytes = (endBitPosition - startBitPosition) / 8; return headerBytes; } + private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter) + { + int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; + for (int tileNum = 0; tileNum < tileCount; tileNum++) + { + Span tileData = tileWriter.WriteTile(tileNum); + if (tileNum != tileCount - 1 && tileCount > 1) + { + writer.WriteLittleEndian((uint)tileData.Length - 1U, tileInfo.TileSizeBytes); + } + + writer.WriteBlob(tileData); + } + } + private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) { bool isCoded = deltaQ == 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs index 85744a4f6e..040ce87b9c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs @@ -8,9 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal class Av1FrameDecoder { - private ObuSequenceHeader sequenceHeader; - private ObuFrameHeader frameHeader; - private Av1FrameBuffer frameBuffer; + private readonly ObuSequenceHeader sequenceHeader; + private readonly ObuFrameHeader frameHeader; + private readonly Av1FrameBuffer frameBuffer; public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer) { @@ -22,6 +22,8 @@ public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHea public void DecodeFrame() { Guard.NotNull(this.sequenceHeader); + Guard.NotNull(this.frameHeader); + Guard.NotNull(this.frameBuffer); // TODO: Implement. } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs index 67f9b0b3ed..a2afda2ac7 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TileDecoderStub.cs @@ -5,10 +5,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; -internal class Av1TileDecoderStub : IAv1TileReader +internal class Av1TileDecoderStub : IAv1TileReader, IAv1TileWriter { + private readonly Dictionary tileDatas = []; + public void ReadTile(Span tileData, int tileNum) - { - // Intentionally left blank. - } + => this.tileDatas.Add(tileNum, tileData.ToArray()); + + public Span WriteTile(int tileNum) + => this.tileDatas[tileNum]; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 5b5d828360..64f752e5a9 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -12,6 +12,11 @@ public class ObuFrameHeaderTests private static readonly byte[] DefaultSequenceHeaderBitStream = [0x0a, 0x06, 0b001_1_1_000, 0b00_1000_01, 0b11_110101, 0b001_11101, 0b111_1_1_1_0_1, 0b1_0_0_1_1_1_10]; + // TODO: Check with libgav1 test code. + private static readonly byte[] KeyFrameHeaderBitStream = + // libgav1 expects this: [0x32, 0x05, 0x10, 0x00]; + [0x32, 0x05, 0x20, 0x04]; + // Bits Syntax element Value // 1 obu_forbidden_bit 0 // 4 obu_type 2 (OBU_TEMPORAL_DELIMITER) @@ -22,10 +27,10 @@ public class ObuFrameHeaderTests private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00]; [Theory] - // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000D)] - // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6BD1)] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] - [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d)] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000d)] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6bd1)] + [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] + [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] public void ReadFrameHeader(string filename, int fileOffset, int blockSize) { // Assign @@ -49,26 +54,27 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) /* [Theory] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] - public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) + [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d, 0x0128)] + [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc, 0x0114)] + public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize, int tileOffset) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); - Av1TileDecoderStub tileDecoder = new(); + Av1TileDecoderStub tileStub = new(); Av1BitStreamReader reader = new(span); ObuReader obuReader = new(); // Act 1 - obuReader.ReadAll(ref reader, blockSize, tileDecoder); + obuReader.ReadAll(ref reader, blockSize, tileStub); // Assign 2 MemoryStream encoded = new(); // Act 2 ObuWriter obuWriter = new(); - ObuWriter.Write(encoded, obuReader.SequenceHeader, obuReader.FrameHeader); + obuWriter.WriteAll(encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); // Assert Assert.Equal(span, encoded.ToArray()); @@ -76,25 +82,27 @@ public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, */ [Theory] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC)] + [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] + [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); byte[] content = File.ReadAllBytes(filePath); Span span = content.AsSpan(fileOffset, blockSize); - IAv1TileReader tileDecoder = new Av1TileDecoderStub(); + Av1TileDecoderStub tileStub = new(); Av1BitStreamReader reader = new(span); ObuReader obuReader1 = new(); // Act 1 - obuReader1.ReadAll(ref reader, blockSize, tileDecoder); + obuReader1.ReadAll(ref reader, blockSize, tileStub); // Assign 2 MemoryStream encoded = new(); // Act 2 - ObuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader); + ObuWriter obuWriter = new(); + obuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, tileStub); // Assign 2 Span encodedBuffer = encoded.ToArray(); @@ -169,9 +177,10 @@ public void WriteTemporalDelimiter() { // Arrange using MemoryStream stream = new(2); + ObuWriter obuWriter = new(); // Act - ObuWriter.WriteAll(stream, null, null); + obuWriter.WriteAll(stream, null, null, null); byte[] actual = stream.GetBuffer(); // Assert @@ -184,9 +193,10 @@ public void WriteSequenceHeader() // Arrange using MemoryStream stream = new(10); ObuSequenceHeader input = GetDefaultSequenceHeader(); + ObuWriter obuWriter = new(); // Act - ObuWriter.WriteAll(stream, input, null); + obuWriter.WriteAll(stream, input, null, null); byte[] buffer = stream.GetBuffer(); // Assert @@ -195,6 +205,28 @@ public void WriteSequenceHeader() Assert.Equal(DefaultSequenceHeaderBitStream, actual); } + [Fact] + public void WriteFrameHeader() + { + // Arrange + using MemoryStream stream = new(10); + ObuSequenceHeader sequenceInput = GetDefaultSequenceHeader(); + ObuFrameHeader frameInput = GetKeyFrameHeader(); + Av1TileDecoderStub tileStub = new(); + byte[] empty = []; + tileStub.ReadTile(empty, 0); + ObuWriter obuWriter = new(); + + // Act + obuWriter.WriteAll(stream, sequenceInput, frameInput, tileStub); + byte[] buffer = stream.GetBuffer(); + + // Assert + // Skip over Temporal Delimiter and Sequence header. + byte[] actual = buffer.AsSpan().Slice(DefaultTemporalDelimiterBitStream.Length + DefaultSequenceHeaderBitStream.Length, KeyFrameHeaderBitStream.Length).ToArray(); + Assert.Equal(KeyFrameHeaderBitStream, actual); + } + private static ObuSequenceHeader GetDefaultSequenceHeader() // Offset Bits Syntax element Value @@ -261,4 +293,34 @@ private static ObuSequenceHeader GetDefaultSequenceHeader() }, AreFilmGrainingParametersPresent = true, }; + + private static ObuFrameHeader GetKeyFrameHeader() + => new() + { + FrameType = ObuFrameType.KeyFrame, + ShowFrame = true, + ShowableFrame = false, + DisableFrameEndUpdateCdf = false, + FrameSize = new() + { + FrameWidth = 426, + FrameHeight = 240, + RenderWidth = 426, + RenderHeight = 240, + SuperResolutionUpscaledWidth = 426, + }, + PrimaryReferenceFrame = 7, + ModeInfoRowCount = 60, + ModeInfoColumnCount = 108, + RefreshFrameFlags = 0xff, + ErrorResilientMode = true, + ForceIntegerMotionVector = true, + TilesInfo = new ObuTileGroupHeader() + { + HasUniformTileSpacing = true, + TileColumnCount = 1, + TileRowCount = 1, + } + }; + } From b8322fc554431fcded066cc42a89afad4d08010d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 12:13:03 +0200 Subject: [PATCH 146/190] Merge in required changes from upstream --- .../Formats/Heif/HeifDecoderCore.cs | 17 +++----- .../Formats/Heif/HeifEncoderCore.cs | 4 +- src/ImageSharp/Formats/Heif/HeifMetadata.cs | 40 ++++++++++++++++++- src/ImageSharp/ImageSharp.csproj | 2 + .../Formats/Heif/HeifEncoderTests.cs | 2 +- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 1c65d6e00c..4481aadf7b 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Performs the HEIF decoding operation. /// -internal sealed class HeifDecoderCore : IImageDecoderInternals +internal sealed class HeifDecoderCore : ImageDecoderCore { /// /// The general configuration. @@ -42,8 +42,8 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals /// /// The decoder options. public HeifDecoderCore(DecoderOptions options) + : base(options) { - this.Options = options; this.configuration = options.Configuration; this.metadata = new ImageMetadata(); this.items = new List(); @@ -51,14 +51,7 @@ public HeifDecoderCore(DecoderOptions options) } /// - public DecoderOptions Options { get; } - - /// - public Size Dimensions { get; } - - /// - public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel + protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) { if (!this.CheckFileTypeBox(stream)) { @@ -106,7 +99,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken } /// - public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + protected override ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.CheckFileTypeBox(stream); @@ -134,7 +127,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat this.UpdateMetadata(this.metadata, item); - return new ImageInfo(new PixelTypeInfo(item.BitsPerPixel), new(item.Extent.Width, item.Extent.Height), this.metadata); + return new ImageInfo(new(item.Extent.Width, item.Extent.Height), this.metadata); } private bool CheckFileTypeBox(BufferedReadStream stream) diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 8985768112..519aa3200b 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Image encoder for writing an image to a stream as a HEIF image. /// -internal sealed class HeifEncoderCore : IImageEncoderInternals +internal sealed class HeifEncoderCore { /// /// The global configuration. @@ -326,7 +326,7 @@ private static async Task CompressPixels(Image image, Ca using MemoryStream stream = new(); JpegEncoder encoder = new() { - ColorType = JpegEncodingColor.YCbCrRatio420 + ColorType = JpegColorType.YCbCrRatio420 }; await image.SaveAsJpegAsync(stream, encoder, cancellationToken); return stream.ToArray(); diff --git a/src/ImageSharp/Formats/Heif/HeifMetadata.cs b/src/ImageSharp/Formats/Heif/HeifMetadata.cs index 725389f0e5..6bbf8b49e6 100644 --- a/src/ImageSharp/Formats/Heif/HeifMetadata.cs +++ b/src/ImageSharp/Formats/Heif/HeifMetadata.cs @@ -1,12 +1,14 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.PixelFormats; + namespace SixLabors.ImageSharp.Formats.Heif; /// /// Provides HEIF specific metadata information for the image. /// -public class HeifMetadata : IDeepCloneable +public class HeifMetadata : IFormatMetadata { /// /// Initializes a new instance of the class. @@ -28,5 +30,39 @@ private HeifMetadata(HeifMetadata other) public HeifCompressionMethod CompressionMethod { get; set; } /// - public IDeepCloneable DeepClone() => new HeifMetadata(this); + public static HeifMetadata FromFormatConnectingMetadata(FormatConnectingMetadata metadata) + { + return new HeifMetadata + { + CompressionMethod = HeifCompressionMethod.LegacyJpeg + }; + } + + /// + public PixelTypeInfo GetPixelTypeInfo() + { + int bpp = 8; + PixelColorType colorType = PixelColorType.RGB; + PixelComponentInfo info = PixelComponentInfo.Create(3, bpp, 8, 8, 8); + + return new PixelTypeInfo(bpp) + { + AlphaRepresentation = PixelAlphaRepresentation.None, + ColorType = colorType, + ComponentInfo = info, + }; + } + + /// + public FormatConnectingMetadata ToFormatConnectingMetadata() + => new() + { + PixelTypeInfo = this.GetPixelTypeInfo(), + }; + + /// + IDeepCloneable IDeepCloneable.DeepClone() => this.DeepClone(); + + /// + public HeifMetadata DeepClone() => new(this); } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 156c328406..1d83fbe4a4 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -56,6 +56,7 @@ True True Heif4CharCode.tt + True True @@ -162,6 +163,7 @@ TextTemplatingFileGenerator Heif4CharCode.cs + ImageMetadataExtensions.cs TextTemplatingFileGenerator diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs index c7e0e8832d..8826b2103f 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs @@ -17,7 +17,7 @@ public class HeifEncoderTests public static void Encode(TestImageProvider provider, HeifCompressionMethod compressionMethod) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(new MagickReferenceDecoder()); + using Image image = provider.GetImage(new MagickReferenceDecoder(HeifFormat.Instance)); using MemoryStream stream = new(); HeifEncoder encoder = new(); image.Save(stream, encoder); From 23bf5dede079719a1567ca6614a590696527de0f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 16:42:03 +0200 Subject: [PATCH 147/190] Obu reading and writing improvements --- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 +- .../ObuLoopRestorationItem.cs | 11 + .../ObuLoopRestorationParameters.cs | 18 +- .../ObuQuantizationParameters.cs | 2 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 56 ++-- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 246 +++++++++++++----- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 7 +- 8 files changed, 243 insertions(+), 101 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 6540ff8afe..3dc6788e6e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -37,7 +37,7 @@ internal class ObuFrameHeader public ObuLoopFilterParameters LoopFilterParameters { get; set; } = new ObuLoopFilterParameters(); - public ObuLoopRestorationParameters[] LoopRestorationParameters { get; set; } = new ObuLoopRestorationParameters[3]; + public ObuLoopRestorationParameters LoopRestorationParameters { get; set; } = new ObuLoopRestorationParameters(); public ObuConstraintDirectionalEnhancementFilterParameters CdefParameters { get; set; } = new ObuConstraintDirectionalEnhancementFilterParameters(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs new file mode 100644 index 0000000000..ade31a6606 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationItem.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +internal class ObuLoopRestorationItem +{ + internal int Size { get; set; } + + internal ObuRestorationType Type { get; set; } = ObuRestorationType.None; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs index 205beba370..18db716813 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuLoopRestorationParameters.cs @@ -5,7 +5,21 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; internal class ObuLoopRestorationParameters { - internal int Size { get; set; } + internal ObuLoopRestorationParameters() + { + this.Items = new ObuLoopRestorationItem[3]; + this.Items[0] = new(); + this.Items[1] = new(); + this.Items[2] = new(); + } - internal ObuRestorationType Type { get; set; } = ObuRestorationType.None; + internal bool UsesLoopRestoration { get; set; } + + internal bool UsesChromaLoopRestoration { get; set; } + + internal ObuLoopRestorationItem[] Items { get; } + + internal int UnitShift { get; set; } + + internal int UVShift { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs index 58aa3d6316..a3924eeeec 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuQuantizationParameters.cs @@ -16,4 +16,6 @@ internal class ObuQuantizationParameters public int[] DeltaQAc { get; internal set; } = new int[3]; public int[] QMatrix { get; internal set; } = new int[3]; + + public bool HasSeparateUvDelta { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 3ce0fb5ef5..7ea7db3133 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1272,9 +1272,9 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder frameInfo.CdefParameters.YStrength[0] != 0 || frameInfo.CdefParameters.UvStrength[0] != 0)); bool doLoopRestoration = noIbc && - (frameInfo.LoopRestorationParameters[(int)Av1Plane.Y].Type != ObuRestorationType.None || - frameInfo.LoopRestorationParameters[(int)Av1Plane.U].Type != ObuRestorationType.None || - frameInfo.LoopRestorationParameters[(int)Av1Plane.V].Type != ObuRestorationType.None); + (frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.Y].Type != ObuRestorationType.None || + frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None || + frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None); for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) { @@ -1363,15 +1363,15 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob quantParams.DeltaQAc[(int)Av1Plane.Y] = 0; if (planesCount > 1) { - bool areUvDeltaDifferent = false; + quantParams.HasSeparateUvDelta = false; if (colorInfo.HasSeparateUvDelta) { - areUvDeltaDifferent = reader.ReadBoolean(); + quantParams.HasSeparateUvDelta = reader.ReadBoolean(); } quantParams.DeltaQDc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); quantParams.DeltaQAc[(int)Av1Plane.U] = ReadDeltaQ(ref reader); - if (areUvDeltaDifferent) + if (quantParams.HasSeparateUvDelta) { quantParams.DeltaQDc[(int)Av1Plane.V] = ReadDeltaQ(ref reader); quantParams.DeltaQAc[(int)Av1Plane.V] = ReadDeltaQ(ref reader); @@ -1504,7 +1504,6 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, int planesCount) { ObuFrameHeader frameInfo = this.FrameHeader!; - frameInfo.LoopFilterParameters.FilterLevel = new int[2]; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) { return; @@ -1575,58 +1574,51 @@ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHea /// private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - _ = planesCount; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) { - frameInfo.LoopRestorationParameters[0] = new ObuLoopRestorationParameters(); - frameInfo.LoopRestorationParameters[1] = new ObuLoopRestorationParameters(); - frameInfo.LoopRestorationParameters[2] = new ObuLoopRestorationParameters(); return; } - bool usesLoopRestoration = false; - bool usesChromaLoopRestoration = false; + frameInfo.LoopRestorationParameters.UsesLoopRestoration = false; + frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration = false; for (int i = 0; i < planesCount; i++) { - frameInfo.LoopRestorationParameters[i] = new ObuLoopRestorationParameters - { - Type = (ObuRestorationType)reader.ReadLiteral(2) - }; + frameInfo.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2); - if (frameInfo.LoopRestorationParameters[i].Type != ObuRestorationType.None) + if (frameInfo.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None) { - usesLoopRestoration = true; + frameInfo.LoopRestorationParameters.UsesLoopRestoration = true; if (i > 0) { - usesChromaLoopRestoration = true; + frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration = true; } } } - if (usesLoopRestoration) + if (frameInfo.LoopRestorationParameters.UsesLoopRestoration) { - uint loopRestorationShift = reader.ReadLiteral(1); + frameInfo.LoopRestorationParameters.UnitShift = (int)reader.ReadLiteral(1); if (sequenceHeader.Use128x128Superblock) { - loopRestorationShift++; + frameInfo.LoopRestorationParameters.UnitShift++; } else { if (reader.ReadBoolean()) { - loopRestorationShift += reader.ReadLiteral(1); + frameInfo.LoopRestorationParameters.UnitShift += (int)reader.ReadLiteral(1); } } - frameInfo.LoopRestorationParameters[0].Size = Av1Constants.RestorationMaxTileSize >> (int)(2 - loopRestorationShift); - int uvShift = 0; - if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && usesChromaLoopRestoration) + frameInfo.LoopRestorationParameters.Items[0].Size = Av1Constants.RestorationMaxTileSize >> (2 - frameInfo.LoopRestorationParameters.UnitShift); + frameInfo.LoopRestorationParameters.UVShift = 0; + if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration) { - uvShift = (int)reader.ReadLiteral(1); + frameInfo.LoopRestorationParameters.UVShift = (int)reader.ReadLiteral(1); } - frameInfo.LoopRestorationParameters[1].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift; - frameInfo.LoopRestorationParameters[2].Size = frameInfo.LoopRestorationParameters[0].Size >> uvShift; + frameInfo.LoopRestorationParameters.Items[1].Size = frameInfo.LoopRestorationParameters.Items[0].Size >> frameInfo.LoopRestorationParameters.UVShift; + frameInfo.LoopRestorationParameters.Items[2].Size = frameInfo.LoopRestorationParameters.Items[0].Size >> frameInfo.LoopRestorationParameters.UVShift; } } @@ -1839,14 +1831,14 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt { grainParams.CbMult = reader.ReadLiteral(8); grainParams.CbLumaMult = reader.ReadLiteral(8); - grainParams.CbOffset = reader.ReadLiteral(8); + grainParams.CbOffset = reader.ReadLiteral(9); } if (grainParams.NumCrPoints != 0) { grainParams.CrMult = reader.ReadLiteral(8); grainParams.CrLumaMult = reader.ReadLiteral(8); - grainParams.CrOffset = reader.ReadLiteral(8); + grainParams.CrOffset = reader.ReadLiteral(9); } grainParams.OverlapFlag = reader.ReadBoolean(); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index e7025b5cff..b48061632a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -1,7 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; +using System.Buffers; +using System.Reflection.PortableExecutable; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -16,7 +17,7 @@ internal class ObuWriter /// public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter) { - MemoryStream bufferStream = new(100); + MemoryStream bufferStream = new(2000); Av1BitStreamWriter writer = new(bufferStream); WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); @@ -37,7 +38,7 @@ public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHe WriteTileGroup(ref writer, frameInfo.TilesInfo, tileWriter); } - int bytesWritten = 5; // (writer.BitPosition + 7) >> 3; + int bytesWritten = (writer.BitPosition + 7) >> 3; writer.Flush(); WriteObuHeaderAndSize(stream, ObuType.Frame, bufferStream.GetBuffer(), bytesWritten); } @@ -128,7 +129,14 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH writer.WriteBoolean(colorConfig.IsMonochrome); } - writer.WriteBoolean(false); // colorConfig.IsColorDescriptionPresent + writer.WriteBoolean(colorConfig.IsColorDescriptionPresent); + if (colorConfig.IsColorDescriptionPresent) + { + writer.WriteLiteral((uint)colorConfig.ColorPrimaries, 8); + writer.WriteLiteral((uint)colorConfig.TransferCharacteristics, 8); + writer.WriteLiteral((uint)colorConfig.MatrixCoefficients, 8); + } + if (colorConfig.IsMonochrome) { writer.WriteBoolean(colorConfig.ColorRange); @@ -368,6 +376,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ if (frameHeader.FrameType == ObuFrameType.KeyFrame) { WriteFrameSize(ref writer, sequenceHeader, frameHeader, false); + WriteRenderSize(ref writer, frameHeader); if (frameHeader.AllowScreenContentTools) { writer.WriteBoolean(frameHeader.AllowIntraBlockCopy); @@ -376,6 +385,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ else if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame) { WriteFrameSize(ref writer, sequenceHeader, frameHeader, false); + WriteRenderSize(ref writer, frameHeader); if (frameHeader.AllowScreenContentTools) { writer.WriteBoolean(frameHeader.AllowIntraBlockCopy); @@ -447,7 +457,8 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ } } - writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select); + // No Frame Reference mode selection for AVIF + WriteTransformMode(ref writer, frameHeader); // No compound INTER-INTER for AVIF. if (frameHeader.SkipModeParameters.SkipModeAllowed) @@ -455,52 +466,16 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag); } - if (FrameMightAllowWarpedMotion(sequenceHeader, frameHeader)) - { - writer.WriteBoolean(frameHeader.AllowWarpedMotion); - } - else - { - Guard.IsFalse(frameHeader.AllowWarpedMotion, nameof(frameHeader.AllowWarpedMotion), "No warped motion allowed."); - } - + // No warp motion for AVIF. writer.WriteBoolean(frameHeader.UseReducedTransformSet); // No global motion for AVIF. - if (sequenceHeader.AreFilmGrainingParametersPresent && (frameHeader.ShowFrame || frameHeader.ShowableFrame)) - { - WriteFilmGrainFilterParameters(ref writer, frameHeader.FilmGrainParameters); - } - } - - private static bool IsSuperResolutionUnscaled(ObuFrameSize frameSize) - => frameSize.FrameWidth == frameSize.SuperResolutionUpscaledWidth; - - private static bool FrameMightAllowWarpedMotion(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) - => false; // !frameHeader.ErrorResilientMode && !FrameIsIntraOnly(sequenceHeader) && scs->enable_warped_motion; - - private static void SetupPastIndependence(ObuFrameHeader frameInfo) - { - // TODO: Initialize the loop filter parameters. + WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader); } private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; - private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) - { - if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) - { - int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; - int qIndex = baseQIndex + data; - return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ); - } - else - { - return baseQIndex; - } - } - private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) { int startBitPosition = writer.BitPosition; @@ -557,7 +532,7 @@ private static void WriteTileData(ref Av1BitStreamWriter writer, ObuTileGroupHea private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) { - bool isCoded = deltaQ == 0; + bool isCoded = deltaQ != 0; writer.WriteBoolean(isCoded); if (isCoded) { @@ -606,15 +581,14 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]); if (planesCount > 1) { - bool areUvDeltaDifferent = false; if (colorInfo.HasSeparateUvDelta) { - writer.WriteBoolean(colorInfo.HasSeparateUvDelta); + writer.WriteBoolean(quantParams.HasSeparateUvDelta); } WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.U]); WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.U]); - if (areUvDeltaDifferent) + if (quantParams.HasSeparateUvDelta) { WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.V]); WriteDeltaQ(ref writer, quantParams.DeltaQAc[(int)Av1Plane.V]); @@ -641,14 +615,37 @@ private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, O private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - _ = writer; - _ = sequenceHeader; - _ = frameInfo; - _ = planesCount; + if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) + { + return; + } + + writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevel[0], 6); + writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevel[1], 6); + if (sequenceHeader.ColorConfig.PlaneCount > 1) + { + if (frameInfo.LoopFilterParameters.FilterLevel[0] > 0 || frameInfo.LoopFilterParameters.FilterLevel[1] > 0) + { + writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevelU, 6); + writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevelV, 6); + } + } - // TODO: Parse more stuff. + writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.SharpnessLevel, 3); + writer.WriteBoolean(frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled); + if (frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled) + { + writer.WriteBoolean(frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate); + if (frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate) + { + throw new NotImplementedException("Reference update of loop filter not supported yet."); + } + } } + /// + /// 5.9.21. TX mode syntax. + /// private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) { if (!frameInfo.CodedLossless) @@ -659,12 +656,37 @@ private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHe private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { - _ = writer; - _ = sequenceHeader; - _ = frameInfo; - _ = planesCount; + if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) + { + return; + } + + for (int i = 0; i < planesCount; i++) + { + writer.WriteLiteral((uint)frameInfo.LoopRestorationParameters.Items[i].Type, 2); + } - // TODO: Parse more stuff. + if (frameInfo.LoopRestorationParameters.UsesLoopRestoration) + { + uint unitShift = (uint)frameInfo.LoopRestorationParameters.UnitShift; + if (sequenceHeader.Use128x128Superblock) + { + writer.WriteLiteral(unitShift - 1, 1); + } + else + { + writer.WriteLiteral(unitShift & 0x01, 1); + if (unitShift > 0) + { + writer.WriteLiteral(unitShift - 1, 1); + } + } + + if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration) + { + writer.WriteLiteral((uint)frameInfo.LoopRestorationParameters.UVShift, 1); + } + } } private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) @@ -674,7 +696,10 @@ private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequen _ = frameInfo; _ = planesCount; - // TODO: Parse more stuff. + if (!frameInfo.CodedLossless && !frameInfo.AllowIntraBlockCopy && sequenceHeader.EnableCdef) + { + throw new NotImplementedException("Didn't implment writing CDF yet."); + } } private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) @@ -703,11 +728,108 @@ private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, bool Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); } - private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuFilmGrainParameters filmGrainInfo) + private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - _ = writer; - _ = filmGrainInfo; + ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters; + if (!sequenceHeader.AreFilmGrainingParametersPresent && (!frameHeader.ShowFrame && !frameHeader.ShowableFrame)) + { + return; + } + + writer.WriteBoolean(grainParams.ApplyGrain); + if (!grainParams.ApplyGrain) + { + return; + } + + writer.WriteLiteral(grainParams.GrainSeed, 16); + writer.WriteLiteral(grainParams.NumYPoints, 4); + Guard.NotNull(grainParams.PointYValue); + Guard.NotNull(grainParams.PointYScaling); + for (int i = 0; i < grainParams.NumYPoints; i++) + { + writer.WriteLiteral(grainParams.PointYValue[i], 8); + writer.WriteLiteral(grainParams.PointYScaling[i], 8); + } + + if (!sequenceHeader.ColorConfig.IsMonochrome) + { + writer.WriteBoolean(grainParams.ChromaScalingFromLuma); + } + + if (!sequenceHeader.ColorConfig.IsMonochrome && + !grainParams.ChromaScalingFromLuma && + (!sequenceHeader.ColorConfig.SubSamplingX || !sequenceHeader.ColorConfig.SubSamplingY || grainParams.NumYPoints != 0)) + { + writer.WriteLiteral(grainParams.NumCbPoints, 4); + Guard.NotNull(grainParams.PointCbValue); + Guard.NotNull(grainParams.PointCbScaling); + for (int i = 0; i < grainParams.NumCbPoints; i++) + { + writer.WriteLiteral(grainParams.PointCbValue[i], 8); + writer.WriteLiteral(grainParams.PointCbScaling[i], 8); + } + + writer.WriteLiteral(grainParams.NumCrPoints, 4); + Guard.NotNull(grainParams.PointCrValue); + Guard.NotNull(grainParams.PointCrScaling); + for (int i = 0; i < grainParams.NumCbPoints; i++) + { + writer.WriteLiteral(grainParams.PointCrValue[i], 8); + writer.WriteLiteral(grainParams.PointCrScaling[i], 8); + } + } + + writer.WriteLiteral(grainParams.GrainScalingMinus8, 2); + writer.WriteLiteral(grainParams.ArCoeffLag, 2); + uint numPosLuma = 2 * grainParams.ArCoeffLag * (grainParams.ArCoeffLag + 1); + + uint numPosChroma = 0; + if (grainParams.NumYPoints != 0) + { + numPosChroma = numPosLuma + 1; + Guard.NotNull(grainParams.ArCoeffsYPlus128); + for (int i = 0; i < numPosLuma; i++) + { + writer.WriteLiteral(grainParams.ArCoeffsYPlus128[i], 8); + } + } + + if (grainParams.ChromaScalingFromLuma || grainParams.NumCbPoints != 0) + { + Guard.NotNull(grainParams.ArCoeffsCbPlus128); + for (int i = 0; i < numPosChroma; i++) + { + writer.WriteLiteral(grainParams.ArCoeffsCbPlus128[i], 8); + } + } + + if (grainParams.ChromaScalingFromLuma || grainParams.NumCrPoints != 0) + { + Guard.NotNull(grainParams.ArCoeffsCrPlus128); + for (int i = 0; i < numPosChroma; i++) + { + writer.WriteLiteral(grainParams.ArCoeffsCrPlus128[i], 8); + } + } + + writer.WriteLiteral(grainParams.ArCoeffShiftMinus6, 2); + writer.WriteLiteral(grainParams.GrainScaleShift, 2); + if (grainParams.NumCbPoints != 0) + { + writer.WriteLiteral(grainParams.CbMult, 8); + writer.WriteLiteral(grainParams.CbLumaMult, 8); + writer.WriteLiteral(grainParams.CbOffset, 9); + } + + if (grainParams.NumCrPoints != 0) + { + writer.WriteLiteral(grainParams.CrMult, 8); + writer.WriteLiteral(grainParams.CrLumaMult, 8); + writer.WriteLiteral(grainParams.CrOffset, 9); + } - // Film grain filter not supported yet + writer.WriteBoolean(grainParams.OverlapFlag); + writer.WriteBoolean(grainParams.ClipToRestrictedRange); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index a27335a6f2..656cbc6055 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -129,7 +129,7 @@ private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlock int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int plane = 0; plane < planesCount; plane++) { - if (this.FrameInfo.LoopRestorationParameters[plane].Type != ObuRestorationType.None) + if (this.FrameInfo.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None) { // TODO: Implement. throw new NotImplementedException("No loop restoration filter support."); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 64f752e5a9..4dfd72d744 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -14,8 +14,7 @@ public class ObuFrameHeaderTests // TODO: Check with libgav1 test code. private static readonly byte[] KeyFrameHeaderBitStream = - // libgav1 expects this: [0x32, 0x05, 0x10, 0x00]; - [0x32, 0x05, 0x20, 0x04]; + [0x32, 0x07, 0x10, 0x00]; // Bits Syntax element Value // 1 obu_forbidden_bit 0 @@ -55,6 +54,7 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) /* [Theory] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d, 0x0128)] + [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc, 0x0114)] public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize, int tileOffset) { @@ -77,7 +77,8 @@ public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, obuWriter.WriteAll(encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); // Assert - Assert.Equal(span, encoded.ToArray()); + byte[] encodedArray = encoded.ToArray(); + Assert.Equal(span, encodedArray); } */ From 729f35194552a8dc7c82fdb3ac48957df72644a1 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 21:07:05 +0200 Subject: [PATCH 148/190] Obu bitstream writer writes to Span iso Stream --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 107 ++++++++++++------ .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 38 ++++--- src/ImageSharp/Memory/AutoExpandingMemory.cs | 9 +- .../Formats/Heif/Av1/Av1BitStreamTests.cs | 40 +++---- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 8 +- 5 files changed, 129 insertions(+), 73 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index 0b447f832d..affa7f9b57 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -1,39 +1,67 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Memory; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; -internal ref struct Av1BitStreamWriter(Stream stream) +internal ref struct Av1BitStreamWriter { private const int WordSize = 8; - private readonly Stream stream = stream; + private readonly AutoExpandingMemory memory; + private Span span; + private int capacityTrigger; private byte buffer = 0; - private int bitOffset = 0; - public readonly int BitPosition => (int)(this.stream.Position * WordSize) + this.bitOffset; + public Av1BitStreamWriter(AutoExpandingMemory memory) + { + this.memory = memory; + this.span = memory.GetEntireSpan(); + this.capacityTrigger = memory.Capacity - 1; + } + + public int BitPosition { get; private set; } = 0; + + public readonly int Capacity => this.memory.Capacity; - public readonly int Length => (int)this.stream.Length; + public static int GetLittleEndianBytes128(uint value, Span span) + { + if (value < 0x80U) + { + span[0] = (byte)value; + return 1; + } + else if (value < 0x8000U) + { + span[0] = (byte)((value & 0x7fU) | 0x80U); + span[1] = (byte)((value >> 7) & 0xff); + return 2; + } + else + { + throw new NotImplementedException("No such large values yet."); + } + } public void Skip(int bitCount) { - this.bitOffset += bitCount; - while (this.bitOffset >= WordSize) + this.BitPosition += bitCount; + while (this.BitPosition >= WordSize) { - this.bitOffset -= WordSize; - this.stream.WriteByte(this.buffer); - this.buffer = 0; + this.BitPosition -= WordSize; + this.WriteBuffer(); } } public void Flush() { - if (Av1Math.Modulus8(this.bitOffset) != 0) + if (Av1Math.Modulus8(this.BitPosition) != 0) { // Flush a partial byte also. - this.stream.WriteByte(this.buffer); + this.WriteBuffer(); } - this.bitOffset = 0; + this.BitPosition = 0; } public void WriteLiteral(uint value, int bitCount) @@ -64,19 +92,8 @@ public void WriteSignedFromUnsigned(int signedValue, int n) public void WriteLittleEndianBytes128(uint value) { - if (value < 128) - { - this.WriteLiteral(value, 8); - } - else if (value < 0x8000U) - { - this.WriteLiteral((value & 0x7FU) | 0x80U, 8); - this.WriteLiteral(value >> 7, 8); - } - else - { - throw new NotImplementedException("No such large values yet."); - } + int bytesWritten = GetLittleEndianBytes128(value, this.span.Slice(this.BitPosition >> 3)); + this.BitPosition += bytesWritten << 3; } internal void WriteNonSymmetric(uint value, uint numberOfSymbols) @@ -104,14 +121,14 @@ internal void WriteNonSymmetric(uint value, uint numberOfSymbols) private void WriteBit(byte value) { - this.buffer = (byte)(((value << (7 - this.bitOffset)) & 0xff) | this.buffer); - this.bitOffset++; - if (this.bitOffset == WordSize) + int bit = this.BitPosition & 0x07; + this.buffer = (byte)(((value << (7 - bit)) & 0xff) | this.buffer); + if (bit == 7) { - this.stream.WriteByte(this.buffer); - this.buffer = 0; - this.bitOffset = 0; + this.WriteBuffer(); } + + this.BitPosition++; } public void WriteLittleEndian(uint value, int n) @@ -131,7 +148,29 @@ internal void WriteBlob(Span tileData) { DebugGuard.IsTrue(Av1Math.Modulus8(this.BitPosition) == 0, "Writing of Tile Data only allowed on byte alignment"); - this.stream.Write(tileData); - this.bitOffset += tileData.Length << 3; + int wordPosition = this.BitPosition >> 3; + if (this.span.Length <= wordPosition + tileData.Length) + { + this.memory.GetSpan(wordPosition + tileData.Length); + this.span = this.memory.GetEntireSpan(); + } + + tileData.CopyTo(this.span[wordPosition..]); + this.BitPosition += tileData.Length << 3; + } + + private void WriteBuffer() + { + int wordPosition = Av1Math.DivideBy8Floor(this.BitPosition); + if (wordPosition > this.capacityTrigger) + { + // Expand the memory allocation. + this.memory.GetSpan(wordPosition + 1); + this.span = this.memory.GetEntireSpan(); + this.capacityTrigger = this.span.Length - 1; + } + + this.span[wordPosition] = this.buffer; + this.buffer = 0; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index b48061632a..58abc953e8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -1,9 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Buffers; -using System.Reflection.PortableExecutable; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -15,23 +14,24 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter) + public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter) { - MemoryStream bufferStream = new(2000); - Av1BitStreamWriter writer = new(bufferStream); - WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, [], 0); + // TODO: Determine inital size dynamically + int initialBufferSize = 2000; + AutoExpandingMemory buffer = new(configuration, initialBufferSize); + Av1BitStreamWriter writer = new(buffer); + WriteObuHeaderAndSize(stream, ObuType.TemporalDelimiter, []); if (sequenceHeader != null) { WriteSequenceHeader(ref writer, sequenceHeader); int bytesWritten = (writer.BitPosition + 7) >> 3; writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, bufferStream.GetBuffer(), bytesWritten); + WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, buffer.GetSpan(bytesWritten)); } if (frameInfo != null && sequenceHeader != null) { - bufferStream.Position = 0; this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); if (frameInfo.TilesInfo != null) { @@ -40,7 +40,7 @@ public void WriteAll(Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHe int bytesWritten = (writer.BitPosition + 7) >> 3; writer.Flush(); - WriteObuHeaderAndSize(stream, ObuType.Frame, bufferStream.GetBuffer(), bytesWritten); + WriteObuHeaderAndSize(stream, ObuType.Frame, buffer.GetSpan(bytesWritten)); } } @@ -53,15 +53,25 @@ private static void WriteObuHeader(ref Av1BitStreamWriter writer, ObuType type) writer.WriteBoolean(false); // Reserved } + private static byte WriteObuHeader(ObuType type) => + + // 0: Forbidden bit + // 1: Type, 4 + // 5: Extension (false) + // 6: HasSize (true) + // 7: Reserved (false) + (byte)(((byte)type << 3) | 0x02); + /// /// Read OBU header and size. /// - private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload, int length) + private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload) { - Av1BitStreamWriter writer = new(stream); - WriteObuHeader(ref writer, type); - writer.WriteLittleEndianBytes128((uint)length); - stream.Write(payload, 0, length); + stream.WriteByte(WriteObuHeader(type)); + Span lengthBytes = stackalloc byte[2]; + int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes); + stream.Write(lengthBytes, 0, lengthLength); + stream.Write(payload); } /// diff --git a/src/ImageSharp/Memory/AutoExpandingMemory.cs b/src/ImageSharp/Memory/AutoExpandingMemory.cs index 7d118e05bc..e23fb3e5f8 100644 --- a/src/ImageSharp/Memory/AutoExpandingMemory.cs +++ b/src/ImageSharp/Memory/AutoExpandingMemory.cs @@ -23,9 +23,11 @@ public AutoExpandingMemory(Configuration configuration, int initialSize) this.allocation = this.configuration.MemoryAllocator.Allocate(initialSize); } + public int Capacity => this.allocation.Memory.Length; + public Span GetSpan(int requestedSize) { - Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize)); + Guard.MustBeGreaterThanOrEqualTo(requestedSize, 0, nameof(requestedSize)); this.EnsureCapacity(requestedSize); return this.allocation.Memory.Span[..requestedSize]; @@ -34,12 +36,15 @@ public Span GetSpan(int requestedSize) public Span GetSpan(int offset, int requestedSize) { Guard.MustBeGreaterThanOrEqualTo(offset, 0, nameof(offset)); - Guard.MustBeGreaterThan(requestedSize, 0, nameof(requestedSize)); + Guard.MustBeGreaterThanOrEqualTo(requestedSize, 0, nameof(requestedSize)); this.EnsureCapacity(offset + requestedSize); return this.allocation.Memory.Span.Slice(offset, requestedSize); } + public Span GetEntireSpan() + => this.GetSpan(this.Capacity); + public void Dispose() => this.allocation.Dispose(); private void EnsureCapacity(int requestedSize) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs index ec9d1114da..94d442b12d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1BitStreamTests.cs @@ -3,6 +3,7 @@ using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -73,7 +74,7 @@ public void ReadLiteral32BitsWithMsbSet() [InlineData(new bool[] { false, true, false, true })] public void WriteAsBoolean(bool[] booleans) { - using MemoryStream stream = new(8); + using AutoExpandingMemory stream = new(Configuration.Default, 8); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < booleans.Length; i++) { @@ -83,7 +84,7 @@ public void WriteAsBoolean(bool[] booleans) writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); bool[] actual = new bool[booleans.Length]; for (int i = 0; i < booleans.Length; i++) { @@ -94,19 +95,19 @@ public void WriteAsBoolean(bool[] booleans) } [Theory] - [InlineData(6, 4)] - [InlineData(42, 8)] - [InlineData(52, 8)] + //[InlineData(6, 4)] + //[InlineData(42, 8)] + //[InlineData(52, 8)] [InlineData(4050, 16)] public void WriteAsLiteral(uint value, int bitCount) { - using MemoryStream stream = new(8); + using AutoExpandingMemory stream = new(Configuration.Default, 8); Av1BitStreamWriter writer = new(stream); writer.WriteLiteral(value, bitCount); writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); uint actual = reader.ReadLiteral(bitCount); Assert.Equal(value, actual); } @@ -122,7 +123,7 @@ public void WriteAsLiteral(uint value, int bitCount) public void ReadLiteralRainbowArray(int bitCount) { uint[] values = Enumerable.Range(0, (1 << bitCount) - 1).Select(i => (uint)i).ToArray(); - using MemoryStream stream = new(280); + using AutoExpandingMemory stream = new(Configuration.Default, 280); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -132,7 +133,7 @@ public void ReadLiteralRainbowArray(int bitCount) writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); uint[] actuals = new uint[values.Length]; for (int i = 0; i < values.Length; i++) { @@ -151,7 +152,7 @@ public void ReadLiteralRainbowArray(int bitCount) public void ReadWriteAsLiteralArray(int bitCount, uint val1, uint val2, uint val3, uint val4) { uint[] values = [val1, val2, val3, val4]; - using MemoryStream stream = new(80); + using AutoExpandingMemory stream = new(Configuration.Default, 80); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -161,7 +162,7 @@ public void ReadWriteAsLiteralArray(int bitCount, uint val1, uint val2, uint val writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); for (int i = 0; i < values.Length; i++) { uint actual = reader.ReadLiteral(bitCount); @@ -180,7 +181,7 @@ public void ReadWriteAsLiteralArray(int bitCount, uint val1, uint val2, uint val public void ReadWriteAsNonSymmetricArray(uint numberOfSymbols, uint val1, uint val2, uint val3, uint val4) { uint[] values = [val1, val2, val3, val4]; - using MemoryStream stream = new(80); + using AutoExpandingMemory stream = new(Configuration.Default, 80); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -190,7 +191,7 @@ public void ReadWriteAsNonSymmetricArray(uint numberOfSymbols, uint val1, uint v writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); uint[] actuals = new uint[4]; for (int i = 0; i < values.Length; i++) { @@ -213,7 +214,7 @@ public void ReadSignedRainbowArray(int bitCount) { int maxValue = (1 << (bitCount - 1)) - 1; int[] values = Enumerable.Range(-maxValue, maxValue).ToArray(); - using MemoryStream stream = new(280); + using AutoExpandingMemory stream = new(Configuration.Default, 280); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -223,7 +224,7 @@ public void ReadSignedRainbowArray(int bitCount) writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); int[] actuals = new int[values.Length]; for (int i = 0; i < values.Length; i++) { @@ -294,7 +295,7 @@ public void ReadUnsignedVariableLength(byte[] buffer, uint expected, int expecte public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int val4) { int[] values = [val1, val2, val3, val4]; - using MemoryStream stream = new(80); + using AutoExpandingMemory stream = new(Configuration.Default, 80); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -304,7 +305,7 @@ public void ReadWriteSignedArray(int bitCount, int val1, int val2, int val3, int writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetEntireSpan()); int[] actuals = new int[4]; for (int i = 0; i < values.Length; i++) { @@ -344,7 +345,8 @@ public void ReadLittleEndianBytes128(byte[] buffer, ulong expected, int expected public void ReadWriteLittleEndianBytes128Array(uint val0, uint val1, uint val2, uint val3, uint val4) { uint[] values = [val0, val1, val2, val3, val4]; - using MemoryStream stream = new(80); + int bufferSize = 80; + using AutoExpandingMemory stream = new(Configuration.Default, bufferSize); Av1BitStreamWriter writer = new(stream); for (int i = 0; i < values.Length; i++) { @@ -354,7 +356,7 @@ public void ReadWriteLittleEndianBytes128Array(uint val0, uint val1, uint val2, writer.Flush(); // Read the written value back. - Av1BitStreamReader reader = new(stream.GetBuffer()); + Av1BitStreamReader reader = new(stream.GetSpan(bufferSize)); uint[] actuals = new uint[5]; for (int i = 0; i < values.Length; i++) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 4dfd72d744..0c523442b6 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -103,7 +103,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b // Act 2 ObuWriter obuWriter = new(); - obuWriter.WriteAll(encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, tileStub); + obuWriter.WriteAll(Configuration.Default, encoded, obuReader1.SequenceHeader, obuReader1.FrameHeader, tileStub); // Assign 2 Span encodedBuffer = encoded.ToArray(); @@ -181,7 +181,7 @@ public void WriteTemporalDelimiter() ObuWriter obuWriter = new(); // Act - obuWriter.WriteAll(stream, null, null, null); + obuWriter.WriteAll(Configuration.Default, stream, null, null, null); byte[] actual = stream.GetBuffer(); // Assert @@ -197,7 +197,7 @@ public void WriteSequenceHeader() ObuWriter obuWriter = new(); // Act - obuWriter.WriteAll(stream, input, null, null); + obuWriter.WriteAll(Configuration.Default, stream, input, null, null); byte[] buffer = stream.GetBuffer(); // Assert @@ -219,7 +219,7 @@ public void WriteFrameHeader() ObuWriter obuWriter = new(); // Act - obuWriter.WriteAll(stream, sequenceInput, frameInput, tileStub); + obuWriter.WriteAll(Configuration.Default, stream, sequenceInput, frameInput, tileStub); byte[] buffer = stream.GetBuffer(); // Assert From 2bac577cf7280d4bf97fd04fab083a5e1e1a3dae Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 21:31:04 +0200 Subject: [PATCH 149/190] Fix AutoExpandingMemoryTest --- tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs b/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs index 24ed904c0d..5b13889c37 100644 --- a/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs +++ b/tests/ImageSharp.Tests/Memory/AutoExpandingMemoryTests.cs @@ -42,7 +42,6 @@ public void KeepDataWhileExpanding(int initialCapacity, int requestedCapacity) [Theory] [InlineData(1, -1)] - [InlineData(1, 0)] [InlineData(-2, 1)] [InlineData(-2, 0)] public void Guards(int initialCapacity, int requestedCapacity) => From 0b1639b82f189c0a44c7f8278507a852f595d60f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 5 Aug 2024 19:42:46 +0200 Subject: [PATCH 150/190] Sync method arguments --- .../Av1/OpenBitstreamUnit/ObuFrameHeader.cs | 2 + .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 304 +++++++++--------- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 115 ++++--- 3 files changed, 219 insertions(+), 202 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs index 3dc6788e6e..adc759267f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuFrameHeader.cs @@ -53,6 +53,8 @@ internal class ObuFrameHeader public ObuDeltaParameters DeltaQParameters { get; set; } = new ObuDeltaParameters(); + public bool IsIntra => this.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame; + internal ObuFrameSize FrameSize { get; set; } = new ObuFrameSize(); internal int ModeInfoColumnCount { get; set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 7ea7db3133..1042effdfe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -846,13 +846,12 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob /// /// 5.9.2. Uncompressed header syntax. /// - private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, int planesCount) + private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; - ObuFrameHeader frameInfo = this.FrameHeader!; + ObuFrameHeader frameHeader = this.FrameHeader!; int idLength = 0; uint previousFrameId = 0; - bool isIntraFrame = false; bool frameSizeOverrideFlag = false; if (sequenceHeader.IsFrameIdNumbersPresent) { @@ -862,115 +861,113 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade if (sequenceHeader.IsReducedStillPictureHeader) { - frameInfo.ShowExistingFrame = false; - frameInfo.FrameType = ObuFrameType.KeyFrame; - isIntraFrame = true; - frameInfo.ShowFrame = true; - frameInfo.ShowableFrame = false; - frameInfo.ErrorResilientMode = true; + frameHeader.ShowExistingFrame = false; + frameHeader.FrameType = ObuFrameType.KeyFrame; + frameHeader.ShowFrame = true; + frameHeader.ShowableFrame = false; + frameHeader.ErrorResilientMode = true; } else { - frameInfo.ShowExistingFrame = reader.ReadBoolean(); - if (frameInfo.ShowExistingFrame) + frameHeader.ShowExistingFrame = reader.ReadBoolean(); + if (frameHeader.ShowExistingFrame) { - frameInfo.FrameToShowMapIdx = reader.ReadLiteral(3); + frameHeader.FrameToShowMapIdx = reader.ReadLiteral(3); if (sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) { // 5.9.31. Temporal point info syntax. - frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); + frameHeader.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); } if (sequenceHeader.IsFrameIdNumbersPresent) { - frameInfo.DisplayFrameId = reader.ReadLiteral(idLength); + frameHeader.DisplayFrameId = reader.ReadLiteral(idLength); } // TODO: This is incomplete here, not sure how we can display an already decoded frame here or if this is really relevent for still pictures. throw new NotImplementedException("ShowExistingFrame is not yet implemented"); } - frameInfo.FrameType = (ObuFrameType)reader.ReadLiteral(2); - isIntraFrame = frameInfo.FrameType is ObuFrameType.IntraOnlyFrame or ObuFrameType.KeyFrame; - frameInfo.ShowFrame = reader.ReadBoolean(); + frameHeader.FrameType = (ObuFrameType)reader.ReadLiteral(2); + frameHeader.ShowFrame = reader.ReadBoolean(); - if (frameInfo.ShowFrame && !sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) + if (frameHeader.ShowFrame && !sequenceHeader.DecoderModelInfoPresentFlag && sequenceHeader.TimingInfo?.EqualPictureInterval == false) { // 5.9.31. Temporal point info syntax. - frameInfo.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); + frameHeader.FramePresentationTime = reader.ReadLiteral((int)sequenceHeader!.DecoderModelInfo!.FramePresentationTimeLength); } - if (frameInfo.ShowFrame) + if (frameHeader.ShowFrame) { - frameInfo.ShowableFrame = frameInfo.FrameType != ObuFrameType.KeyFrame; + frameHeader.ShowableFrame = frameHeader.FrameType != ObuFrameType.KeyFrame; } else { - frameInfo.ShowableFrame = reader.ReadBoolean(); + frameHeader.ShowableFrame = reader.ReadBoolean(); } - if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + if (frameHeader.FrameType == ObuFrameType.SwitchFrame || (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame)) { - frameInfo.ErrorResilientMode = true; + frameHeader.ErrorResilientMode = true; } else { - frameInfo.ErrorResilientMode = reader.ReadBoolean(); + frameHeader.ErrorResilientMode = reader.ReadBoolean(); } } - if (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame) + if (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame) { - frameInfo.ReferenceValid = new bool[Av1Constants.ReferenceFrameCount]; - frameInfo.ReferenceOrderHint = new bool[Av1Constants.ReferenceFrameCount]; - Array.Fill(frameInfo.ReferenceValid, false); - Array.Fill(frameInfo.ReferenceOrderHint, false); + frameHeader.ReferenceValid = new bool[Av1Constants.ReferenceFrameCount]; + frameHeader.ReferenceOrderHint = new bool[Av1Constants.ReferenceFrameCount]; + Array.Fill(frameHeader.ReferenceValid, false); + Array.Fill(frameHeader.ReferenceOrderHint, false); } - frameInfo.DisableCdfUpdate = reader.ReadBoolean(); - frameInfo.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2; - if (frameInfo.AllowScreenContentTools) + frameHeader.DisableCdfUpdate = reader.ReadBoolean(); + frameHeader.AllowScreenContentTools = sequenceHeader.ForceScreenContentTools == 2; + if (frameHeader.AllowScreenContentTools) { - frameInfo.AllowScreenContentTools = reader.ReadBoolean(); + frameHeader.AllowScreenContentTools = reader.ReadBoolean(); } - if (frameInfo.AllowScreenContentTools) + if (frameHeader.AllowScreenContentTools) { if (sequenceHeader.ForceIntegerMotionVector == 1) { - frameInfo.ForceIntegerMotionVector = reader.ReadBoolean(); + frameHeader.ForceIntegerMotionVector = reader.ReadBoolean(); } else { - frameInfo.ForceIntegerMotionVector = sequenceHeader.ForceIntegerMotionVector != 0; + frameHeader.ForceIntegerMotionVector = sequenceHeader.ForceIntegerMotionVector != 0; } } else { - frameInfo.ForceIntegerMotionVector = false; + frameHeader.ForceIntegerMotionVector = false; } - if (isIntraFrame) + if (frameHeader.IsIntra) { - frameInfo.ForceIntegerMotionVector = true; + frameHeader.ForceIntegerMotionVector = true; } - bool havePreviousFrameId = !(frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame); + bool havePreviousFrameId = !(frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame); if (havePreviousFrameId) { - previousFrameId = frameInfo.CurrentFrameId; + previousFrameId = frameHeader.CurrentFrameId; } if (sequenceHeader.IsFrameIdNumbersPresent) { - frameInfo.CurrentFrameId = reader.ReadLiteral(idLength); + frameHeader.CurrentFrameId = reader.ReadLiteral(idLength); if (havePreviousFrameId) { - uint diffFrameId = (frameInfo.CurrentFrameId > previousFrameId) ? - frameInfo.CurrentFrameId - previousFrameId : - (uint)((1 << idLength) + (int)frameInfo.CurrentFrameId - previousFrameId); - if (frameInfo.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) + uint diffFrameId = (frameHeader.CurrentFrameId > previousFrameId) ? + frameHeader.CurrentFrameId - previousFrameId : + (uint)((1 << idLength) + (int)frameHeader.CurrentFrameId - previousFrameId); + if (frameHeader.CurrentFrameId == previousFrameId || diffFrameId >= 1 << (idLength - 1)) { throw new ImageFormatException("Current frame ID cannot be same as previous Frame ID"); } @@ -979,27 +976,27 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade int diffLength = sequenceHeader.DeltaFrameIdLength; for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { - if (frameInfo.CurrentFrameId > (1U << diffLength)) + if (frameHeader.CurrentFrameId > (1U << diffLength)) { - if ((frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId) || - frameInfo.ReferenceFrameIndex[i] > (frameInfo.CurrentFrameId - (1 - diffLength))) + if ((frameHeader.ReferenceFrameIndex[i] > frameHeader.CurrentFrameId) || + frameHeader.ReferenceFrameIndex[i] > (frameHeader.CurrentFrameId - (1 - diffLength))) { - frameInfo.ReferenceValid[i] = false; + frameHeader.ReferenceValid[i] = false; } } - else if (frameInfo.ReferenceFrameIndex[i] > frameInfo.CurrentFrameId && - frameInfo.ReferenceFrameIndex[i] < ((1 << idLength) + (frameInfo.CurrentFrameId - (1 << diffLength)))) + else if (frameHeader.ReferenceFrameIndex[i] > frameHeader.CurrentFrameId && + frameHeader.ReferenceFrameIndex[i] < ((1 << idLength) + (frameHeader.CurrentFrameId - (1 << diffLength)))) { - frameInfo.ReferenceValid[i] = false; + frameHeader.ReferenceValid[i] = false; } } } else { - frameInfo.CurrentFrameId = 0; + frameHeader.CurrentFrameId = 0; } - if (frameInfo.FrameType == ObuFrameType.SwitchFrame) + if (frameHeader.FrameType == ObuFrameType.SwitchFrame) { frameSizeOverrideFlag = true; } @@ -1012,84 +1009,85 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade frameSizeOverrideFlag = reader.ReadBoolean(); } - frameInfo.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); + frameHeader.OrderHint = reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); - if (isIntraFrame || frameInfo.ErrorResilientMode) + if (frameHeader.IsIntra || frameHeader.ErrorResilientMode) { - frameInfo.PrimaryReferenceFrame = Av1Constants.PrimaryReferenceFrameNone; + frameHeader.PrimaryReferenceFrame = Av1Constants.PrimaryReferenceFrameNone; } else { - frameInfo.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits); + frameHeader.PrimaryReferenceFrame = reader.ReadLiteral(Av1Constants.PimaryReferenceBits); } // Skipping, as no decoder info model present - frameInfo.AllowHighPrecisionMotionVector = false; - frameInfo.UseReferenceFrameMotionVectors = false; - frameInfo.AllowIntraBlockCopy = false; - if (frameInfo.FrameType == ObuFrameType.SwitchFrame || (frameInfo.FrameType == ObuFrameType.KeyFrame && frameInfo.ShowFrame)) + frameHeader.AllowHighPrecisionMotionVector = false; + frameHeader.UseReferenceFrameMotionVectors = false; + frameHeader.AllowIntraBlockCopy = false; + if (frameHeader.FrameType == ObuFrameType.SwitchFrame || (frameHeader.FrameType == ObuFrameType.KeyFrame && frameHeader.ShowFrame)) { - frameInfo.RefreshFrameFlags = 0xFFU; + frameHeader.RefreshFrameFlags = 0xFFU; } else { - frameInfo.RefreshFrameFlags = reader.ReadLiteral(8); + frameHeader.RefreshFrameFlags = reader.ReadLiteral(8); } - if (frameInfo.FrameType == ObuFrameType.IntraOnlyFrame) + if (frameHeader.FrameType == ObuFrameType.IntraOnlyFrame) { - DebugGuard.IsTrue(frameInfo.RefreshFrameFlags != 0xFFU, nameof(frameInfo.RefreshFrameFlags)); + DebugGuard.IsTrue(frameHeader.RefreshFrameFlags != 0xFFU, nameof(frameHeader.RefreshFrameFlags)); } - if (!isIntraFrame || (frameInfo.RefreshFrameFlags != 0xFFU)) + if (!frameHeader.IsIntra || (frameHeader.RefreshFrameFlags != 0xFFU)) { - if (frameInfo.ErrorResilientMode && sequenceHeader.OrderHintInfo != null) + if (frameHeader.ErrorResilientMode && sequenceHeader.OrderHintInfo != null) { for (int i = 0; i < Av1Constants.ReferenceFrameCount; i++) { int referenceOrderHint = (int)reader.ReadLiteral(sequenceHeader.OrderHintInfo.OrderHintBits); - if (referenceOrderHint != (frameInfo.ReferenceOrderHint[i] ? 1U : 0U)) + if (referenceOrderHint != (frameHeader.ReferenceOrderHint[i] ? 1U : 0U)) { - frameInfo.ReferenceValid[i] = false; + frameHeader.ReferenceValid[i] = false; } } } } - if (isIntraFrame) + if (frameHeader.IsIntra) { this.ReadFrameSize(ref reader, frameSizeOverrideFlag); this.ReadRenderSize(ref reader); - if (frameInfo.AllowScreenContentTools && frameInfo.FrameSize.RenderWidth != 0) + if (frameHeader.AllowScreenContentTools && frameHeader.FrameSize.RenderWidth != 0) { - if (frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth) + if (frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth) { - frameInfo.AllowIntraBlockCopy = reader.ReadBoolean(); + frameHeader.AllowIntraBlockCopy = reader.ReadBoolean(); } } } else { // Single image is always Intra. + throw new InvalidImageContentException("AVIF image can only contain INTRA frames."); } // SetupFrameBufferReferences(sequenceHeader, frameInfo); // CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); // SetupFrameSignBias(sequenceHeader, frameInfo); - if (sequenceHeader.IsReducedStillPictureHeader || frameInfo.DisableCdfUpdate) + if (sequenceHeader.IsReducedStillPictureHeader || frameHeader.DisableCdfUpdate) { - frameInfo.DisableFrameEndUpdateCdf = true; + frameHeader.DisableFrameEndUpdateCdf = true; } else { - frameInfo.DisableFrameEndUpdateCdf = reader.ReadBoolean(); + frameHeader.DisableFrameEndUpdateCdf = reader.ReadBoolean(); } - if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) + if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { // InitConCoefficientCdfs(); - SetupPastIndependence(frameInfo); + SetupPastIndependence(frameHeader); } else { @@ -1098,21 +1096,21 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade throw new NotImplementedException(); } - if (frameInfo.UseReferenceFrameMotionVectors) + if (frameHeader.UseReferenceFrameMotionVectors) { // MotionFieldEstimations(); throw new NotImplementedException(); } // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); - frameInfo.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameInfo); - ReadQuantizationParameters(ref reader, frameInfo.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); - ReadSegmentationParameters(ref reader, sequenceHeader, frameInfo, planesCount); - ReadFrameDeltaQParameters(ref reader, frameInfo); - ReadFrameDeltaLoopFilterParameters(ref reader, frameInfo); + frameHeader.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameHeader); + ReadQuantizationParameters(ref reader, sequenceHeader, frameHeader); + ReadSegmentationParameters(ref reader, frameHeader); + ReadFrameDeltaQParameters(ref reader, frameHeader); + ReadFrameDeltaLoopFilterParameters(ref reader, frameHeader); // SetupSegmentationDequantization(); - if (frameInfo.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) + if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); } @@ -1122,72 +1120,74 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader, ObuHeade throw new NotImplementedException(); } - int tilesCount = frameInfo.TilesInfo.TileColumnCount * frameInfo.TilesInfo.TileRowCount; - frameInfo.CodedLossless = true; - frameInfo.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount]; - frameInfo.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount]; - frameInfo.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount]; + int tilesCount = frameHeader.TilesInfo.TileColumnCount * frameHeader.TilesInfo.TileRowCount; + frameHeader.CodedLossless = true; + frameHeader.SegmentationParameters.QMLevel[0] = new int[Av1Constants.MaxSegmentCount]; + frameHeader.SegmentationParameters.QMLevel[1] = new int[Av1Constants.MaxSegmentCount]; + frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount]; for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) { - int qIndex = GetQIndex(frameInfo.SegmentationParameters, segmentId, frameInfo.QuantizationParameters.BaseQIndex); - frameInfo.QuantizationParameters.QIndex[segmentId] = qIndex; - frameInfo.LosslessArray[segmentId] = qIndex == 0 && - frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && - frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 && - frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 && - frameInfo.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 && - frameInfo.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0; - if (!frameInfo.LosslessArray[segmentId]) + int qIndex = GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex); + frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex; + frameHeader.LosslessArray[segmentId] = qIndex == 0 && + frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && + frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.U] == 0 && + frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.U] == 0 && + frameHeader.QuantizationParameters.DeltaQAc[(int)Av1Plane.V] == 0 && + frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.V] == 0; + if (!frameHeader.LosslessArray[segmentId]) { - frameInfo.CodedLossless = false; + frameHeader.CodedLossless = false; } - if (frameInfo.QuantizationParameters.IsUsingQMatrix) + if (frameHeader.QuantizationParameters.IsUsingQMatrix) { - if (frameInfo.LosslessArray[segmentId]) + if (frameHeader.LosslessArray[segmentId]) { - frameInfo.SegmentationParameters.QMLevel[0][segmentId] = 15; - frameInfo.SegmentationParameters.QMLevel[1][segmentId] = 15; - frameInfo.SegmentationParameters.QMLevel[2][segmentId] = 15; + frameHeader.SegmentationParameters.QMLevel[0][segmentId] = 15; + frameHeader.SegmentationParameters.QMLevel[1][segmentId] = 15; + frameHeader.SegmentationParameters.QMLevel[2][segmentId] = 15; } else { - frameInfo.SegmentationParameters.QMLevel[0][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; - frameInfo.SegmentationParameters.QMLevel[1][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.U]; - frameInfo.SegmentationParameters.QMLevel[2][segmentId] = frameInfo.QuantizationParameters.QMatrix[(int)Av1Plane.V]; + frameHeader.SegmentationParameters.QMLevel[0][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.Y]; + frameHeader.SegmentationParameters.QMLevel[1][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.U]; + frameHeader.SegmentationParameters.QMLevel[2][segmentId] = frameHeader.QuantizationParameters.QMatrix[(int)Av1Plane.V]; } } } - if (frameInfo.CodedLossless) + if (frameHeader.CodedLossless) { - DebugGuard.IsFalse(frameInfo.DeltaQParameters.IsPresent, nameof(frameInfo.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame."); + DebugGuard.IsFalse(frameHeader.DeltaQParameters.IsPresent, nameof(frameHeader.DeltaQParameters.IsPresent), "No Delta Q parameters are allowed for lossless frame."); } - frameInfo.AllLossless = frameInfo.CodedLossless && frameInfo.FrameSize.FrameWidth == frameInfo.FrameSize.SuperResolutionUpscaledWidth; - this.ReadLoopFilterParameters(ref reader, planesCount); - ReadCdefParameters(ref reader, sequenceHeader, frameInfo, planesCount); - ReadLoopRestorationParameters(ref reader, sequenceHeader, frameInfo, planesCount); - ReadTransformMode(ref reader, frameInfo); + frameHeader.AllLossless = frameHeader.CodedLossless && frameHeader.FrameSize.FrameWidth == frameHeader.FrameSize.SuperResolutionUpscaledWidth; + this.ReadLoopFilterParameters(ref reader, sequenceHeader); + ReadCdefParameters(ref reader, sequenceHeader, frameHeader); + ReadLoopRestorationParameters(ref reader, sequenceHeader, frameHeader); + ReadTransformMode(ref reader, frameHeader); - frameInfo.ReferenceMode = ReadFrameReferenceMode(ref reader, isIntraFrame); - ReadSkipModeParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame, frameInfo.ReferenceMode); - if (isIntraFrame || frameInfo.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) + frameHeader.ReferenceMode = ReadFrameReferenceMode(ref reader, frameHeader); + ReadSkipModeParameters(ref reader, sequenceHeader, frameHeader); + if (frameHeader.IsIntra || frameHeader.ErrorResilientMode || !sequenceHeader.EnableWarpedMotion) { - frameInfo.AllowWarpedMotion = false; + frameHeader.AllowWarpedMotion = false; } else { - frameInfo.AllowWarpedMotion = reader.ReadBoolean(); + frameHeader.AllowWarpedMotion = reader.ReadBoolean(); } - frameInfo.UseReducedTransformSet = reader.ReadBoolean(); - ReadGlobalMotionParameters(ref reader, sequenceHeader, frameInfo, isIntraFrame); - frameInfo.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameInfo); + frameHeader.UseReducedTransformSet = reader.ReadBoolean(); + ReadGlobalMotionParameters(ref reader, sequenceHeader, frameHeader); + frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader); } private static void SetupPastIndependence(ObuFrameHeader frameInfo) { + _ = frameInfo; + // TODO: Implement. // The current frame can be decoded without dependencies on previous coded frame. } @@ -1214,9 +1214,8 @@ private static int GetQIndex(ObuSegmentationParameters segmentationParameters, i /// internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, bool trailingBit) { - int planeCount = this.SequenceHeader!.ColorConfig.PlaneCount; int startBitPosition = reader.BitPosition; - this.ReadUncompressedFrameHeader(ref reader, header, planeCount); + this.ReadUncompressedFrameHeader(ref reader); if (trailingBit) { ReadTrailingBits(ref reader); @@ -1356,12 +1355,14 @@ private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader re /// /// 5.9.12. Quantization params syntax. /// - private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) + private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { + ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters; + ObuColorConfig colorInfo = sequenceHeader.ColorConfig; quantParams.BaseQIndex = (int)reader.ReadLiteral(8); quantParams.DeltaQDc[(int)Av1Plane.Y] = ReadDeltaQ(ref reader); quantParams.DeltaQAc[(int)Av1Plane.Y] = 0; - if (planesCount > 1) + if (colorInfo.PlaneCount > 1) { quantParams.HasSeparateUvDelta = false; if (colorInfo.HasSeparateUvDelta) @@ -1415,7 +1416,7 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob /// /// 5.9.14. Segmentation params syntax. /// - private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) { frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); @@ -1501,7 +1502,7 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob /// /// 5.9.11. Loop filter params syntax /// - private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, int planesCount) + private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { ObuFrameHeader frameInfo = this.FrameHeader!; if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) @@ -1512,7 +1513,7 @@ private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, int planesC frameInfo.LoopFilterParameters.FilterLevel[0] = (int)reader.ReadLiteral(6); frameInfo.LoopFilterParameters.FilterLevel[1] = (int)reader.ReadLiteral(6); - if (planesCount > 1) + if (sequenceHeader.ColorConfig.PlaneCount > 1) { if (frameInfo.LoopFilterParameters.FilterLevel[0] > 0 || frameInfo.LoopFilterParameters.FilterLevel[1] > 0) { @@ -1572,7 +1573,7 @@ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHea /// /// See section 5.9.20. Loop restoration params syntax. /// - private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) { @@ -1581,6 +1582,7 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, frameInfo.LoopRestorationParameters.UsesLoopRestoration = false; frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration = false; + int planesCount = sequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) { frameInfo.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2); @@ -1625,10 +1627,11 @@ private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, /// /// See section 5.9.19. CDEF params syntax. /// - private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameInfo.CdefParameters; - if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) + ObuConstraintDirectionalEnhancementFilterParameters cdefInfo = frameHeader.CdefParameters; + bool multiPlane = sequenceHeader.ColorConfig.PlaneCount > 1; + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || sequenceHeader.CdefLevel == 0) { cdefInfo.BitCount = 0; cdefInfo.YStrength[0] = 0; @@ -1641,11 +1644,11 @@ private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenc cdefInfo.Damping = (int)reader.ReadLiteral(2) + 3; cdefInfo.BitCount = (int)reader.ReadLiteral(2); - for (int i = 0; i < (1 << frameInfo.CdefParameters.BitCount); i++) + for (int i = 0; i < (1 << frameHeader.CdefParameters.BitCount); i++) { cdefInfo.YStrength[i] = (int)reader.ReadLiteral(6); - if (planesCount > 1) + if (multiPlane) { cdefInfo.UvStrength[i] = (int)reader.ReadLiteral(6); } @@ -1655,9 +1658,12 @@ private static void ReadCdefParameters(ref Av1BitStreamReader reader, ObuSequenc /// /// 5.9.24. Global motion params syntax. /// - private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) + private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - if (isIntraFrame) + _ = reader; + _ = sequenceHeader; + + if (frameHeader.IsIntra) { return; } @@ -1669,9 +1675,9 @@ private static void ReadGlobalMotionParameters(ref Av1BitStreamReader reader, Ob /// /// 5.9.23. Frame reference mode syntax /// - private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, bool isIntraFrame) + private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) { - if (isIntraFrame) + if (frameHeader.IsIntra) { return ObuReferenceMode.SingleReference; } @@ -1682,24 +1688,24 @@ private static ObuReferenceMode ReadFrameReferenceMode(ref Av1BitStreamReader re /// /// 5.11.10. Skip mode syntax. /// - private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame, ObuReferenceMode referenceSelect) + private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - if (isIntraFrame || referenceSelect == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) + if (frameHeader.IsIntra || frameHeader.ReferenceMode == ObuReferenceMode.ReferenceModeSelect || !sequenceHeader.OrderHintInfo.EnableOrderHint) { - frameInfo.SkipModeParameters.SkipModeAllowed = false; + frameHeader.SkipModeParameters.SkipModeAllowed = false; } else { // Not applicable for INTRA frames. } - if (frameInfo.SkipModeParameters.SkipModeAllowed) + if (frameHeader.SkipModeParameters.SkipModeAllowed) { - frameInfo.SkipModeParameters.SkipModeFlag = reader.ReadBoolean(); + frameHeader.SkipModeParameters.SkipModeFlag = reader.ReadBoolean(); } else { - frameInfo.SkipModeParameters.SkipModeFlag = false; + frameHeader.SkipModeParameters.SkipModeFlag = false; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 58abc953e8..d1db3f25f6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -407,8 +407,8 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ } WriteTileInfo(ref writer, sequenceHeader, frameHeader); - WriteQuantizationParameters(ref writer, frameHeader.QuantizationParameters, sequenceHeader.ColorConfig, planesCount); - WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader, planesCount); + WriteQuantizationParameters(ref writer, sequenceHeader, frameHeader); + WriteSegmentationParameters(ref writer, sequenceHeader, frameHeader); if (frameHeader.QuantizationParameters.BaseQIndex > 0) { @@ -454,16 +454,16 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ { if (!frameHeader.CodedLossless) { - WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader, planesCount); + WriteLoopFilterParameters(ref writer, sequenceHeader, frameHeader); if (sequenceHeader.CdefLevel > 0) { - WriteCdefParameters(ref writer, sequenceHeader, frameHeader, planesCount); + WriteCdefParameters(ref writer, sequenceHeader, frameHeader); } } if (sequenceHeader.EnableRestoration) { - WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader, planesCount); + WriteLoopRestorationParameters(ref writer, sequenceHeader, frameHeader); } } @@ -471,15 +471,13 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ WriteTransformMode(ref writer, frameHeader); // No compound INTER-INTER for AVIF. - if (frameHeader.SkipModeParameters.SkipModeAllowed) - { - writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag); - } + WriteFrameReferenceMode(ref writer, frameHeader); + WriteSkipModeParameters(ref writer, frameHeader); // No warp motion for AVIF. writer.WriteBoolean(frameHeader.UseReducedTransformSet); - // No global motion for AVIF. + WriteGlobalMotionParameters(ref writer, frameHeader); WriteFilmGrainFilterParameters(ref writer, sequenceHeader, frameHeader); } @@ -585,13 +583,14 @@ private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter w /// /// See section 5.9.12. /// - private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuQuantizationParameters quantParams, ObuColorConfig colorInfo, int planesCount) + private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { + ObuQuantizationParameters quantParams = frameHeader.QuantizationParameters; writer.WriteLiteral((uint)quantParams.BaseQIndex, 8); WriteDeltaQ(ref writer, quantParams.DeltaQDc[(int)Av1Plane.Y]); - if (planesCount > 1) + if (sequenceHeader.ColorConfig.PlaneCount > 1) { - if (colorInfo.HasSeparateUvDelta) + if (sequenceHeader.ColorConfig.HasSeparateUvDelta) { writer.WriteBoolean(quantParams.HasSeparateUvDelta); } @@ -610,43 +609,44 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O { writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.Y], 4); writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.U], 4); - if (colorInfo.HasSeparateUvDelta) + if (sequenceHeader.ColorConfig.HasSeparateUvDelta) { writer.WriteLiteral((uint)quantParams.QMatrix[(int)Av1Plane.V], 4); } } } - private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) { - Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentatino not supported yet."); + _ = sequenceHeader; + Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet."); writer.WriteBoolean(false); } - private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) { return; } - writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevel[0], 6); - writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevel[1], 6); + writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[0], 6); + writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevel[1], 6); if (sequenceHeader.ColorConfig.PlaneCount > 1) { - if (frameInfo.LoopFilterParameters.FilterLevel[0] > 0 || frameInfo.LoopFilterParameters.FilterLevel[1] > 0) + if (frameHeader.LoopFilterParameters.FilterLevel[0] > 0 || frameHeader.LoopFilterParameters.FilterLevel[1] > 0) { - writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevelU, 6); - writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.FilterLevelV, 6); + writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelU, 6); + writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.FilterLevelV, 6); } } - writer.WriteLiteral((uint)frameInfo.LoopFilterParameters.SharpnessLevel, 3); - writer.WriteBoolean(frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled); - if (frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled) + writer.WriteLiteral((uint)frameHeader.LoopFilterParameters.SharpnessLevel, 3); + writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled); + if (frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled) { - writer.WriteBoolean(frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate); - if (frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate) + writer.WriteBoolean(frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate); + if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate) { throw new NotImplementedException("Reference update of loop filter not supported yet."); } @@ -664,21 +664,22 @@ private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHe } } - private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) { return; } + int planesCount = sequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) { - writer.WriteLiteral((uint)frameInfo.LoopRestorationParameters.Items[i].Type, 2); + writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.Items[i].Type, 2); } - if (frameInfo.LoopRestorationParameters.UsesLoopRestoration) + if (frameHeader.LoopRestorationParameters.UsesLoopRestoration) { - uint unitShift = (uint)frameInfo.LoopRestorationParameters.UnitShift; + uint unitShift = (uint)frameHeader.LoopRestorationParameters.UnitShift; if (sequenceHeader.Use128x128Superblock) { writer.WriteLiteral(unitShift - 1, 1); @@ -692,50 +693,58 @@ private static void WriteLoopRestorationParameters(ref Av1BitStreamWriter writer } } - if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration) + if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration) { - writer.WriteLiteral((uint)frameInfo.LoopRestorationParameters.UVShift, 1); + writer.WriteLiteral((uint)frameHeader.LoopRestorationParameters.UVShift, 1); } } } - private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) + private static void WriteCdefParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { _ = writer; _ = sequenceHeader; - _ = frameInfo; - _ = planesCount; - if (!frameInfo.CodedLossless && !frameInfo.AllowIntraBlockCopy && sequenceHeader.EnableCdef) + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableCdef) { - throw new NotImplementedException("Didn't implment writing CDF yet."); + return; } + + throw new NotImplementedException("Didn't implement writing CDEF yet."); } - private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool isIntraFrame) + private static void WriteGlobalMotionParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { _ = writer; - _ = sequenceHeader; - _ = frameInfo; - // Nothing to be written for INTRA frames. - Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + if (frameHeader.IsIntra) + { + // Nothing to be written for INTRA frames. + return; + } + + throw new InvalidImageContentException("AVIF files can only contain INTRA frames."); } - private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, bool isIntraFrame) + private static void WriteFrameReferenceMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { _ = writer; - // Nothing to be written for INTRA frames. - Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + if (frameHeader.IsIntra) + { + // Nothing to be written for INTRA frames. + return; + } + + throw new InvalidImageContentException("AVIF files can only contain INTRA frames."); } - private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, bool isIntraFrame) + private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { - _ = writer; - - // Nothing to be written for INTRA frames. - Guard.IsTrue(isIntraFrame, nameof(isIntraFrame), "Still picture contains only INTRA frames."); + if (frameHeader.SkipModeParameters.SkipModeAllowed) + { + writer.WriteBoolean(frameHeader.SkipModeParameters.SkipModeFlag); + } } private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) From 138526611b1df2fd962772acb23cc282b22e7992 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 5 Aug 2024 22:55:13 +0200 Subject: [PATCH 151/190] Binary identical OBU read write round trip --- .../Formats/Heif/Av1/Av1BitStreamWriter.cs | 7 +++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 58 +++---------------- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 16 +++-- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 18 ++---- 4 files changed, 32 insertions(+), 67 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs index affa7f9b57..687b973331 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitStreamWriter.cs @@ -37,6 +37,13 @@ public static int GetLittleEndianBytes128(uint value, Span span) span[1] = (byte)((value >> 7) & 0xff); return 2; } + else if (value < 0x800000U) + { + span[0] = (byte)((value & 0x7fU) | 0x80U); + span[1] = (byte)((value >> 7) & 0xff); + span[2] = (byte)((value >> 14) & 0xff); + return 3; + } else { throw new NotImplementedException("No such large values yet."); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 1042effdfe..c0d5c63f1e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -247,14 +247,6 @@ private void ComputeImageSize(ObuSequenceHeader sequenceHeader) frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2; } - private static bool IsValidObuType(ObuType type) => type switch - { - ObuType.SequenceHeader or ObuType.TemporalDelimiter or ObuType.FrameHeader or - ObuType.TileGroup or ObuType.Metadata or ObuType.Frame or ObuType.RedundantFrameHeader or - ObuType.TileList or ObuType.Padding => true, - _ => false, - }; - /// /// 5.5.1. General sequence header OBU syntax. /// @@ -292,7 +284,7 @@ internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequen sequenceHeader.TimingInfoPresentFlag = reader.ReadBoolean(); if (sequenceHeader.TimingInfoPresentFlag) { - ReadTimingInfo(ref reader, sequenceHeader); + ReadTimingInfo(ref reader, sequenceHeader); sequenceHeader.DecoderModelInfoPresentFlag = reader.ReadBoolean(); if (sequenceHeader.DecoderModelInfoPresentFlag) { @@ -309,9 +301,11 @@ internal static void ReadSequenceHeader(ref Av1BitStreamReader reader, ObuSequen sequenceHeader.OperatingPoint = new ObuOperatingPoint[operatingPointsCnt]; for (int i = 0; i < operatingPointsCnt; i++) { - sequenceHeader.OperatingPoint[i] = new ObuOperatingPoint(); - sequenceHeader.OperatingPoint[i].Idc = reader.ReadLiteral(12); - sequenceHeader.OperatingPoint[i].SequenceLevelIndex = (int)reader.ReadLiteral(5); + sequenceHeader.OperatingPoint[i] = new ObuOperatingPoint + { + Idc = reader.ReadLiteral(12), + SequenceLevelIndex = (int)reader.ReadLiteral(5) + }; if (sequenceHeader.OperatingPoint[i].SequenceLevelIndex > 7) { sequenceHeader.OperatingPoint[i].SequenceTier = (int)reader.ReadLiteral(1); @@ -638,36 +632,6 @@ private void ReadRenderSize(ref Av1BitStreamReader reader) } } - /// - /// 5.9.7. Frame size with refs syntax. - /// - private void ReadFrameSizeWithReferences(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag) - { - ObuSequenceHeader sequenceHeader = this.SequenceHeader!; - ObuFrameHeader frameInfo = this.FrameHeader!; - bool foundReference = false; - for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) - { - foundReference = reader.ReadBoolean(); - if (foundReference) - { - // Take values over from reference frame - break; - } - } - - if (!foundReference) - { - this.ReadFrameSize(ref reader, frameSizeOverrideFlag); - this.ReadRenderSize(ref reader); - } - else - { - this.ReadSuperResolutionParameters(ref reader); - this.ComputeImageSize(sequenceHeader); - } - } - /// /// 5.9.5. Frame size syntax. /// @@ -1087,7 +1051,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { // InitConCoefficientCdfs(); - SetupPastIndependence(frameHeader); + // SetupPastIndependence(frameHeader); } else { @@ -1184,14 +1148,6 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) frameHeader.FilmGrainParameters = ReadFilmGrainFilterParameters(ref reader, sequenceHeader, frameHeader); } - private static void SetupPastIndependence(ObuFrameHeader frameInfo) - { - _ = frameInfo; - - // TODO: Implement. - // The current frame can be decoded without dependencies on previous coded frame. - } - private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index d1db3f25f6..1772daf722 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -32,7 +32,7 @@ public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHead if (frameInfo != null && sequenceHeader != null) { - this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, true); + this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, false); if (frameInfo.TilesInfo != null) { WriteTileGroup(ref writer, frameInfo.TilesInfo, tileWriter); @@ -68,7 +68,7 @@ private static byte WriteObuHeader(ObuType type) => private static void WriteObuHeaderAndSize(Stream stream, ObuType type, Span payload) { stream.WriteByte(WriteObuHeader(type)); - Span lengthBytes = stackalloc byte[2]; + Span lengthBytes = stackalloc byte[3]; int lengthLength = Av1BitStreamWriter.GetLittleEndianBytes128((uint)payload.Length, lengthBytes); stream.Write(lengthBytes, 0, lengthLength); stream.Write(payload); @@ -416,6 +416,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ if (frameHeader.DeltaQParameters.IsPresent) { writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution - 1, 2); + this.previousQIndex = new int[tileCount]; for (int tileIndex = 0; tileIndex < tileCount; tileIndex++) { this.previousQIndex[tileIndex] = frameHeader.QuantizationParameters.BaseQIndex; @@ -438,6 +439,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ writer.WriteLiteral((uint)(1 + Av1Math.MostSignificantBit((uint)frameHeader.DeltaLoopFilterParameters.Resolution) - 1), 2); writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti); int frameLoopFilterCount = sequenceHeader.ColorConfig.IsMonochrome ? Av1Constants.FrameLoopFilterCount - 2 : Av1Constants.FrameLoopFilterCount; + this.previousDeltaLoopFilter = new int[frameLoopFilterCount]; for (int loopFilterId = 0; loopFilterId < frameLoopFilterCount; loopFilterId++) { this.previousDeltaLoopFilter[loopFilterId] = 0; @@ -498,11 +500,14 @@ private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader se return headerBytes; } + /// + /// 5.11.1. General tile group OBU syntax. + /// private static int WriteTileGroup(ref Av1BitStreamWriter writer, ObuTileGroupHeader tileInfo, IAv1TileWriter tileWriter) { int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = writer.BitPosition; - bool tileStartAndEndPresentFlag = tileCount != 0; + bool tileStartAndEndPresentFlag = tileCount > 1; writer.WriteBoolean(tileStartAndEndPresentFlag); uint tileGroupStart = 0U; @@ -623,6 +628,9 @@ private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, O writer.WriteBoolean(false); } + /// + /// 5.9.11. Loop filter params syntax + /// private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) @@ -750,7 +758,7 @@ private static void WriteSkipModeParameters(ref Av1BitStreamWriter writer, ObuFr private static void WriteFilmGrainFilterParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { ObuFilmGrainParameters grainParams = frameHeader.FilmGrainParameters; - if (!sequenceHeader.AreFilmGrainingParametersPresent && (!frameHeader.ShowFrame && !frameHeader.ShowableFrame)) + if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameHeader.ShowFrame && !frameHeader.ShowableFrame)) { return; } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 0c523442b6..0a13e08945 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -12,9 +12,7 @@ public class ObuFrameHeaderTests private static readonly byte[] DefaultSequenceHeaderBitStream = [0x0a, 0x06, 0b001_1_1_000, 0b00_1000_01, 0b11_110101, 0b001_11101, 0b111_1_1_1_0_1, 0b1_0_0_1_1_1_10]; - // TODO: Check with libgav1 test code. - private static readonly byte[] KeyFrameHeaderBitStream = - [0x32, 0x07, 0x10, 0x00]; + private static readonly byte[] KeyFrameHeaderBitStream = [0x32, 0x06, 0x10, 0x00]; // Bits Syntax element Value // 1 obu_forbidden_bit 0 @@ -26,7 +24,7 @@ public class ObuFrameHeaderTests private static readonly byte[] DefaultTemporalDelimiterBitStream = [0x12, 0x00]; [Theory] - // [InlineData(TestImages.Heif.IrvineAvif, 0x0102, 0x000d)] + // [InlineData(TestImages.Heif.IrvineAvif, 0x0198, 0x6bd1)] [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] @@ -51,12 +49,10 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) Assert.Equal(reader.Length, blockSize); } - /* [Theory] - [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d, 0x0128)] - - [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc, 0x0114)] - public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize, int tileOffset) + [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] + [InlineData(TestImages.Heif.XnConvert, 0x010e, 0x03cc)] + public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, int blockSize) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); @@ -74,13 +70,12 @@ public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, // Act 2 ObuWriter obuWriter = new(); - obuWriter.WriteAll(encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); + obuWriter.WriteAll(Configuration.Default, encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); // Assert byte[] encodedArray = encoded.ToArray(); Assert.Equal(span, encodedArray); } - */ [Theory] [InlineData(TestImages.Heif.Orange4x4, 0x010e, 0x001d)] @@ -323,5 +318,4 @@ private static ObuFrameHeader GetKeyFrameHeader() TileRowCount = 1, } }; - } From 0ff89110471f66d6342b7799dd5c068ec1a986c1 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 9 Aug 2024 15:44:38 +0200 Subject: [PATCH 152/190] Introduce Av1FrameBuffer class and renaming others --- .../Formats/Heif/Av1/Av1BlockModeInfo.cs | 17 -- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 6 +- .../Formats/Heif/Av1/Av1FrameBuffer.cs | 188 +++++++++++++ .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 252 +++++++++--------- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 88 +++--- .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 2 +- .../{Av1FrameBuffer.cs => Av1FrameInfo.cs} | 27 +- .../Heif/Av1/Tiling/Av1FrameModeInfoMap.cs | 4 +- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 6 +- .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 26 +- .../Formats/Heif/Av1/Tiling/Av1TileInfo.cs | 6 +- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 172 ++++++------ .../Heif/Av1/Transform/Av1FrameDecoder.cs | 8 +- .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 1 + 14 files changed, 497 insertions(+), 306 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs rename src/ImageSharp/Formats/Heif/Av1/Tiling/{Av1FrameBuffer.cs => Av1FrameInfo.cs} (92%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs deleted file mode 100644 index 4f41d47082..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Av1BlockModeInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1; - -internal class Av1BlockModeInfo -{ - public Av1BlockSize BlockSize { get; } - - public Av1PredictionMode PredictionMode { get; } - - public Av1PartitionType Partition { get; set; } - - public int SegmentId { get; } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 16897aba40..ac753a5da6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -19,7 +19,7 @@ internal class Av1Decoder : IAv1TileReader public ObuSequenceHeader? SequenceHeader { get; private set; } - public Av1FrameBuffer? FrameBuffer { get; private set; } + public Av1FrameInfo? FrameInfo { get; private set; } public void Decode(Span buffer) { @@ -29,8 +29,8 @@ public void Decode(Span buffer) Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); - this.FrameBuffer = this.tileReader.FrameBuffer; - this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameBuffer); + this.FrameInfo = this.tileReader.FrameInfo; + this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo); this.frameDecoder.DecodeFrame(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs new file mode 100644 index 0000000000..1267502eb8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs @@ -0,0 +1,188 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +/// +/// Buffer for the pixels of a single frame. +/// +internal class Av1FrameBuffer : IDisposable +{ + private const int DecoderPaddingValue = 72; + private const int PictureBufferYFlag = 1 << 0; + private const int PictureBufferCbFlag = 1 << 1; + private const int PictureBufferCrFlag = 1 << 2; + private const int PictureBufferLumaMask = PictureBufferYFlag; + private const int PictureBufferFullMask = PictureBufferYFlag | PictureBufferCbFlag | PictureBufferCrFlag; + + public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHeader, Av1ColorFormat maxColorFormat, bool is16BitPipeline) + { + Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat; + this.MaxWidth = sequenceHeader.MaxFrameWidth; + this.MaxHeight = sequenceHeader.MaxFrameHeight; + this.BitDepth = (Av1BitDepth)sequenceHeader.ColorConfig.BitDepth; + int bitsPerPixel = this.BitDepth > Av1BitDepth.EightBit || is16BitPipeline ? 2 : 1; + this.ColorFormat = colorFormat; + this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask; + + int leftPadding = DecoderPaddingValue; + int rightPadding = DecoderPaddingValue; + int topPadding = DecoderPaddingValue; + int bottomPadding = DecoderPaddingValue; + + this.Width = this.MaxWidth; + this.Height = this.MaxHeight; + int strideY = this.MaxWidth + leftPadding + rightPadding; + int heightY = this.MaxHeight + topPadding + bottomPadding; + this.OriginX = leftPadding; + this.OriginY = topPadding; + this.OriginOriginY = bottomPadding; + int strideChroma = 0; + int heightChroma = 0; + switch (this.ColorFormat) + { + case Av1ColorFormat.Yuv420: + strideChroma = (strideY + 1) >> 1; + heightChroma = (this.Height + 1) >> 1; + break; + case Av1ColorFormat.Yuv422: + strideChroma = (strideY + 1) >> 1; + heightChroma = this.Height; + break; + case Av1ColorFormat.Yuv444: + strideChroma = strideY; + heightChroma = this.Height; + break; + } + + this.PackedFlag = false; + + this.BufferY = null; + this.BufferCb = null; + this.BufferCr = null; + if ((this.BufferEnableMask & PictureBufferYFlag) != 0) + { + this.BufferY = configuration.MemoryAllocator.Allocate2D(strideY * bitsPerPixel, heightY); + } + + if ((this.BufferEnableMask & PictureBufferCbFlag) != 0) + { + this.BufferCb = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); + } + + if ((this.BufferEnableMask & PictureBufferCrFlag) != 0) + { + this.BufferCr = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); + } + + this.BitIncrementY = null; + this.BitIncrementCb = null; + this.BitIncrementCr = null; + this.BitIncrementY = null; + this.BitIncrementCb = null; + this.BitIncrementCr = null; + } + + /// + /// Gets the Y luma buffer. + /// + public Buffer2D? BufferY { get; private set; } + + /// + /// Gets the U chroma buffer. + /// + public Buffer2D? BufferCb { get; private set; } + + /// + /// Gets the V chroma buffer. + /// + public Buffer2D? BufferCr { get; private set; } + + public Buffer2D? BitIncrementY { get; private set; } + + public Buffer2D? BitIncrementCb { get; private set; } + + public Buffer2D? BitIncrementCr { get; private set; } + + /// + /// Gets or sets the horizontal padding distance. + /// + public int OriginX { get; set; } + + /// + /// Gets or sets the vertical padding distance. + /// + public int OriginY { get; set; } + + /// + /// Gets or sets the vertical bottom padding distance + /// + public int OriginOriginY { get; set; } + + /// + /// Gets or sets the Luma picture width, which excludes the padding. + /// + public int Width { get; set; } + + /// + /// Gets or sets the Luma picture height, which excludes the padding. + /// + public int Height { get; set; } + + /// + /// Gets or sets the Lume picture width. + /// + public int MaxWidth { get; set; } + + /// + /// Gets or sets the pixel bit depth. + /// + public Av1BitDepth BitDepth { get; set; } + + /// + /// Gets or sets the chroma subsampling. + /// + public Av1ColorFormat ColorFormat { get; set; } + + /// + /// Gets or sets the Luma picture height. + /// + public int MaxHeight { get; set; } + + public int LumaSize { get; } + + public int ChromaSize { get; } + + /// + /// Gets or sets a value indicating whether the bytes of the buffers are packed. + /// + public bool PackedFlag { get; set; } + + /// + /// Gets or sets a value indicating whether film grain parameters are present for this frame. + /// + public bool FilmGrainFlag { get; set; } + + public int BufferEnableMask { get; set; } + + public bool Is16BitPipeline { get; set; } + + public void Dispose() + { + this.BufferY?.Dispose(); + this.BufferY = null; + this.BufferCb?.Dispose(); + this.BufferCb = null; + this.BufferCr?.Dispose(); + this.BufferCr = null; + this.BitIncrementY?.Dispose(); + this.BitIncrementY = null; + this.BitIncrementCb?.Dispose(); + this.BitIncrementCb = null; + this.BitIncrementCr?.Dispose(); + this.BitIncrementCr = null; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index c0d5c63f1e..c933055afe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -241,10 +241,10 @@ private static void AlignToByteBoundary(ref Av1BitStreamReader reader) private void ComputeImageSize(ObuSequenceHeader sequenceHeader) { - ObuFrameHeader frameInfo = this.FrameHeader!; - frameInfo.ModeInfoColumnCount = 2 * ((frameInfo.FrameSize.FrameWidth + 7) >> 3); - frameInfo.ModeInfoRowCount = 2 * ((frameInfo.FrameSize.FrameHeight + 7) >> 3); - frameInfo.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2; + ObuFrameHeader frameHeader = this.FrameHeader!; + frameHeader.ModeInfoColumnCount = 2 * ((frameHeader.FrameSize.FrameWidth + 7) >> 3); + frameHeader.ModeInfoRowCount = 2 * ((frameHeader.FrameSize.FrameHeight + 7) >> 3); + frameHeader.ModeInfoStride = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, Av1Constants.MaxSuperBlockSizeLog2) >> Av1Constants.ModeInfoSizeLog2; } /// @@ -582,7 +582,7 @@ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig c private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; - ObuFrameHeader frameInfo = this.FrameHeader!; + ObuFrameHeader frameHeader = this.FrameHeader!; bool useSuperResolution = false; if (sequenceHeader.EnableSuperResolution) { @@ -591,24 +591,24 @@ private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) if (useSuperResolution) { - frameInfo.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(Av1Constants.SuperResolutionScaleBits) + Av1Constants.SuperResolutionScaleDenominatorMinimum; + frameHeader.FrameSize.SuperResolutionDenominator = (int)reader.ReadLiteral(Av1Constants.SuperResolutionScaleBits) + Av1Constants.SuperResolutionScaleDenominatorMinimum; } else { - frameInfo.FrameSize.SuperResolutionDenominator = Av1Constants.ScaleNumerator; + frameHeader.FrameSize.SuperResolutionDenominator = Av1Constants.ScaleNumerator; } - frameInfo.FrameSize.SuperResolutionUpscaledWidth = frameInfo.FrameSize.FrameWidth; - frameInfo.FrameSize.FrameWidth = - ((frameInfo.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) + - (frameInfo.FrameSize.SuperResolutionDenominator / 2)) / - frameInfo.FrameSize.SuperResolutionDenominator; + frameHeader.FrameSize.SuperResolutionUpscaledWidth = frameHeader.FrameSize.FrameWidth; + frameHeader.FrameSize.FrameWidth = + ((frameHeader.FrameSize.SuperResolutionUpscaledWidth * Av1Constants.ScaleNumerator) + + (frameHeader.FrameSize.SuperResolutionDenominator / 2)) / + frameHeader.FrameSize.SuperResolutionDenominator; /* - if (frameInfo.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator) + if (frameHeader.FrameSize.SuperResolutionDenominator != Av1Constants.ScaleNumerator) { - int manWidth = Math.Min(16, frameInfo.FrameSize.SuperResolutionUpscaledWidth); - frameInfo.FrameSize.FrameWidth = Math.Max(manWidth, frameInfo.FrameSize.FrameWidth); + int manWidth = Math.Min(16, frameHeader.FrameSize.SuperResolutionUpscaledWidth); + frameHeader.FrameSize.FrameWidth = Math.Max(manWidth, frameHeader.FrameSize.FrameWidth); } */ } @@ -618,17 +618,17 @@ private void ReadSuperResolutionParameters(ref Av1BitStreamReader reader) /// private void ReadRenderSize(ref Av1BitStreamReader reader) { - ObuFrameHeader frameInfo = this.FrameHeader!; + ObuFrameHeader frameHeader = this.FrameHeader!; bool renderSizeAndFrameSizeDifferent = reader.ReadBoolean(); if (renderSizeAndFrameSizeDifferent) { - frameInfo.FrameSize.RenderWidth = (int)reader.ReadLiteral(16) + 1; - frameInfo.FrameSize.RenderHeight = (int)reader.ReadLiteral(16) + 1; + frameHeader.FrameSize.RenderWidth = (int)reader.ReadLiteral(16) + 1; + frameHeader.FrameSize.RenderHeight = (int)reader.ReadLiteral(16) + 1; } else { - frameInfo.FrameSize.RenderWidth = frameInfo.FrameSize.SuperResolutionUpscaledWidth; - frameInfo.FrameSize.RenderHeight = frameInfo.FrameSize.FrameHeight; + frameHeader.FrameSize.RenderWidth = frameHeader.FrameSize.SuperResolutionUpscaledWidth; + frameHeader.FrameSize.RenderHeight = frameHeader.FrameSize.FrameHeight; } } @@ -638,16 +638,16 @@ private void ReadRenderSize(ref Av1BitStreamReader reader) private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverrideFlag) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; - ObuFrameHeader frameInfo = this.FrameHeader!; + ObuFrameHeader frameHeader = this.FrameHeader!; if (frameSizeOverrideFlag) { - frameInfo.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; - frameInfo.FrameSize.FrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; + frameHeader.FrameSize.FrameWidth = (int)reader.ReadLiteral(sequenceHeader.FrameWidthBits) + 1; + frameHeader.FrameSize.FrameHeight = (int)reader.ReadLiteral(sequenceHeader.FrameHeightBits) + 1; } else { - frameInfo.FrameSize.FrameWidth = sequenceHeader.MaxFrameWidth; - frameInfo.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; + frameHeader.FrameSize.FrameWidth = sequenceHeader.MaxFrameWidth; + frameHeader.FrameSize.FrameHeight = sequenceHeader.MaxFrameHeight; } this.ReadSuperResolutionParameters(ref reader); @@ -657,15 +657,15 @@ private void ReadFrameSize(ref Av1BitStreamReader reader, bool frameSizeOverride /// /// 5.9.15. Tile info syntax. /// - private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { ObuTileGroupHeader tileInfo = new(); int superblockColumnCount; int superblockRowCount; int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; - superblockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; - superblockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockColumnCount = (frameHeader.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockRowCount = (frameHeader.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (superblockSizeLog2 << 1); @@ -701,7 +701,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob i++; } - tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnStartModeInfo[i] = frameHeader.ModeInfoColumnCount; tileInfo.TileColumnCount = i; tileInfo.MinLog2TileRowCount = Math.Max(tileInfo.MinLog2TileCount - tileInfo.TileColumnCountLog2, 0); @@ -728,7 +728,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob i++; } - tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowStartModeInfo[i] = frameHeader.ModeInfoRowCount; tileInfo.TileRowCount = i; } else @@ -750,7 +750,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob throw new ImageFormatException("Super block tiles width does not add up to total width."); } - tileInfo.TileColumnStartModeInfo[i] = frameInfo.ModeInfoColumnCount; + tileInfo.TileColumnStartModeInfo[i] = frameHeader.ModeInfoColumnCount; tileInfo.TileColumnCount = i; tileInfo.TileColumnCountLog2 = TileLog2(1, tileInfo.TileColumnCount); if (tileInfo.MinLog2TileCount > 0) @@ -779,7 +779,7 @@ private static ObuTileGroupHeader ReadTileInfo(ref Av1BitStreamReader reader, Ob throw new ImageFormatException("Super block tiles height does not add up to total height."); } - tileInfo.TileRowStartModeInfo[i] = frameInfo.ModeInfoRowCount; + tileInfo.TileRowStartModeInfo[i] = frameHeader.ModeInfoRowCount; tileInfo.TileRowCount = i; tileInfo.TileRowCountLog2 = TileLog2(1, tileInfo.TileRowCount); } @@ -1035,10 +1035,10 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) throw new InvalidImageContentException("AVIF image can only contain INTRA frames."); } - // SetupFrameBufferReferences(sequenceHeader, frameInfo); - // CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameInfo); + // SetupFrameBufferReferences(sequenceHeader, frameHeader); + // CheckAddTemporalMotionVectorBuffer(sequenceHeader, frameHeader); - // SetupFrameSignBias(sequenceHeader, frameInfo); + // SetupFrameSignBias(sequenceHeader, frameHeader); if (sequenceHeader.IsReducedStillPictureHeader || frameHeader.DisableCdfUpdate) { frameHeader.DisableFrameEndUpdateCdf = true; @@ -1055,7 +1055,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) } else { - // LoadCdfs(frameInfo.PrimaryReferenceFrame); + // LoadCdfs(frameHeader.PrimaryReferenceFrame); // LoadPrevious(); throw new NotImplementedException(); } @@ -1066,7 +1066,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) throw new NotImplementedException(); } - // GenerateNextReferenceFrameMap(sequenceHeader, frameInfo); + // GenerateNextReferenceFrameMap(sequenceHeader, frameHeader); frameHeader.TilesInfo = ReadTileInfo(ref reader, sequenceHeader, frameHeader); ReadQuantizationParameters(ref reader, sequenceHeader, frameHeader); ReadSegmentationParameters(ref reader, frameHeader); @@ -1076,7 +1076,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) // SetupSegmentationDequantization(); if (frameHeader.PrimaryReferenceFrame == Av1Constants.PrimaryReferenceFrameNone) { - // ResetParseContext(mainParseContext, frameInfo.QuantizationParameters.BaseQIndex); + // ResetParseContext(mainParseContext, frameHeader.QuantizationParameters.BaseQIndex); } else { @@ -1190,7 +1190,7 @@ internal void ReadFrameHeader(ref Av1BitStreamReader reader, ObuHeader header, b private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder, ObuHeader header, out bool isLastTileGroup) { ObuSequenceHeader sequenceHeader = this.SequenceHeader!; - ObuFrameHeader frameInfo = this.FrameHeader!; + ObuFrameHeader frameHeader = this.FrameHeader!; ObuTileGroupHeader tileInfo = this.FrameHeader!.TilesInfo; int tileCount = tileInfo.TileColumnCount * tileInfo.TileRowCount; int startBitPosition = reader.BitPosition; @@ -1220,16 +1220,16 @@ private void ReadTileGroup(ref Av1BitStreamReader reader, IAv1TileReader decoder int headerBytes = (endBitPosition - startBitPosition) / 8; header.PayloadSize -= headerBytes; - bool noIbc = !frameInfo.AllowIntraBlockCopy; - bool doLoopFilter = noIbc && (frameInfo.LoopFilterParameters.FilterLevel[0] != 0 || frameInfo.LoopFilterParameters.FilterLevel[1] != 0); - bool doCdef = noIbc && (!frameInfo.CodedLossless && - (frameInfo.CdefParameters.BitCount != 0 || - frameInfo.CdefParameters.YStrength[0] != 0 || - frameInfo.CdefParameters.UvStrength[0] != 0)); + bool noIbc = !frameHeader.AllowIntraBlockCopy; + bool doLoopFilter = noIbc && (frameHeader.LoopFilterParameters.FilterLevel[0] != 0 || frameHeader.LoopFilterParameters.FilterLevel[1] != 0); + bool doCdef = noIbc && (!frameHeader.CodedLossless && + (frameHeader.CdefParameters.BitCount != 0 || + frameHeader.CdefParameters.YStrength[0] != 0 || + frameHeader.CdefParameters.UvStrength[0] != 0)); bool doLoopRestoration = noIbc && - (frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.Y].Type != ObuRestorationType.None || - frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None || - frameInfo.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None); + (frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.Y].Type != ObuRestorationType.None || + frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.U].Type != ObuRestorationType.None || + frameHeader.LoopRestorationParameters.Items[(int)Av1Plane.V].Type != ObuRestorationType.None); for (int tileNum = tileGroupStart; tileNum <= tileGroupEnd; tileNum++) { @@ -1270,40 +1270,40 @@ private static int ReadDeltaQ(ref Av1BitStreamReader reader) /// /// 5.9.17. Quantizer index delta parameters syntax. /// - private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadFrameDeltaQParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) { - frameInfo.DeltaQParameters.Resolution = 0; - frameInfo.DeltaQParameters.IsPresent = false; - if (frameInfo.QuantizationParameters.BaseQIndex > 0) + frameHeader.DeltaQParameters.Resolution = 0; + frameHeader.DeltaQParameters.IsPresent = false; + if (frameHeader.QuantizationParameters.BaseQIndex > 0) { - frameInfo.DeltaQParameters.IsPresent = reader.ReadBoolean(); + frameHeader.DeltaQParameters.IsPresent = reader.ReadBoolean(); } - if (frameInfo.DeltaQParameters.IsPresent) + if (frameHeader.DeltaQParameters.IsPresent) { - frameInfo.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2); + frameHeader.DeltaQParameters.Resolution = (int)reader.ReadLiteral(2); } } /// /// 5.9.18. Loop filter delta parameters syntax. /// - private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadFrameDeltaLoopFilterParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) { - frameInfo.DeltaLoopFilterParameters.IsPresent = false; - frameInfo.DeltaLoopFilterParameters.Resolution = 0; - frameInfo.DeltaLoopFilterParameters.IsMulti = false; - if (frameInfo.DeltaQParameters.IsPresent) + frameHeader.DeltaLoopFilterParameters.IsPresent = false; + frameHeader.DeltaLoopFilterParameters.Resolution = 0; + frameHeader.DeltaLoopFilterParameters.IsMulti = false; + if (frameHeader.DeltaQParameters.IsPresent) { - if (!frameInfo.AllowIntraBlockCopy) + if (!frameHeader.AllowIntraBlockCopy) { - frameInfo.DeltaLoopFilterParameters.IsPresent = reader.ReadBoolean(); + frameHeader.DeltaLoopFilterParameters.IsPresent = reader.ReadBoolean(); } - if (frameInfo.DeltaLoopFilterParameters.IsPresent) + if (frameHeader.DeltaLoopFilterParameters.IsPresent) { - frameInfo.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2); - frameInfo.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean(); + frameHeader.DeltaLoopFilterParameters.Resolution = (int)reader.ReadLiteral(2); + frameHeader.DeltaLoopFilterParameters.IsMulti = reader.ReadBoolean(); } } } @@ -1372,30 +1372,30 @@ private static void ReadQuantizationParameters(ref Av1BitStreamReader reader, Ob /// /// 5.9.14. Segmentation params syntax. /// - private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) { - frameInfo.SegmentationParameters.Enabled = reader.ReadBoolean(); + frameHeader.SegmentationParameters.Enabled = reader.ReadBoolean(); - if (frameInfo.SegmentationParameters.Enabled) + if (frameHeader.SegmentationParameters.Enabled) { - if (frameInfo.PrimaryReferenceFrame == PrimaryRefNone) + if (frameHeader.PrimaryReferenceFrame == PrimaryRefNone) { - frameInfo.SegmentationParameters.SegmentationUpdateMap = 1; - frameInfo.SegmentationParameters.SegmentationTemporalUpdate = 0; - frameInfo.SegmentationParameters.SegmentationUpdateData = 1; + frameHeader.SegmentationParameters.SegmentationUpdateMap = 1; + frameHeader.SegmentationParameters.SegmentationTemporalUpdate = 0; + frameHeader.SegmentationParameters.SegmentationUpdateData = 1; } else { - frameInfo.SegmentationParameters.SegmentationUpdateMap = reader.ReadBoolean() ? 1 : 0; - if (frameInfo.SegmentationParameters.SegmentationUpdateMap == 1) + frameHeader.SegmentationParameters.SegmentationUpdateMap = reader.ReadBoolean() ? 1 : 0; + if (frameHeader.SegmentationParameters.SegmentationUpdateMap == 1) { - frameInfo.SegmentationParameters.SegmentationTemporalUpdate = reader.ReadBoolean() ? 1 : 0; + frameHeader.SegmentationParameters.SegmentationTemporalUpdate = reader.ReadBoolean() ? 1 : 0; } - frameInfo.SegmentationParameters.SegmentationUpdateData = reader.ReadBoolean() ? 1 : 0; + frameHeader.SegmentationParameters.SegmentationUpdateData = reader.ReadBoolean() ? 1 : 0; } - if (frameInfo.SegmentationParameters.SegmentationUpdateData == 1) + if (frameHeader.SegmentationParameters.SegmentationUpdateData == 1) { for (int i = 0; i < MaxSegments; i++) { @@ -1403,7 +1403,7 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob { int featureValue = 0; bool featureEnabled = reader.ReadBoolean(); - frameInfo.SegmentationParameters.FeatureEnabled[i, j] = featureEnabled; + frameHeader.SegmentationParameters.FeatureEnabled[i, j] = featureEnabled; int clippedValue = 0; if (featureEnabled) { @@ -1420,7 +1420,7 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob } } - frameInfo.SegmentationParameters.FeatureData[i, j] = clippedValue; + frameHeader.SegmentationParameters.FeatureData[i, j] = clippedValue; } } } @@ -1431,24 +1431,24 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob { for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++) { - frameInfo.SegmentationParameters.FeatureEnabled[i, j] = false; - frameInfo.SegmentationParameters.FeatureData[i, j] = 0; + frameHeader.SegmentationParameters.FeatureEnabled[i, j] = false; + frameHeader.SegmentationParameters.FeatureData[i, j] = 0; } } } - frameInfo.SegmentationParameters.SegmentIdPrecedesSkip = false; - frameInfo.SegmentationParameters.LastActiveSegmentId = 0; + frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = false; + frameHeader.SegmentationParameters.LastActiveSegmentId = 0; for (int i = 0; i < Av1Constants.MaxSegmentCount; i++) { for (int j = 0; j < Av1Constants.SegmentationLevelMax; j++) { - if (frameInfo.SegmentationParameters.FeatureEnabled[i, j]) + if (frameHeader.SegmentationParameters.FeatureEnabled[i, j]) { - frameInfo.SegmentationParameters.LastActiveSegmentId = i; + frameHeader.SegmentationParameters.LastActiveSegmentId = i; if (j >= SegLvlRefFrame) { - frameInfo.SegmentationParameters.SegmentIdPrecedesSkip = true; + frameHeader.SegmentationParameters.SegmentIdPrecedesSkip = true; } } } @@ -1460,36 +1460,36 @@ private static void ReadSegmentationParameters(ref Av1BitStreamReader reader, Ob /// private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader) { - ObuFrameHeader frameInfo = this.FrameHeader!; - if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy) + ObuFrameHeader frameHeader = this.FrameHeader!; + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy) { return; } - frameInfo.LoopFilterParameters.FilterLevel[0] = (int)reader.ReadLiteral(6); - frameInfo.LoopFilterParameters.FilterLevel[1] = (int)reader.ReadLiteral(6); + frameHeader.LoopFilterParameters.FilterLevel[0] = (int)reader.ReadLiteral(6); + frameHeader.LoopFilterParameters.FilterLevel[1] = (int)reader.ReadLiteral(6); if (sequenceHeader.ColorConfig.PlaneCount > 1) { - if (frameInfo.LoopFilterParameters.FilterLevel[0] > 0 || frameInfo.LoopFilterParameters.FilterLevel[1] > 0) + if (frameHeader.LoopFilterParameters.FilterLevel[0] > 0 || frameHeader.LoopFilterParameters.FilterLevel[1] > 0) { - frameInfo.LoopFilterParameters.FilterLevelU = (int)reader.ReadLiteral(6); - frameInfo.LoopFilterParameters.FilterLevelV = (int)reader.ReadLiteral(6); + frameHeader.LoopFilterParameters.FilterLevelU = (int)reader.ReadLiteral(6); + frameHeader.LoopFilterParameters.FilterLevelV = (int)reader.ReadLiteral(6); } } - frameInfo.LoopFilterParameters.SharpnessLevel = (int)reader.ReadLiteral(3); - frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled = reader.ReadBoolean(); - if (frameInfo.LoopFilterParameters.ReferenceDeltaModeEnabled) + frameHeader.LoopFilterParameters.SharpnessLevel = (int)reader.ReadLiteral(3); + frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled = reader.ReadBoolean(); + if (frameHeader.LoopFilterParameters.ReferenceDeltaModeEnabled) { - frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate = reader.ReadBoolean(); - if (frameInfo.LoopFilterParameters.ReferenceDeltaModeUpdate) + frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate = reader.ReadBoolean(); + if (frameHeader.LoopFilterParameters.ReferenceDeltaModeUpdate) { for (int i = 0; i < Av1Constants.TotalReferencesPerFrame; i++) { if (reader.ReadBoolean()) { - frameInfo.LoopFilterParameters.ReferenceDeltas[i] = reader.ReadSignedFromUnsigned(7); + frameHeader.LoopFilterParameters.ReferenceDeltas[i] = reader.ReadSignedFromUnsigned(7); } } @@ -1497,7 +1497,7 @@ private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequence { if (reader.ReadBoolean()) { - frameInfo.LoopFilterParameters.ModeDeltas[i] = reader.ReadSignedFromUnsigned(7); + frameHeader.LoopFilterParameters.ModeDeltas[i] = reader.ReadSignedFromUnsigned(7); } } } @@ -1507,21 +1507,21 @@ private void ReadLoopFilterParameters(ref Av1BitStreamReader reader, ObuSequence /// /// 5.9.21. TX mode syntax. /// - private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameInfo) + private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHeader frameHeader) { - if (frameInfo.CodedLossless) + if (frameHeader.CodedLossless) { - frameInfo.TransformMode = Av1TransformMode.Only4x4; + frameHeader.TransformMode = Av1TransformMode.Only4x4; } else { if (reader.ReadBoolean()) { - frameInfo.TransformMode = Av1TransformMode.Select; + frameHeader.TransformMode = Av1TransformMode.Select; } else { - frameInfo.TransformMode = Av1TransformMode.Largest; + frameHeader.TransformMode = Av1TransformMode.Largest; } } } @@ -1529,54 +1529,54 @@ private static void ReadTransformMode(ref Av1BitStreamReader reader, ObuFrameHea /// /// See section 5.9.20. Loop restoration params syntax. /// - private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - if (frameInfo.CodedLossless || frameInfo.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) + if (frameHeader.CodedLossless || frameHeader.AllowIntraBlockCopy || !sequenceHeader.EnableRestoration) { return; } - frameInfo.LoopRestorationParameters.UsesLoopRestoration = false; - frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration = false; + frameHeader.LoopRestorationParameters.UsesLoopRestoration = false; + frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration = false; int planesCount = sequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) { - frameInfo.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2); + frameHeader.LoopRestorationParameters.Items[i].Type = (ObuRestorationType)reader.ReadLiteral(2); - if (frameInfo.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None) + if (frameHeader.LoopRestorationParameters.Items[i].Type != ObuRestorationType.None) { - frameInfo.LoopRestorationParameters.UsesLoopRestoration = true; + frameHeader.LoopRestorationParameters.UsesLoopRestoration = true; if (i > 0) { - frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration = true; + frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration = true; } } } - if (frameInfo.LoopRestorationParameters.UsesLoopRestoration) + if (frameHeader.LoopRestorationParameters.UsesLoopRestoration) { - frameInfo.LoopRestorationParameters.UnitShift = (int)reader.ReadLiteral(1); + frameHeader.LoopRestorationParameters.UnitShift = (int)reader.ReadLiteral(1); if (sequenceHeader.Use128x128Superblock) { - frameInfo.LoopRestorationParameters.UnitShift++; + frameHeader.LoopRestorationParameters.UnitShift++; } else { if (reader.ReadBoolean()) { - frameInfo.LoopRestorationParameters.UnitShift += (int)reader.ReadLiteral(1); + frameHeader.LoopRestorationParameters.UnitShift += (int)reader.ReadLiteral(1); } } - frameInfo.LoopRestorationParameters.Items[0].Size = Av1Constants.RestorationMaxTileSize >> (2 - frameInfo.LoopRestorationParameters.UnitShift); - frameInfo.LoopRestorationParameters.UVShift = 0; - if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameInfo.LoopRestorationParameters.UsesChromaLoopRestoration) + frameHeader.LoopRestorationParameters.Items[0].Size = Av1Constants.RestorationMaxTileSize >> (2 - frameHeader.LoopRestorationParameters.UnitShift); + frameHeader.LoopRestorationParameters.UVShift = 0; + if (sequenceHeader.ColorConfig.SubSamplingX && sequenceHeader.ColorConfig.SubSamplingY && frameHeader.LoopRestorationParameters.UsesChromaLoopRestoration) { - frameInfo.LoopRestorationParameters.UVShift = (int)reader.ReadLiteral(1); + frameHeader.LoopRestorationParameters.UVShift = (int)reader.ReadLiteral(1); } - frameInfo.LoopRestorationParameters.Items[1].Size = frameInfo.LoopRestorationParameters.Items[0].Size >> frameInfo.LoopRestorationParameters.UVShift; - frameInfo.LoopRestorationParameters.Items[2].Size = frameInfo.LoopRestorationParameters.Items[0].Size >> frameInfo.LoopRestorationParameters.UVShift; + frameHeader.LoopRestorationParameters.Items[1].Size = frameHeader.LoopRestorationParameters.Items[0].Size >> frameHeader.LoopRestorationParameters.UVShift; + frameHeader.LoopRestorationParameters.Items[2].Size = frameHeader.LoopRestorationParameters.Items[0].Size >> frameHeader.LoopRestorationParameters.UVShift; } } @@ -1668,10 +1668,10 @@ private static void ReadSkipModeParameters(ref Av1BitStreamReader reader, ObuSeq /// /// 5.9.30. Film grain params syntax. /// - private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { ObuFilmGrainParameters grainParams = new(); - if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameInfo.ShowFrame && !frameInfo.ShowableFrame)) + if (!sequenceHeader.AreFilmGrainingParametersPresent || (!frameHeader.ShowFrame && !frameHeader.ShowableFrame)) { return grainParams; } @@ -1684,7 +1684,7 @@ private static ObuFilmGrainParameters ReadFilmGrainFilterParameters(ref Av1BitSt grainParams.GrainSeed = reader.ReadLiteral(16); - if (frameInfo.FrameType == ObuFrameType.InterFrame) + if (frameHeader.FrameType == ObuFrameType.InterFrame) { grainParams.UpdateGrain = reader.ReadBoolean(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 1772daf722..16452799d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -14,7 +14,7 @@ internal class ObuWriter /// /// Encode a single frame into OBU's. /// - public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, IAv1TileWriter tileWriter) + public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1TileWriter tileWriter) { // TODO: Determine inital size dynamically int initialBufferSize = 2000; @@ -30,12 +30,12 @@ public void WriteAll(Configuration configuration, Stream stream, ObuSequenceHead WriteObuHeaderAndSize(stream, ObuType.SequenceHeader, buffer.GetSpan(bytesWritten)); } - if (frameInfo != null && sequenceHeader != null) + if (frameHeader != null && sequenceHeader != null) { - this.WriteFrameHeader(ref writer, sequenceHeader, frameInfo, false); - if (frameInfo.TilesInfo != null) + this.WriteFrameHeader(ref writer, sequenceHeader, frameHeader, false); + if (frameHeader.TilesInfo != null) { - WriteTileGroup(ref writer, frameInfo.TilesInfo, tileWriter); + WriteTileGroup(ref writer, frameHeader.TilesInfo, tileWriter); } int bytesWritten = (writer.BitPosition + 7) >> 3; @@ -192,7 +192,7 @@ private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig } } - private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { bool useSuperResolution = false; if (sequenceHeader.EnableSuperResolution) @@ -202,22 +202,22 @@ private static void WriteSuperResolutionParameters(ref Av1BitStreamWriter writer if (useSuperResolution) { - writer.WriteLiteral((uint)frameInfo.FrameSize.SuperResolutionDenominator - Av1Constants.SuperResolutionScaleDenominatorMinimum, Av1Constants.SuperResolutionScaleBits); + writer.WriteLiteral((uint)frameHeader.FrameSize.SuperResolutionDenominator - Av1Constants.SuperResolutionScaleDenominatorMinimum, Av1Constants.SuperResolutionScaleBits); } } - private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + private static void WriteRenderSize(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { bool renderSizeAndFrameSizeDifferent = false; writer.WriteBoolean(false); if (renderSizeAndFrameSizeDifferent) { - writer.WriteLiteral((uint)frameInfo.FrameSize.RenderWidth - 1, 16); - writer.WriteLiteral((uint)frameInfo.FrameSize.RenderHeight - 1, 16); + writer.WriteLiteral((uint)frameHeader.FrameSize.RenderWidth - 1, 16); + writer.WriteLiteral((uint)frameHeader.FrameSize.RenderHeight - 1, 16); } } - private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag) { bool foundReference = false; for (int i = 0; i < Av1Constants.ReferencesPerFrame; i++) @@ -232,35 +232,35 @@ private static void WriteFrameSizeWithReferences(ref Av1BitStreamWriter writer, if (!foundReference) { - WriteFrameSize(ref writer, sequenceHeader, frameInfo, frameSizeOverrideFlag); - WriteRenderSize(ref writer, frameInfo); + WriteFrameSize(ref writer, sequenceHeader, frameHeader, frameSizeOverrideFlag); + WriteRenderSize(ref writer, frameHeader); } else { - WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); + WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader); } } - private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool frameSizeOverrideFlag) + private static void WriteFrameSize(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool frameSizeOverrideFlag) { if (frameSizeOverrideFlag) { - writer.WriteLiteral((uint)frameInfo.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1); - writer.WriteLiteral((uint)frameInfo.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1); + writer.WriteLiteral((uint)frameHeader.FrameSize.FrameWidth - 1, sequenceHeader.FrameWidthBits + 1); + writer.WriteLiteral((uint)frameHeader.FrameSize.FrameHeight - 1, sequenceHeader.FrameHeightBits + 1); } - WriteSuperResolutionParameters(ref writer, sequenceHeader, frameInfo); + WriteSuperResolutionParameters(ref writer, sequenceHeader, frameHeader); } - private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - ObuTileGroupHeader tileInfo = frameInfo.TilesInfo; + ObuTileGroupHeader tileInfo = frameHeader.TilesInfo; int superblockColumnCount; int superblockRowCount; int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; int superblockShift = superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; - superblockColumnCount = (frameInfo.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; - superblockRowCount = (frameInfo.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockColumnCount = (frameHeader.ModeInfoColumnCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; + superblockRowCount = (frameHeader.ModeInfoRowCount + sequenceHeader.SuperblockModeInfoSize - 1) >> superblockShift; int superBlockSize = superblockShift + 2; int maxTileAreaOfSuperBlock = Av1Constants.MaxTileArea >> (2 * superBlockSize); @@ -341,7 +341,7 @@ private static void WriteTileInfo(ref Av1BitStreamWriter writer, ObuSequenceHead writer.WriteLiteral((uint)tileInfo.TileSizeBytes - 1, 2); } - frameInfo.TilesInfo = tileInfo; + frameHeader.TilesInfo = tileInfo; } private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) @@ -356,7 +356,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ } else { - // Guard.IsTrue(frameInfo.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools); + // Guard.IsTrue(frameHeader.AllowScreenContentTools == sequenceHeader.ForceScreenContentTools); } if (frameHeader.AllowScreenContentTools) @@ -367,7 +367,7 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ } else { - // Guard.IsTrue(frameInfo.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameInfo.ForceIntegerMotionVector), "Frame and sequence must be in sync"); + // Guard.IsTrue(frameHeader.ForceIntegerMotionVector == sequenceHeader.ForceIntegerMotionVector, nameof(frameHeader.ForceIntegerMotionVector), "Frame and sequence must be in sync"); } } @@ -486,10 +486,10 @@ private void WriteUncompressedFrameHeader(ref Av1BitStreamWriter writer, ObuSequ private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) => segmentationParameters.Enabled && segmentationParameters.FeatureEnabled[segmentId, (int)feature]; - private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, bool writeTrailingBits) + private int WriteFrameHeader(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool writeTrailingBits) { int startBitPosition = writer.BitPosition; - this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameInfo); + this.WriteUncompressedFrameHeader(ref writer, sequenceHeader, frameHeader); if (writeTrailingBits) { WriteTrailingBits(ref writer); @@ -555,32 +555,32 @@ private static int WriteDeltaQ(ref Av1BitStreamWriter writer, int deltaQ) return deltaQ; } - private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + private static void WriteFrameDeltaQParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { - if (frameInfo.QuantizationParameters.BaseQIndex > 0) + if (frameHeader.QuantizationParameters.BaseQIndex > 0) { - writer.WriteBoolean(frameInfo.DeltaQParameters.IsPresent); + writer.WriteBoolean(frameHeader.DeltaQParameters.IsPresent); } - if (frameInfo.DeltaQParameters.IsPresent) + if (frameHeader.DeltaQParameters.IsPresent) { - writer.WriteLiteral((uint)frameInfo.DeltaQParameters.Resolution, 2); + writer.WriteLiteral((uint)frameHeader.DeltaQParameters.Resolution, 2); } } - private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + private static void WriteFrameDeltaLoopFilterParameters(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { - if (frameInfo.DeltaQParameters.IsPresent) + if (frameHeader.DeltaQParameters.IsPresent) { - if (!frameInfo.AllowIntraBlockCopy) + if (!frameHeader.AllowIntraBlockCopy) { - writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.IsPresent); + writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsPresent); } - if (frameInfo.DeltaLoopFilterParameters.IsPresent) + if (frameHeader.DeltaLoopFilterParameters.IsPresent) { - writer.WriteLiteral((uint)frameInfo.DeltaLoopFilterParameters.Resolution, 2); - writer.WriteBoolean(frameInfo.DeltaLoopFilterParameters.IsMulti); + writer.WriteLiteral((uint)frameHeader.DeltaLoopFilterParameters.Resolution, 2); + writer.WriteBoolean(frameHeader.DeltaLoopFilterParameters.IsMulti); } } } @@ -621,10 +621,10 @@ private static void WriteQuantizationParameters(ref Av1BitStreamWriter writer, O } } - private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + private static void WriteSegmentationParameters(ref Av1BitStreamWriter writer, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { _ = sequenceHeader; - Guard.IsFalse(frameInfo.SegmentationParameters.Enabled, nameof(frameInfo.SegmentationParameters.Enabled), "Segmentation not supported yet."); + Guard.IsFalse(frameHeader.SegmentationParameters.Enabled, nameof(frameHeader.SegmentationParameters.Enabled), "Segmentation not supported yet."); writer.WriteBoolean(false); } @@ -664,11 +664,11 @@ private static void WriteLoopFilterParameters(ref Av1BitStreamWriter writer, Obu /// /// 5.9.21. TX mode syntax. /// - private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameInfo) + private static void WriteTransformMode(ref Av1BitStreamWriter writer, ObuFrameHeader frameHeader) { - if (!frameInfo.CodedLossless) + if (!frameHeader.CodedLossless) { - writer.WriteBoolean(frameInfo.TransformMode == Av1TransformMode.Select); + writer.WriteBoolean(frameHeader.TransformMode == Av1TransformMode.Select); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index 6d742c97c2..dbbbf4f50d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -56,7 +56,7 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInS public Av1IntraFilterModeInfo FilterIntraModeInfo { get; internal set; } /// - /// Gets the index of the first of this Mode Info in the . + /// Gets the index of the first of this Mode Info in the . /// public int[] FirstTransformLocation { get; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs similarity index 92% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs rename to src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs index 96643183dd..41011d93fd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs @@ -5,7 +5,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1FrameBuffer +/// +/// Collection of all information for a single frame. +/// +internal partial class Av1FrameInfo { // Number of Coefficients in a single ModeInfo 4x4 block of pixels (1 length + 4 x 4). public const int CoefficientCountPerModeInfo = 1 + 16; @@ -29,7 +32,7 @@ internal partial class Av1FrameBuffer private readonly int deltaLoopFactorLog2 = 2; private readonly int[] deltaLoopFilter; - public Av1FrameBuffer(ObuSequenceHeader sequenceHeader) + public Av1FrameInfo(ObuSequenceHeader sequenceHeader) { // init_main_frame_ctxt int superblockSizeLog2 = sequenceHeader.SuperblockSizeLog2; @@ -102,20 +105,28 @@ public Av1BlockModeInfo GetModeInfo(Point superblockIndex, Point modeInfoIndex) return this.modeInfos[index]; } - public Span GetSuperblockTransformY(Point index) + public ref Av1TransformInfo GetSuperblockTransform(int plane, Point index) + { + if (plane == 0) + { + return ref this.GetSuperblockTransformY(index); + } + + return ref this.GetSuperblockTransformUv(index); + } + + public ref Av1TransformInfo GetSuperblockTransformY(Point index) { Span span = this.transformInfosY; int offset = ((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock; - int length = this.modeInfoCountPerSuperblock; - return span.Slice(offset, length); + return ref span[offset]; } - public Span GetSuperblockTransformUv(Point index) + public ref Av1TransformInfo GetSuperblockTransformUv(Point index) { Span span = this.transformInfosUv; int offset = (((index.Y * this.superblockColumnCount) + index.X) * this.modeInfoCountPerSuperblock) << 1; - int length = this.modeInfoCountPerSuperblock << 1; - return span.Slice(offset, length); + return ref span[offset]; } public Span GetCoefficients(int plane) => diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs index 196a409352..6867b3966d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -3,10 +3,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1FrameBuffer +internal partial class Av1FrameInfo { /// - /// Mapping of instances, from position to index into the . + /// Mapping of instances, from position to index into the . /// /// /// For a visual representation of how this map looks in practice, see diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index c1e733bfbe..1c706c34e0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -75,7 +75,7 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI public int ModeBlockToBottomEdge => this.modeBlockToBottomEdge; - public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInfo) + public void ComputeBoundaryOffsets(ObuFrameHeader frameHeader, Av1TileInfo tileInfo) { Av1BlockSize blockSize = this.ModeInfo.BlockSize; int bw4 = blockSize.Get4x4WideCount(); @@ -86,9 +86,9 @@ public void ComputeBoundaryOffsets(ObuFrameHeader frameInfo, Av1TileInfo tileInf this.AvailableLeftForChroma = this.AvailableLeft; int shift = Av1Constants.ModeInfoSizeLog2 + 3; this.modeBlockToLeftEdge = -this.ColumnIndex << shift; - this.modeBlockToRightEdge = (frameInfo.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift; + this.modeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift; this.modeBlockToTopEdge = -this.RowIndex << shift; - this.modeBlockToBottomEdge = (frameInfo.ModeInfoRowCount - bh4 - this.RowIndex) << shift; + this.modeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift; } public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index bb5a90d0d0..f2484c264b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -5,12 +5,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SuperblockInfo { - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameInfo frameInfo; - public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) + public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position) { this.Position = position; - this.frameBuffer = frameBuffer; + this.frameInfo = frameInfo; } /// @@ -18,27 +18,29 @@ public Av1SuperblockInfo(Av1FrameBuffer frameBuffer, Point position) /// public Point Position { get; } - public ref int SuperblockDeltaQ => ref this.frameBuffer.GetDeltaQuantizationIndex(this.Position); + public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position); public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0)); - public Span CoefficientsY => this.frameBuffer.GetCoefficientsY(this.Position); + public Span CoefficientsY => this.frameInfo.GetCoefficientsY(this.Position); - public Span CoefficientsU => this.frameBuffer.GetCoefficientsU(this.Position); + public Span CoefficientsU => this.frameInfo.GetCoefficientsU(this.Position); - public Span CoefficientsV => this.frameBuffer.GetCoefficientsV(this.Position); + public Span CoefficientsV => this.frameInfo.GetCoefficientsV(this.Position); - public Span CdefStrength => this.frameBuffer.GetCdefStrength(this.Position); + public Span CdefStrength => this.frameInfo.GetCdefStrength(this.Position); - public Span SuperblockDeltaLoopFilter => this.frameBuffer.GetDeltaLoopFilter(this.Position); + public Span SuperblockDeltaLoopFilter => this.frameInfo.GetDeltaLoopFilter(this.Position); public int TransformInfoIndexY { get; internal set; } public int TransformInfoIndexUv { get; internal set; } - public Span GetTransformInfoY() => this.frameBuffer.GetSuperblockTransformY(this.Position); + public int BlockCount { get; internal set; } - public Span GetTransformInfoUv() => this.frameBuffer.GetSuperblockTransformUv(this.Position); + public ref Av1TransformInfo GetTransformInfoY() => ref this.frameInfo.GetSuperblockTransformY(this.Position); - public Av1BlockModeInfo GetModeInfo(Point index) => this.frameBuffer.GetModeInfo(this.Position, index); + public ref Av1TransformInfo GetTransformInfoUv() => ref this.frameInfo.GetSuperblockTransformUv(this.Position); + + public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index f4058b9183..4a5e503c83 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs @@ -7,10 +7,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1TileInfo { - public Av1TileInfo(int row, int column, ObuFrameHeader frameInfo) + public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader) { - this.SetTileRow(frameInfo.TilesInfo, frameInfo.ModeInfoRowCount, row); - this.SetTileColumn(frameInfo.TilesInfo, frameInfo.ModeInfoColumnCount, column); + this.SetTileRow(frameHeader.TilesInfo, frameHeader.ModeInfoRowCount, row); + this.SetTileColumn(frameHeader.TilesInfo, frameHeader.ModeInfoColumnCount, column); } public int ModeInfoRowStart { get; private set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 656cbc6055..47780069e8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -34,17 +35,17 @@ internal class Av1TileReader : IAv1TileReader private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; - public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) + public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { - this.FrameInfo = frameInfo; + this.FrameHeader = frameHeader; this.SequenceHeader = sequenceHeader; // init_main_frame_ctxt - this.FrameBuffer = new(this.SequenceHeader); - this.segmentIds = new int[this.FrameInfo.ModeInfoRowCount][]; - for (int y = 0; y < this.FrameInfo.ModeInfoRowCount; y++) + this.FrameInfo = new(this.SequenceHeader); + this.segmentIds = new int[this.FrameHeader.ModeInfoRowCount][]; + for (int y = 0; y < this.FrameHeader.ModeInfoRowCount; y++) { - this.segmentIds[y] = new int[this.FrameInfo.ModeInfoColumnCount]; + this.segmentIds[y] = new int[this.FrameHeader.ModeInfoColumnCount]; } // reallocate_parse_context_memory @@ -57,28 +58,28 @@ public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo) this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.SuperblockModeInfoSize); this.transformUnitCount = new int[Av1Constants.MaxPlanes][]; - this.transformUnitCount[0] = new int[this.FrameBuffer.ModeInfoCount]; - this.transformUnitCount[1] = new int[this.FrameBuffer.ModeInfoCount]; - this.transformUnitCount[2] = new int[this.FrameBuffer.ModeInfoCount]; + this.transformUnitCount[0] = new int[this.FrameInfo.ModeInfoCount]; + this.transformUnitCount[1] = new int[this.FrameInfo.ModeInfoCount]; + this.transformUnitCount[2] = new int[this.FrameInfo.ModeInfoCount]; this.coefficientIndex = new int[Av1Constants.MaxPlanes]; } - public ObuFrameHeader FrameInfo { get; } + public ObuFrameHeader FrameHeader { get; } public ObuSequenceHeader SequenceHeader { get; } - public Av1FrameBuffer FrameBuffer { get; } + public Av1FrameInfo FrameInfo { get; } public void ReadTile(Span tileData, int tileNum) { - Av1SymbolDecoder reader = new(tileData, this.FrameInfo.QuantizationParameters.BaseQIndex); - int tileColumnIndex = tileNum % this.FrameInfo.TilesInfo.TileColumnCount; - int tileRowIndex = tileNum / this.FrameInfo.TilesInfo.TileColumnCount; - - int modeInfoColumnStart = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; - int modeInfoColumnEnd = this.FrameInfo.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; - int modeInfoRowStart = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex]; - int modeInfoRowEnd = this.FrameInfo.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; + Av1SymbolDecoder reader = new(tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); + int tileColumnIndex = tileNum % this.FrameHeader.TilesInfo.TileColumnCount; + int tileRowIndex = tileNum / this.FrameHeader.TilesInfo.TileColumnCount; + + int modeInfoColumnStart = this.FrameHeader.TilesInfo.TileColumnStartModeInfo[tileColumnIndex]; + int modeInfoColumnEnd = this.FrameHeader.TilesInfo.TileColumnStartModeInfo[tileColumnIndex + 1]; + int modeInfoRowStart = this.FrameHeader.TilesInfo.TileRowStartModeInfo[tileRowIndex]; + int modeInfoRowEnd = this.FrameHeader.TilesInfo.TileRowStartModeInfo[tileRowIndex + 1]; this.aboveNeighborContext.Clear(this.SequenceHeader, modeInfoColumnStart, modeInfoColumnEnd); this.ClearLoopFilterDelta(); int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; @@ -98,7 +99,7 @@ public void ReadTile(Span tileData, int tileNum) } } - Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameInfo); + Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameHeader); Av1BlockSize superBlockSize = this.SequenceHeader.SuperblockSize; int superBlock4x4Size = this.SequenceHeader.SuperblockSizeLog2; for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += superBlock4x4Size) @@ -109,10 +110,10 @@ public void ReadTile(Span tileData, int tileNum) { int superBlockColumn = (column << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size; Point superblockPosition = new(superBlockColumn, superBlockRow); - Av1SuperblockInfo superblockInfo = this.FrameBuffer.GetSuperblock(superblockPosition); + Av1SuperblockInfo superblockInfo = this.FrameInfo.GetSuperblock(superblockPosition); Point modeInfoPosition = new(column, row); - this.FrameBuffer.ClearCdef(superblockPosition); + this.FrameInfo.ClearCdef(superblockPosition); this.firstTransformOffset[0] = 0; this.firstTransformOffset[1] = 0; this.ReadLoopRestoration(modeInfoPosition, superBlockSize); @@ -122,14 +123,14 @@ public void ReadTile(Span tileData, int tileNum) } private void ClearLoopFilterDelta() - => this.FrameBuffer.ClearDeltaLoopFilter(); + => this.FrameInfo.ClearDeltaLoopFilter(); private void ReadLoopRestoration(Point modeInfoLocation, Av1BlockSize superBlockSize) { int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int plane = 0; plane < planesCount; plane++) { - if (this.FrameInfo.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None) + if (this.FrameHeader.LoopRestorationParameters.Items[plane].Type != ObuRestorationType.None) { // TODO: Implement. throw new NotImplementedException("No loop restoration filter support."); @@ -144,7 +145,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, { int columnIndex = modeInfoLocation.X; int rowIndex = modeInfoLocation.Y; - if (modeInfoLocation.Y >= this.FrameInfo.ModeInfoRowCount || modeInfoLocation.X >= this.FrameInfo.ModeInfoColumnCount) + if (modeInfoLocation.Y >= this.FrameHeader.ModeInfoRowCount || modeInfoLocation.X >= this.FrameHeader.ModeInfoColumnCount) { return; } @@ -152,8 +153,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, int block4x4Size = blockSize.Get4x4WideCount(); int halfBlock4x4Size = block4x4Size >> 1; int quarterBlock4x4Size = halfBlock4x4Size >> 1; - bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameInfo.ModeInfoRowCount; - bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameInfo.ModeInfoColumnCount; + bool hasRows = (modeInfoLocation.Y + halfBlock4x4Size) < this.FrameHeader.ModeInfoRowCount; + bool hasColumns = (modeInfoLocation.X + halfBlock4x4Size) < this.FrameHeader.ModeInfoColumnCount; Av1PartitionType partitionType = Av1PartitionType.None; if (blockSize >= Av1BlockSize.Block8x8) { @@ -245,7 +246,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, for (int i = 0; i < 4; i++) { int currentBlockRow = rowIndex + (i * quarterBlock4x4Size); - if (i > 0 && currentBlockRow > this.FrameInfo.ModeInfoRowCount) + if (i > 0 && currentBlockRow > this.FrameHeader.ModeInfoRowCount) { break; } @@ -259,7 +260,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, for (int i = 0; i < 4; i++) { int currentBlockColumn = columnIndex + (i * quarterBlock4x4Size); - if (i > 0 && currentBlockColumn > this.FrameInfo.ModeInfoColumnCount) + if (i > 0 && currentBlockColumn > this.FrameHeader.ModeInfoColumnCount) { break; } @@ -289,11 +290,12 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 blockModeInfo.PartitionType = partitionType; blockModeInfo.FirstTransformLocation[0] = this.firstTransformOffset[0]; blockModeInfo.FirstTransformLocation[1] = this.firstTransformOffset[1]; - bool hasChroma = this.HasChroma(modeInfoLocation, blockSize); + bool hasChroma = HasChroma(this.SequenceHeader, modeInfoLocation, blockSize); Av1PartitionInfo partitionInfo = new(blockModeInfo, superblockInfo, hasChroma, partitionType); partitionInfo.ColumnIndex = columnIndex; partitionInfo.RowIndex = rowIndex; - partitionInfo.ComputeBoundaryOffsets(this.FrameInfo, tileInfo); + superblockInfo.BlockCount++; + partitionInfo.ComputeBoundaryOffsets(this.FrameHeader, tileInfo); if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) @@ -328,7 +330,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize); // Update the Frame buffer for this ModeInfo. - this.FrameBuffer.UpdateModeInfo(blockModeInfo, superblockInfo); + this.FrameInfo.UpdateModeInfo(blockModeInfo, superblockInfo); } private void ResetSkipContext(Av1PartitionInfo partitionInfo) @@ -342,8 +344,8 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) DebugGuard.IsTrue(planeBlockSize != Av1BlockSize.Invalid, nameof(planeBlockSize)); int txsWide = planeBlockSize.GetWidth() >> 2; int txsHigh = planeBlockSize.GetHeight() >> 2; - int aboveOffset = (partitionInfo.ColumnIndex - this.FrameInfo.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> subX; - int leftOffset = (partitionInfo.RowIndex - this.FrameInfo.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> subY; + int aboveOffset = (partitionInfo.ColumnIndex - this.FrameHeader.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> subX; + int leftOffset = (partitionInfo.RowIndex - this.FrameHeader.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> subY; this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide); this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh); } @@ -362,7 +364,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf modeUnitBlocksWide = Math.Min(maxBlocksWide, modeUnitBlocksWide); modeUnitBlocksHigh = Math.Min(maxBlocksHigh, modeUnitBlocksHigh); int planeCount = this.SequenceHeader.ColorConfig.PlaneCount; - bool isLossless = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; + bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool isLosslessBlock = isLossless && (blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128); int subSampling = (this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0) + (this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0); int chromaTransformUnitCount = isLosslessBlock ? ((maxBlocksWide * maxBlocksHigh) >> subSampling) : partitionInfo.ModeInfo.TransformUnitsCount[(int)Av1PlaneType.Uv]; @@ -389,13 +391,13 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf continue; } - Span transformInfos = plane == 0 ? superblockInfo.GetTransformInfoY() : superblockInfo.GetTransformInfoUv(); + ref Av1TransformInfo transformInfoRef = ref (plane == 0) ? ref superblockInfo.GetTransformInfoY() : ref superblockInfo.GetTransformInfoUv(); if (isLosslessBlock) { // TODO: Implement. int unitHeight = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksHigh + row, maxBlocksHigh), 0); int unitWidth = Av1Math.RoundPowerOf2(Math.Min(modeUnitBlocksWide + column, maxBlocksWide), 0); - DebugGuard.IsTrue(transformInfos[transformInfoIndices[plane]].Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4."); + DebugGuard.IsTrue(Unsafe.Add(ref transformInfoRef, transformInfoIndices[plane]).Size == Av1TransformSize.Size4x4, "Lossless frame shall have transform units of size 4x4."); transformUnitCount = ((unitWidth - column) * (unitHeight - row)) >> (subX + subY); } else @@ -415,7 +417,7 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf DebugGuard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), string.Empty); for (int tu = 0; tu < transformUnitCount; tu++) { - Av1TransformInfo transformInfo = transformInfos[transformInfoIndices[plane]]; + Av1TransformInfo transformInfo = Unsafe.Add(ref transformInfoRef, transformInfoIndices[plane]); DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetX, maxBlocksWide, nameof(transformInfo)); DebugGuard.MustBeLessThanOrEqualTo(transformInfo.OffsetY, maxBlocksHigh, nameof(transformInfo)); @@ -426,8 +428,8 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf int startX = (partitionInfo.ColumnIndex >> subX) + blockColumn; int startY = (partitionInfo.RowIndex >> subY) + blockRow; - if (startX >= (this.FrameInfo.ModeInfoColumnCount >> subX) || - startY >= (this.FrameInfo.ModeInfoRowCount >> subY)) + if (startX >= (this.FrameHeader.ModeInfoColumnCount >> subX) || + startY >= (this.FrameHeader.ModeInfoRowCount >> subY)) { return; } @@ -456,12 +458,12 @@ private void Residual(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf } } - private bool HasChroma(Point modeInfoLocation, Av1BlockSize blockSize) + public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLocation, Av1BlockSize blockSize) { int blockWide = blockSize.Get4x4WideCount(); int blockHigh = blockSize.Get4x4HighCount(); - bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; + bool subX = sequenceHeader.ColorConfig.SubSamplingX; + bool subY = sequenceHeader.ColorConfig.SubSamplingY; bool hasChroma = ((modeInfoLocation.Y & 0x01) != 0 || (blockHigh & 0x01) == 0 || !subY) && ((modeInfoLocation.X & 0x01) != 0 || (blockWide & 0x01) == 0 || !subX); return hasChroma; @@ -534,7 +536,7 @@ private int ParseTransformBlock( /// private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int plane, Av1TransformBlockContext transformBlockContext, Av1TransformSize transformSize, int coefficientIndex, Av1TransformInfo transformInfo) { - Span coefficientBuffer = this.FrameBuffer.GetCoefficients(plane); + Span coefficientBuffer = this.FrameInfo.GetCoefficients(plane); int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); @@ -955,7 +957,7 @@ private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockE private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo) { Av1TransformType transformType = Av1TransformType.DctDct; - if (this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) + if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) { transformType = Av1TransformType.DctDct; } @@ -973,7 +975,7 @@ private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1Partiti } } - Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameInfo.UseReducedTransformSet); + Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameHeader.UseReducedTransformSet); if (!transformType.IsExtendedSetUsed(transformSetType)) { transformType = Av1TransformType.DctDct; @@ -1219,12 +1221,12 @@ private static int GetEntropyContext(Av1TransformSize transformSize, int[] above private Av1TransformSize ReadTransformSize(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo, bool allowSelect) { Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; - if (this.FrameInfo.LosslessArray[modeInfo.SegmentId]) + if (this.FrameHeader.LosslessArray[modeInfo.SegmentId]) { return Av1TransformSize.Size4x4; } - if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameInfo.TransformMode == Av1TransformMode.Select) + if (modeInfo.BlockSize > Av1BlockSize.Block4x4 && allowSelect && this.FrameHeader.TransformMode == Av1TransformMode.Select) { return this.ReadSelectedTransformSize(ref reader, partitionInfo, superblockInfo, tileInfo); } @@ -1283,8 +1285,8 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { int transformInfoYIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Y]; int transformInfoUvIndex = partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv]; - Span lumaTransformInfo = superblockInfo.GetTransformInfoY(); - Span chromaTransformInfo = superblockInfo.GetTransformInfoUv(); + ref Av1TransformInfo lumaTransformInfo = ref superblockInfo.GetTransformInfoY(); + ref Av1TransformInfo chromaTransformInfo = ref superblockInfo.GetTransformInfoUv(); int totalLumaTransformUnitCount = 0; int totalChromaTransformUnitCount = 0; int forceSplitCount = 0; @@ -1297,7 +1299,7 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super width = Math.Min(width, maxBlockWide); height = Math.Min(height, maxBlockHigh); - bool isLossLess = this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]; + bool isLossLess = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; Av1TransformSize transformSizeUv = isLossLess ? Av1TransformSize.Size4x4 : blockSize.GetMaxUvTransformSize(subX, subY); for (int idy = 0; idy < maxBlockHigh; idy += height) @@ -1317,7 +1319,7 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - lumaTransformInfo[transformInfoYIndex] = new Av1TransformInfo( + Unsafe.Add(ref lumaTransformInfo, transformInfoYIndex) = new Av1TransformInfo( transformSize, blockColumn, blockRow); transformInfoYIndex++; lumaTransformUnitCount++; @@ -1342,7 +1344,7 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super { for (int blockColumn = idx; blockColumn < unitWidth; blockColumn += stepColumn) { - chromaTransformInfo[transformInfoUvIndex] = new Av1TransformInfo( + Unsafe.Add(ref chromaTransformInfo, transformInfoUvIndex) = new Av1TransformInfo( transformSizeUv, blockColumn, blockRow); transformInfoUvIndex++; chromaTransformUnitCount++; @@ -1363,9 +1365,13 @@ private unsafe void UpdateTransformInfo(Av1PartitionInfo partitionInfo, Av1Super partitionInfo.ModeInfo.FirstTransformLocation[(int)Av1PlaneType.Uv], nameof(totalChromaTransformUnitCount)); int originalIndex = transformInfoUvIndex - totalChromaTransformUnitCount; + ref Av1TransformInfo originalInfo = ref Unsafe.Add(ref chromaTransformInfo, originalIndex); + ref Av1TransformInfo infoV = ref Unsafe.Add(ref chromaTransformInfo, transformInfoUvIndex); for (int i = 0; i < totalChromaTransformUnitCount; i++) { - chromaTransformInfo[transformInfoUvIndex + i] = chromaTransformInfo[originalIndex + i]; + infoV = originalInfo; + originalInfo = ref Unsafe.Add(ref originalInfo, 1); + infoV = ref Unsafe.Add(ref infoV, 1); } } @@ -1399,7 +1405,7 @@ private static void ReadPaletteTokens(ref Av1SymbolDecoder reader, Av1PartitionI /// private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - DebugGuard.IsTrue(this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); + DebugGuard.IsTrue(this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame, "Only INTRA frames supported."); this.ReadIntraFrameModeInfo(ref reader, partitionInfo); } @@ -1408,21 +1414,21 @@ private void ReadModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitio /// private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) { this.IntraSegmentId(ref reader, partitionInfo); } // this.skipMode = false; partitionInfo.ModeInfo.Skip = this.ReadSkip(ref reader, partitionInfo); - if (!this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip) + if (!this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip) { this.IntraSegmentId(ref reader, partitionInfo); } this.ReadCdef(ref reader, partitionInfo); - if (this.FrameInfo.DeltaQParameters.IsPresent) + if (this.FrameHeader.DeltaQParameters.IsPresent) { this.ReadDeltaQuantizerIndex(ref reader, partitionInfo); this.ReadDeltaLoopFilter(ref reader, partitionInfo); @@ -1468,7 +1474,7 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf if (partitionInfo.ModeInfo.BlockSize >= Av1BlockSize.Block8x8 && partitionInfo.ModeInfo.BlockSize.GetWidth() <= 64 && partitionInfo.ModeInfo.BlockSize.GetHeight() <= 64 && - this.FrameInfo.AllowScreenContentTools) + this.FrameHeader.AllowScreenContentTools) { this.PaletteModeInfo(ref reader, partitionInfo); } @@ -1478,13 +1484,13 @@ private void ReadIntraFrameModeInfo(ref Av1SymbolDecoder reader, Av1PartitionInf } private bool AllowIntraBlockCopy() - => (this.FrameInfo.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && + => (this.FrameHeader.FrameType is ObuFrameType.KeyFrame or ObuFrameType.IntraOnlyFrame) && (this.SequenceHeader.ForceScreenContentTools > 0) && - this.FrameInfo.AllowIntraBlockCopy; + this.FrameHeader.AllowIntraBlockCopy; private bool IsChromaForLumaAllowed(Av1PartitionInfo partitionInfo) { - if (this.FrameInfo.LosslessArray[partitionInfo.ModeInfo.SegmentId]) + if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]) { // In lossless, CfL is available when the partition size is equal to the // transform size. @@ -1569,15 +1575,15 @@ private static bool IsDirectionalMode(Av1PredictionMode mode) /// private void IntraSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - if (this.FrameInfo.SegmentationParameters.Enabled) + if (this.FrameHeader.SegmentationParameters.Enabled) { this.ReadSegmentId(ref reader, partitionInfo); } int blockWidth4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); int blockHeight4x4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); - int modeInfoCountX = Math.Min(this.FrameInfo.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4); - int modeInfoCountY = Math.Min(this.FrameInfo.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4); + int modeInfoCountX = Math.Min(this.FrameHeader.ModeInfoColumnCount - partitionInfo.ColumnIndex, blockWidth4x4); + int modeInfoCountY = Math.Min(this.FrameHeader.ModeInfoRowCount - partitionInfo.RowIndex, blockHeight4x4); int segmentId = partitionInfo.ModeInfo.SegmentId; for (int y = 0; y < modeInfoCountY; y++) { @@ -1637,18 +1643,18 @@ private void ReadSegmentId(ref Av1SymbolDecoder reader, Av1PartitionInfo partiti int ctx = prevUL < 0 ? 0 /* Edge cases */ : prevUL == prevU && prevUL == prevL ? 2 : prevUL == prevU || prevUL == prevL || prevU == prevL ? 1 : 0; - int lastActiveSegmentId = this.FrameInfo.SegmentationParameters.LastActiveSegmentId; + int lastActiveSegmentId = this.FrameHeader.SegmentationParameters.LastActiveSegmentId; partitionInfo.ModeInfo.SegmentId = NegativeDeinterleave(reader.ReadSegmentId(ctx), predictor, lastActiveSegmentId + 1); } } private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int columnIndex) { - int modeInfoOffset = (rowIndex * this.FrameInfo.ModeInfoColumnCount) + columnIndex; + int modeInfoOffset = (rowIndex * this.FrameHeader.ModeInfoColumnCount) + columnIndex; int bw4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); - int xMin = Math.Min(this.FrameInfo.ModeInfoColumnCount - columnIndex, bw4); - int yMin = Math.Min(this.FrameInfo.ModeInfoRowCount - rowIndex, bh4); + int xMin = Math.Min(this.FrameHeader.ModeInfoColumnCount - columnIndex, bw4); + int yMin = Math.Min(this.FrameHeader.ModeInfoRowCount - rowIndex, bh4); int segmentId = Av1Constants.MaxSegments - 1; for (int y = 0; y < yMin; y++) { @@ -1712,7 +1718,7 @@ private static int NegativeDeinterleave(int diff, int reference, int max) /// private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { - if (partitionInfo.ModeInfo.Skip || this.FrameInfo.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameInfo.AllowIntraBlockCopy) + if (partitionInfo.ModeInfo.Skip || this.FrameHeader.CodedLossless || !this.SequenceHeader.EnableCdef || this.FrameHeader.AllowIntraBlockCopy) { return; } @@ -1723,7 +1729,7 @@ private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf int c = partitionInfo.ColumnIndex & cdefMask4; if (partitionInfo.CdefStrength[r][c] == -1) { - partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameInfo.CdefParameters.BitCount); + partitionInfo.CdefStrength[r][c] = reader.ReadLiteral(this.FrameHeader.CdefParameters.BitCount); if (this.SequenceHeader.SuperblockSize == Av1BlockSize.Block128x128) { int w4 = partitionInfo.ModeInfo.BlockSize.Get4x4WideCount(); @@ -1742,16 +1748,16 @@ private void ReadCdef(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent || + if (this.FrameHeader.DeltaLoopFilterParameters.IsPresent || (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; } - if (this.FrameInfo.DeltaLoopFilterParameters.IsPresent) + if (this.FrameHeader.DeltaLoopFilterParameters.IsPresent) { int frameLoopFilterCount = 1; - if (this.FrameInfo.DeltaLoopFilterParameters.IsMulti) + if (this.FrameHeader.DeltaLoopFilterParameters.IsMulti) { frameLoopFilterCount = this.SequenceHeader.ColorConfig.PlaneCount > 1 ? Av1Constants.FrameLoopFilterCount : Av1Constants.FrameLoopFilterCount - 2; } @@ -1771,7 +1777,7 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p { bool deltaLoopFilterSign = reader.ReadLiteral(1) > 0; int reducedDeltaLoopFilterLevel = deltaLoopFilterSign ? -deltaLoopFilterAbsolute : deltaLoopFilterAbsolute; - int deltaLoopFilterResolution = this.FrameInfo.DeltaLoopFilterParameters.Resolution; + int deltaLoopFilterResolution = this.FrameHeader.DeltaLoopFilterParameters.Resolution; currentDeltaLoopFilter[i] = Av1Math.Clip3(-Av1Constants.MaxLoopFilter, Av1Constants.MaxLoopFilter, currentDeltaLoopFilter[i] + (reducedDeltaLoopFilterLevel << deltaLoopFilterResolution)); } } @@ -1781,8 +1787,8 @@ private void ReadDeltaLoopFilter(ref Av1SymbolDecoder reader, Av1PartitionInfo p private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { int segmentId = partitionInfo.ModeInfo.SegmentId; - if (this.FrameInfo.SegmentationParameters.SegmentIdPrecedesSkip && - this.FrameInfo.SegmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.Skip)) + if (this.FrameHeader.SegmentationParameters.SegmentIdPrecedesSkip && + this.FrameHeader.SegmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.Skip)) { return true; } @@ -1797,7 +1803,7 @@ private bool ReadSkip(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInf private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionInfo partitionInfo) { Av1BlockSize superBlockSize = this.SequenceHeader.Use128x128Superblock ? Av1BlockSize.Block128x128 : Av1BlockSize.Block64x64; - if (!this.FrameInfo.DeltaQParameters.IsPresent || + if (!this.FrameHeader.DeltaQParameters.IsPresent || (partitionInfo.ModeInfo.BlockSize == superBlockSize && partitionInfo.ModeInfo.Skip)) { return; @@ -1817,7 +1823,7 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn { bool deltaQuantizerSignBit = reader.ReadLiteral(1) > 0; int reducedDeltaQuantizerIndex = deltaQuantizerSignBit ? -deltaQuantizerAbsolute : deltaQuantizerAbsolute; - int deltaQuantizerResolution = this.FrameInfo.DeltaQParameters.Resolution; + int deltaQuantizerResolution = this.FrameHeader.DeltaQParameters.Resolution; this.currentQuantizerIndex = Av1Math.Clip3(1, 255, this.currentQuantizerIndex + (reducedDeltaQuantizerIndex << deltaQuantizerResolution)); partitionInfo.SuperblockInfo.SuperblockDeltaQ = this.currentQuantizerIndex; } @@ -1825,10 +1831,10 @@ private void ReadDeltaQuantizerIndex(ref Av1SymbolDecoder reader, Av1PartitionIn } private bool IsInside(int rowIndex, int columnIndex) => - columnIndex >= this.FrameInfo.TilesInfo.TileColumnCount && - columnIndex < this.FrameInfo.TilesInfo.TileColumnCount && - rowIndex >= this.FrameInfo.TilesInfo.TileRowCount && - rowIndex < this.FrameInfo.TilesInfo.TileRowCount; + columnIndex >= this.FrameHeader.TilesInfo.TileColumnCount && + columnIndex < this.FrameHeader.TilesInfo.TileColumnCount && + rowIndex >= this.FrameHeader.TilesInfo.TileRowCount && + rowIndex < this.FrameHeader.TilesInfo.TileRowCount; /* private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blockMode, bool subSamplingX, bool subSamplingY) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs index 040ce87b9c..981075d83f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs @@ -10,20 +10,20 @@ internal class Av1FrameDecoder { private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameInfo frameInfo; - public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer) + public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; - this.frameBuffer = frameBuffer; + this.frameInfo = frameInfo; } public void DecodeFrame() { Guard.NotNull(this.sequenceHeader); Guard.NotNull(this.frameHeader); - Guard.NotNull(this.frameBuffer); + Guard.NotNull(this.frameInfo); // TODO: Implement. } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs index 312891c85e..c3213378cb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; From 4f4ace4231e42cce33d5f60b25dac1e9e65d7bd6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 19 Aug 2024 20:45:23 +0200 Subject: [PATCH 153/190] Skeleton code for pixel pipeline --- .../Formats/Heif/Av1/Av1BitDepthExtensions.cs | 11 + .../Formats/Heif/Av1/Av1Constants.cs | 10 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 2 +- .../Formats/Heif/Av1/Av1FrameBuffer.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuColorConfig.cs | 2 +- .../Av1/OpenBitstreamUnit/ObuPartitionInfo.cs | 8 - .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 27 +- .../Heif/Av1/OpenBitstreamUnit/ObuWriter.cs | 6 +- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 155 ++++++ .../LoopFilter/Av1LoopFilterContext.cs | 8 + .../LoopFilter/Av1LoopFilterDecoder.cs | 68 +++ .../Quantification/Av1InverseQuantizer.cs | 146 ++++++ .../Av1QuantizationConstants.cs | 483 ++++++++++++++++++ .../Av1/Pipeline/Quantification/DeQuant.cs | 33 ++ .../Quantification/QuantizationLookup.cs | 190 +++++++ src/ImageSharp/Formats/Heif/Av1/Readme.md | 2 + .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Heif/Av1/Transform/Av1BlockDecoder.cs | 192 +++++++ .../Heif/Av1/Transform/Av1FrameDecoder.cs | 30 -- .../Heif/Av1/Transform/Av1InverseQuantizer.cs | 74 --- .../Transform/Av1TransformSizeExtensions.cs | 2 + .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 2 +- 22 files changed, 1310 insertions(+), 145 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs new file mode 100644 index 0000000000..615ed0b8d5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Av1BitDepthExtensions.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1; + +internal static class Av1BitDepthExtensions +{ + public static int GetBitCount(this Av1BitDepth bitDepth) => 8 + ((int)bitDepth << 1); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index fb8203ae70..cb55ea1ad0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -114,11 +114,6 @@ internal static class Av1Constants /// public const int MaxAngleDelta = 3; - /// - /// Number of segments allowed in segmentation map. - /// - public const int MaxSegments = 8; - /// /// Maximum number of color planes. /// @@ -173,4 +168,9 @@ internal static class Av1Constants /// Log2 of number of values for ChromaFromLuma Alpha U and ChromaFromLuma Alpha V. ///
public const int ChromaFromLumaAlphabetSizeLog2 = 4; + + /// + /// Total number of Quantification Matrices sets stored. + /// + public const int QuantificationMatrixLevelCount = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index ac753a5da6..ff26b2e5cc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -2,8 +2,8 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1; diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs index 1267502eb8..a6a0f77954 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs @@ -23,7 +23,7 @@ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHea Av1ColorFormat colorFormat = sequenceHeader.ColorConfig.IsMonochrome ? Av1ColorFormat.Yuv400 : maxColorFormat; this.MaxWidth = sequenceHeader.MaxFrameWidth; this.MaxHeight = sequenceHeader.MaxFrameHeight; - this.BitDepth = (Av1BitDepth)sequenceHeader.ColorConfig.BitDepth; + this.BitDepth = sequenceHeader.ColorConfig.BitDepth; int bitsPerPixel = this.BitDepth > Av1BitDepth.EightBit || is16BitPipeline ? 2 : 1; this.ColorFormat = colorFormat; this.BufferEnableMask = sequenceHeader.ColorConfig.IsMonochrome ? PictureBufferLumaMask : PictureBufferFullMask; diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs index 7e169f43fd..24e7237a29 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuColorConfig.cs @@ -44,7 +44,7 @@ public bool IsMonochrome public ObuChromoSamplePosition ChromaSamplePosition { get; set; } - public int BitDepth { get; set; } + public Av1BitDepth BitDepth { get; set; } public Av1ColorFormat GetColorFormat() { diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs deleted file mode 100644 index 35afda11f9..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuPartitionInfo.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; - -internal class ObuPartitionInfo -{ -} diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index c933055afe..22eea140c8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -78,7 +79,7 @@ public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileReader case ObuType.SequenceHeader: this.SequenceHeader = new(); ReadSequenceHeader(ref reader, this.SequenceHeader); - if (this.SequenceHeader.ColorConfig.BitDepth == 12) + if (this.SequenceHeader.ColorConfig.BitDepth == Av1BitDepth.TwelveBit) { // TODO: Initialize 12 bit predictors } @@ -503,7 +504,7 @@ private static ObuColorConfig ReadColorConfig(ref Av1BitStreamReader reader, Obu break; case ObuSequenceProfile.Professional: default: - if (colorConfig.BitDepth == 12) + if (colorConfig.BitDepth == Av1BitDepth.TwelveBit) { colorConfig.SubSamplingX = reader.ReadBoolean(); if (colorConfig.SubSamplingX) @@ -564,15 +565,15 @@ private static void ReadBitDepth(ref Av1BitStreamReader reader, ObuColorConfig c bool hasHighBitDepth = reader.ReadBoolean(); if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) { - colorConfig.BitDepth = reader.ReadBoolean() ? 12 : 10; + colorConfig.BitDepth = reader.ReadBoolean() ? Av1BitDepth.TwelveBit : Av1BitDepth.TenBit; } else if (sequenceHeader.SequenceProfile <= ObuSequenceProfile.Professional) { - colorConfig.BitDepth = hasHighBitDepth ? 10 : 8; + colorConfig.BitDepth = hasHighBitDepth ? Av1BitDepth.TenBit : Av1BitDepth.EightBit; } else { - colorConfig.BitDepth = 8; + colorConfig.BitDepth = Av1BitDepth.EightBit; } } @@ -1091,7 +1092,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount]; for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) { - int qIndex = GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex); + int qIndex = QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex); frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex; frameHeader.LosslessArray[segmentId] = qIndex == 0 && frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && @@ -1151,20 +1152,6 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) private static bool IsSegmentationFeatureActive(ObuSegmentationParameters segmentationParameters, int segmentId, ObuSegmentationLevelFeature feature) => segmentationParameters.Enabled && segmentationParameters.IsFeatureActive(segmentId, feature); - private static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) - { - if (IsSegmentationFeatureActive(segmentationParameters, segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) - { - int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; - int qIndex = baseQIndex + data; - return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ); - } - else - { - return baseQIndex; - } - } - /// /// 5.9.1. General frame header OBU syntax. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs index 16452799d7..f718eedd6e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuWriter.cs @@ -164,7 +164,7 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH else { writer.WriteBoolean(colorConfig.ColorRange); - if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == 12) + if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && colorConfig.BitDepth == Av1BitDepth.TwelveBit) { writer.WriteBoolean(colorConfig.SubSamplingX); if (colorConfig.SubSamplingX) @@ -184,11 +184,11 @@ private static void WriteColorConfig(ref Av1BitStreamWriter writer, ObuSequenceH private static void WriteBitDepth(ref Av1BitStreamWriter writer, ObuColorConfig colorConfig, ObuSequenceHeader sequenceHeader) { - bool hasHighBitDepth = colorConfig.BitDepth > 8; + bool hasHighBitDepth = colorConfig.BitDepth > Av1BitDepth.EightBit; writer.WriteBoolean(hasHighBitDepth); if (sequenceHeader.SequenceProfile == ObuSequenceProfile.Professional && hasHighBitDepth) { - writer.WriteBoolean(colorConfig.BitDepth == 12); + writer.WriteBoolean(colorConfig.BitDepth == Av1BitDepth.TwelveBit); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs new file mode 100644 index 0000000000..b4618c7a1f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; + +internal class Av1FrameDecoder +{ + private readonly ObuSequenceHeader sequenceHeader; + private readonly ObuFrameHeader frameHeader; + private readonly Av1FrameInfo frameInfo; + private readonly Av1InverseQuantizer inverseQuantizer; + private readonly DeQuant deQuants; + + public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + this.frameInfo = frameInfo; + this.inverseQuantizer = new(sequenceHeader, frameHeader); + this.deQuants = new(); + } + + public void DecodeFrame() + { + for (int column = 0; column < this.frameHeader.TilesInfo.TileColumnCount; column++) + { + this.DecodeFrameTiles(column); + } + + bool doLoopFilterFlag = false; + bool doLoopRestoration = false; + bool doUpscale = false; + this.DecodeLoopFilterForFrame(doLoopFilterFlag); + if (doLoopRestoration) + { + // LoopRestorationSaveBoundaryLines(false); + } + + // DecodeCdef(); + // SuperResolutionUpscaling(doUpscale); + if (doLoopRestoration && doUpscale) + { + // LoopRestorationSaveBoundaryLines(true); + } + + // DecodeLoopRestoration(doLoopRestoration); + // PadPicture(); + } + + private void DecodeFrameTiles(int tileColumn) + { + int tileRowCount = this.frameHeader.TilesInfo.TileRowCount; + int tileCount = tileRowCount * this.frameHeader.TilesInfo.TileColumnCount; + for (int row = 0; row < tileRowCount; row++) + { + int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[row] << Av1Constants.ModeInfoSizeLog2 >> + this.sequenceHeader.SuperblockSizeLog2; + int superblockRow = row + superblockRowTileStart; + + int modeInfoRow = superblockRow << this.sequenceHeader.SuperblockSizeLog2 >> Av1Constants.ModeInfoSizeLog2; + + // EbColorConfig* color_config = &dec_mod_ctxt->seq_header->color_config; + // svt_cfl_init(&dec_mod_ctxt->cfl_ctx, color_config); + this.DecodeTileRow(row, tileColumn, modeInfoRow, superblockRow); + } + } + + private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int superblockRow) + { + int superblockModeInfoSizeLog2 = this.sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; + int superblockRowTileStart = this.frameHeader.TilesInfo.TileRowStartModeInfo[tileRow] << Av1Constants.ModeInfoSizeLog2 >> + this.sequenceHeader.SuperblockSizeLog2; + + int superblockRowInTile = superblockRow - superblockRowTileStart; + + ObuTileGroupHeader tileInfo = this.frameHeader.TilesInfo; + for (int modeInfoColumn = tileInfo.TileColumnStartModeInfo[tileColumn]; modeInfoColumn < tileInfo.TileColumnStartModeInfo[tileColumn + 1]; + modeInfoColumn += this.sequenceHeader.SuperblockModeInfoSize) + { + int superblockColumn = modeInfoColumn << Av1Constants.ModeInfoSizeLog2 >> this.sequenceHeader.SuperblockSizeLog2; + + Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow)); + + Point modeInfoPosition = new Point(modeInfoColumn, modeInfoRow); + this.DecodeSuperblock(modeInfoPosition, superblockInfo); + } + } + + private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo) + { + this.inverseQuantizer.UpdateDequant(this.deQuants, superblockInfo); + DecodePartition(modeInfoPosition, superblockInfo); + } + + private static void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo) + { + Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition); + + for (int i = 0; i < superblockInfo.BlockCount; i++) + { + Point subPosition = modeInfo.PositionInSuperblock; + Av1BlockSize subSize = modeInfo.BlockSize; + Point globalPosition = new(modeInfoPosition.X, modeInfoPosition.Y); + globalPosition.Offset(subPosition); + Av1BlockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo); + } + } + + private void DecodeLoopFilterForFrame(bool doLoopFilterFlag) + { + if (!doLoopFilterFlag) + { + return; + } + + int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2; + int pictureWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2); + int pictureHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2); + + // Loop over a frame : tregger dec_loop_filter_sb for each SB + for (int superblockIndexY = 0; superblockIndexY < pictureHeightInSuperblocks; ++superblockIndexY) + { + for (int superblockIndexX = 0; superblockIndexX < pictureWidthInSuperblocks; ++superblockIndexX) + { + int superblockOriginX = superblockIndexX << superblockSizeLog2; + int superblockOriginY = superblockIndexY << superblockSizeLog2; + bool endOfRowFlag = superblockIndexX == pictureWidthInSuperblocks - 1; + + Point superblockPoint = new(superblockOriginX, superblockOriginY); + Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint); + + // LF function for a SB + /* + DecodeLoopFilterForSuperblock( + superblockInfo, + this.frameHeader, + this.sequenceHeader, + reconstructionFrameBuffer, + loopFilterContext, + superblockOriginY >> 2, + superblockOriginX >> 2, + Av1Plane.Y, + 3, + endOfRowFlag, + superblockInfo.SuperblockDeltaLoopFilter); + */ + } + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs new file mode 100644 index 0000000000..91b036ff6a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; + +internal class Av1LoopFilterContext +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs new file mode 100644 index 0000000000..9a895ea7e5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs @@ -0,0 +1,68 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; + +internal class Av1LoopFilterDecoder +{ + private readonly ObuSequenceHeader sequenceHeader; + private readonly ObuFrameHeader frameHeader; + private readonly Av1FrameInfo frameInfo; + private readonly Av1FrameBuffer frameBuffer; + private readonly Av1LoopFilterContext loopFilterContext; + + public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + this.frameInfo = frameInfo; + this.frameBuffer = frameBuffer; + this.loopFilterContext = new(); + } + + public void DecodeFrame(bool doLoopFilterFlag) + { + Guard.NotNull(this.sequenceHeader); + Guard.NotNull(this.frameHeader); + Guard.NotNull(this.frameInfo); + + if (!doLoopFilterFlag) + { + return; + } + + int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2; + int frameWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2); + int frameHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2); + + // Loop over a frame : tregger dec_loop_filter_sb for each SB + for (int superblockIndexY = 0; superblockIndexY < frameHeightInSuperblocks; ++superblockIndexY) + { + for (int superblockIndexX = 0; superblockIndexX < frameWidthInSuperblocks; ++superblockIndexX) + { + int superblockOriginX = superblockIndexX << superblockSizeLog2; + int superblockOriginY = superblockIndexY << superblockSizeLog2; + bool endOfRowFlag = superblockIndexX == frameWidthInSuperblocks - 1; + + Point superblockPoint = new(superblockOriginX, superblockOriginY); + Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(superblockPoint); + Point superblockOriginInModeInfo = new(superblockOriginX >> 2, superblockOriginY >> 2); + + // LF function for a SB + this.DecodeForSuperblock( + superblockInfo, + superblockOriginInModeInfo, + Av1Plane.Y, + 3, + endOfRowFlag, + superblockInfo.SuperblockDeltaLoopFilter); + } + } + } + + private void DecodeForSuperblock(Av1SuperblockInfo superblockInfo, Point modeInfoLocation, Av1Plane startPlane, int endPlane, bool endOfRowFlag, Span superblockDeltaLoopFilter) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs new file mode 100644 index 0000000000..550c09b951 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -0,0 +1,146 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; + +internal class Av1InverseQuantizer +{ + private const int QuatizationMatrixTotalSize = 3344; + + private readonly ObuSequenceHeader sequenceHeader; + private readonly ObuFrameHeader frameHeader; + private readonly int[][][] inverseQuantizationMatrix; + private DeQuant? deQuantsDeltaQ; + + public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + + this.inverseQuantizationMatrix = new int[Av1Constants.QuantificationMatrixLevelCount][][]; + for (int q = 0; q < Av1Constants.QuantificationMatrixLevelCount; ++q) + { + this.inverseQuantizationMatrix[q] = new int[Av1Constants.MaxPlanes][]; + for (int c = 0; c < Av1Constants.MaxPlanes; c++) + { + int lumaOrChroma = Math.Min(1, c); + int current = 0; + this.inverseQuantizationMatrix[q][c] = new int[(int)Av1TransformSize.AllSizes]; + for (Av1TransformSize t = 0; t < Av1TransformSize.AllSizes; ++t) + { + int size = t.GetSize2d(); + Av1TransformSize qmTransformSize = t.GetAdjusted(); + if (q == Av1Constants.QuantificationMatrixLevelCount - 1) + { + this.inverseQuantizationMatrix[q][c][(int)t] = -1; + } + else if (t != qmTransformSize) + { + // Reuse matrices for 'qm_tx_size' + this.inverseQuantizationMatrix[q][c][(int)t] = this.inverseQuantizationMatrix[q][c][(int)qmTransformSize]; + } + else + { + Guard.MustBeLessThanOrEqualTo(current + size, QuatizationMatrixTotalSize, nameof(current)); + this.inverseQuantizationMatrix[q][c][(int)t] = Av1QuantizationConstants.InverseWT[q][lumaOrChroma][current]; + current += size; + } + } + } + } + } + + public void UpdateDequant(DeQuant deQuants, Av1SuperblockInfo superblockInfo) + { + Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth; + Guard.NotNull(deQuants, nameof(deQuants)); + this.deQuantsDeltaQ = deQuants; + if (this.frameHeader.DeltaQParameters.IsPresent) + { + for (int i = 0; i < Av1Constants.MaxSegmentCount; i++) + { + int currentQIndex = QuantizationLookup.GetQIndex(this.frameHeader.SegmentationParameters, i, superblockInfo.SuperblockDeltaQ); + + for (Av1Plane plane = 0; (int)plane < Av1Constants.MaxPlanes; plane++) + { + int dcDeltaQ = this.frameHeader.QuantizationParameters.DeltaQDc[(int)plane]; + int acDeltaQ = this.frameHeader.QuantizationParameters.DeltaQAc[(int)plane]; + + this.deQuantsDeltaQ.SetDc(i, plane, QuantizationLookup.GetDcQuant(currentQIndex, dcDeltaQ, bitDepth)); + this.deQuantsDeltaQ.SetAc(i, plane, QuantizationLookup.GetAcQuant(currentQIndex, acDeltaQ, bitDepth)); + } + } + } + } + + public int InverseQuantize(Av1BlockModeInfo mode, Span level, Span qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane) + { + Guard.NotNull(this.deQuantsDeltaQ); + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); + short[] scanIndices = scanOrder.Scan; + int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1; + int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())); + Av1TransformSize qmTransformSize = transformSize.GetAdjusted(); + bool usingQuantizationMatrix = this.frameHeader.QuantizationParameters.IsUsingQMatrix; + bool lossless = this.frameHeader.LosslessArray[mode.SegmentId]; + short dequantDc = this.deQuantsDeltaQ.GetDc(mode.SegmentId, plane); + short dequantAc = this.deQuantsDeltaQ.GetAc(mode.SegmentId, plane); + int qmLevel = lossless || !usingQuantizationMatrix ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : this.frameHeader.QuantizationParameters.QMatrix[(int)plane]; + ref int iqMatrix = ref (transformType.ToClass() == Av1TransformClass.Class2D) ? + ref this.inverseQuantizationMatrix[qmLevel][(int)plane][(int)qmTransformSize] + : ref this.inverseQuantizationMatrix[Av1Constants.QuantificationMatrixLevelCount - 1][0][(int)qmTransformSize]; + int shift = transformSize.GetScale(); + + int coefficientCount = level[0]; + level = level[1..]; + int lev = level[0]; + int qCoefficient; + if (lev != 0) + { + int pos = scanIndices[0]; + qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantDc, pos, ref iqMatrix)) & 0xffffff); + qCoefficient >>= shift; + + if (lev < 0) + { + qCoefficient = -qCoefficient; + } + + qCoefficients[0] = Av1Math.Clamp(qCoefficient, minValue, maxValue); + } + + for (int i = 1; i < coefficientCount; i++) + { + lev = level[i]; + if (lev != 0) + { + int pos = scanIndices[i]; + qCoefficient = (int)(((long)Math.Abs(lev) * GetDeQuantizedValue(dequantAc, pos, ref iqMatrix)) & 0xffffff); + qCoefficient >>= shift; + + if (lev < 0) + { + qCoefficient = -qCoefficient; + } + + qCoefficients[pos] = Av1Math.Clamp(qCoefficient, minValue, maxValue); + } + } + + return coefficientCount; + } + + private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ref int iqMatrix) + { + int deQuantifiedValue = dequant; + + // TODO: Check order of operators + deQuantifiedValue = ((Unsafe.Add(ref iqMatrix, coefficientIndex) * deQuantifiedValue) + (1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1))) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount; + return deQuantifiedValue; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs new file mode 100644 index 0000000000..ea9fb72415 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationConstants.cs @@ -0,0 +1,483 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; + +internal class Av1QuantizationConstants +{ + public static int[][][] InverseWT => + [ + [ + + // Luma + [ + + // Size 4x4 + 32, 43, 73, 97, 43, 67, 94, 110, 73, 94, 137, 150, 97, 110, 150, 200, + + // Size 8x8 + 32, 32, 38, 51, 68, 84, 95, 109, 32, 35, 40, 49, 63, 76, 89, 102, 38, + 40, 54, 65, 78, 91, 98, 106, 51, 49, 65, 82, 97, 111, 113, 121, 68, 63, + 78, 97, 117, 134, 138, 142, 84, 76, 91, 111, 134, 152, 159, 168, 95, 89, + 98, 113, 138, 159, 183, 199, 109, 102, 106, 121, 142, 168, 199, 220, + + // Size 16x16 + 32, 31, 31, 34, 36, 44, 48, 59, 65, 80, 83, 91, 97, 104, 111, 119, 31, + 32, 32, 33, 34, 41, 44, 54, 59, 72, 75, 83, 90, 97, 104, 112, 31, 32, + 33, 35, 36, 42, 45, 54, 59, 71, 74, 81, 86, 93, 100, 107, 34, 33, 35, + 39, 42, 47, 51, 58, 63, 74, 76, 81, 84, 90, 97, 105, 36, 34, 36, 42, 48, + 54, 57, 64, 68, 79, 81, 88, 91, 96, 102, 105, 44, 41, 42, 47, 54, 63, + 67, 75, 79, 90, 92, 95, 100, 102, 109, 112, 48, 44, 45, 51, 57, 67, 71, + 80, 85, 96, 99, 107, 108, 111, 117, 120, 59, 54, 54, 58, 64, 75, 80, 92, + 98, 110, 113, 115, 116, 122, 125, 130, 65, 59, 59, 63, 68, 79, 85, 98, + 105, 118, 121, 127, 130, 134, 135, 140, 80, 72, 71, 74, 79, 90, 96, 110, + 118, 134, 137, 140, 143, 144, 146, 152, 83, 75, 74, 76, 81, 92, 99, 113, + 121, 137, 140, 151, 152, 155, 158, 165, 91, 83, 81, 81, 88, 95, 107, + 115, 127, 140, 151, 159, 166, 169, 173, 179, 97, 90, 86, 84, 91, 100, + 108, 116, 130, 143, 152, 166, 174, 182, 189, 193, 104, 97, 93, 90, 96, + 102, 111, 122, 134, 144, 155, 169, 182, 191, 200, 210, 111, 104, 100, + 97, 102, 109, 117, 125, 135, 146, 158, 173, 189, 200, 210, 220, 119, + 112, 107, 105, 105, 112, 120, 130, 140, 152, 165, 179, 193, 210, 220, + 231, + + // Size 32x32 + 32, 31, 31, 31, 31, 32, 34, 35, 36, 39, 44, 46, 48, 54, 59, 62, 65, 71, + 80, 81, 83, 88, 91, 94, 97, 101, 104, 107, 111, 115, 119, 123, 31, 32, + 32, 32, 32, 32, 34, 34, 35, 38, 42, 44, 46, 51, 56, 59, 62, 68, 76, 77, + 78, 84, 86, 89, 92, 95, 99, 102, 105, 109, 113, 116, 31, 32, 32, 32, 32, + 32, 33, 34, 34, 37, 41, 42, 44, 49, 54, 56, 59, 65, 72, 73, 75, 80, 83, + 86, 90, 93, 97, 101, 104, 108, 112, 116, 31, 32, 32, 32, 33, 33, 34, 35, + 35, 38, 41, 43, 45, 49, 54, 56, 59, 64, 72, 73, 74, 79, 82, 85, 88, 91, + 94, 97, 101, 104, 107, 111, 31, 32, 32, 33, 33, 34, 35, 36, 36, 39, 42, + 44, 45, 50, 54, 56, 59, 64, 71, 72, 74, 78, 81, 84, 86, 89, 93, 96, 100, + 104, 107, 111, 32, 32, 32, 33, 34, 35, 37, 37, 38, 40, 42, 44, 46, 49, + 53, 55, 58, 63, 69, 70, 72, 76, 79, 82, 85, 89, 93, 96, 99, 102, 106, + 109, 34, 34, 33, 34, 35, 37, 39, 41, 42, 45, 47, 49, 51, 54, 58, 60, 63, + 68, 74, 75, 76, 80, 81, 82, 84, 87, 90, 93, 97, 101, 105, 110, 35, 34, + 34, 35, 36, 37, 41, 43, 45, 47, 50, 52, 53, 57, 61, 63, 65, 70, 76, 77, + 79, 82, 84, 86, 89, 91, 92, 93, 96, 100, 103, 107, 36, 35, 34, 35, 36, + 38, 42, 45, 48, 50, 54, 55, 57, 60, 64, 66, 68, 73, 79, 80, 81, 85, 88, + 90, 91, 93, 96, 99, 102, 103, 105, 107, 39, 38, 37, 38, 39, 40, 45, 47, + 50, 54, 58, 59, 61, 65, 69, 71, 73, 78, 84, 85, 86, 91, 92, 92, 95, 98, + 100, 101, 103, 106, 110, 114, 44, 42, 41, 41, 42, 42, 47, 50, 54, 58, + 63, 65, 67, 71, 75, 77, 79, 84, 90, 91, 92, 95, 95, 97, 100, 101, 102, + 105, 109, 111, 112, 114, 46, 44, 42, 43, 44, 44, 49, 52, 55, 59, 65, 67, + 69, 74, 78, 80, 82, 87, 93, 94, 95, 98, 100, 103, 102, 105, 108, 110, + 111, 113, 117, 121, 48, 46, 44, 45, 45, 46, 51, 53, 57, 61, 67, 69, 71, + 76, 80, 83, 85, 90, 96, 97, 99, 103, 107, 105, 108, 111, 111, 113, 117, + 119, 120, 122, 54, 51, 49, 49, 50, 49, 54, 57, 60, 65, 71, 74, 76, 82, + 87, 89, 92, 97, 104, 105, 106, 111, 110, 111, 114, 113, 116, 120, 120, + 121, 125, 130, 59, 56, 54, 54, 54, 53, 58, 61, 64, 69, 75, 78, 80, 87, + 92, 95, 98, 103, 110, 111, 113, 115, 115, 119, 116, 120, 122, 122, 125, + 129, 130, 130, 62, 59, 56, 56, 56, 55, 60, 63, 66, 71, 77, 80, 83, 89, + 95, 98, 101, 107, 114, 115, 117, 119, 123, 121, 125, 126, 125, 129, 131, + 131, 135, 140, 65, 62, 59, 59, 59, 58, 63, 65, 68, 73, 79, 82, 85, 92, + 98, 101, 105, 111, 118, 119, 121, 126, 127, 128, 130, 130, 134, 133, + 135, 140, 140, 140, 71, 68, 65, 64, 64, 63, 68, 70, 73, 78, 84, 87, 90, + 97, 103, 107, 111, 117, 125, 126, 128, 134, 132, 136, 133, 138, 137, + 140, 143, 142, 145, 150, 80, 76, 72, 72, 71, 69, 74, 76, 79, 84, 90, 93, + 96, 104, 110, 114, 118, 125, 134, 135, 137, 139, 140, 139, 143, 142, + 144, 146, 146, 151, 152, 151, 81, 77, 73, 73, 72, 70, 75, 77, 80, 85, + 91, 94, 97, 105, 111, 115, 119, 126, 135, 137, 138, 144, 147, 146, 148, + 149, 151, 150, 156, 155, 157, 163, 83, 78, 75, 74, 74, 72, 76, 79, 81, + 86, 92, 95, 99, 106, 113, 117, 121, 128, 137, 138, 140, 147, 151, 156, + 152, 157, 155, 161, 158, 162, 165, 164, 88, 84, 80, 79, 78, 76, 80, 82, + 85, 91, 95, 98, 103, 111, 115, 119, 126, 134, 139, 144, 147, 152, 154, + 158, 163, 159, 165, 163, 168, 168, 169, 176, 91, 86, 83, 82, 81, 79, 81, + 84, 88, 92, 95, 100, 107, 110, 115, 123, 127, 132, 140, 147, 151, 154, + 159, 161, 166, 171, 169, 173, 173, 176, 179, 177, 94, 89, 86, 85, 84, + 82, 82, 86, 90, 92, 97, 103, 105, 111, 119, 121, 128, 136, 139, 146, + 156, 158, 161, 166, 168, 174, 179, 178, 180, 183, 183, 190, 97, 92, 90, + 88, 86, 85, 84, 89, 91, 95, 100, 102, 108, 114, 116, 125, 130, 133, 143, + 148, 152, 163, 166, 168, 174, 176, 182, 187, 189, 188, 193, 191, 101, + 95, 93, 91, 89, 89, 87, 91, 93, 98, 101, 105, 111, 113, 120, 126, 130, + 138, 142, 149, 157, 159, 171, 174, 176, 183, 184, 191, 195, 199, 197, + 204, 104, 99, 97, 94, 93, 93, 90, 92, 96, 100, 102, 108, 111, 116, 122, + 125, 134, 137, 144, 151, 155, 165, 169, 179, 182, 184, 191, 193, 200, + 204, 210, 206, 107, 102, 101, 97, 96, 96, 93, 93, 99, 101, 105, 110, + 113, 120, 122, 129, 133, 140, 146, 150, 161, 163, 173, 178, 187, 191, + 193, 200, 202, 210, 214, 222, 111, 105, 104, 101, 100, 99, 97, 96, 102, + 103, 109, 111, 117, 120, 125, 131, 135, 143, 146, 156, 158, 168, 173, + 180, 189, 195, 200, 202, 210, 212, 220, 224, 115, 109, 108, 104, 104, + 102, 101, 100, 103, 106, 111, 113, 119, 121, 129, 131, 140, 142, 151, + 155, 162, 168, 176, 183, 188, 199, 204, 210, 212, 220, 222, 230, 119, + 113, 112, 107, 107, 106, 105, 103, 105, 110, 112, 117, 120, 125, 130, + 135, 140, 145, 152, 157, 165, 169, 179, 183, 193, 197, 210, 214, 220, + 222, 231, 232, 123, 116, 116, 111, 111, 109, 110, 107, 107, 114, 114, + 121, 122, 130, 130, 140, 140, 150, 151, 163, 164, 176, 177, 190, 191, + 204, 206, 222, 224, 230, 232, 242, + + // Size 4x8 + 32, 42, 75, 91, 33, 42, 69, 86, 37, 58, 84, 91, 49, 71, 103, 110, 65, + 84, 125, 128, 80, 97, 142, 152, 91, 100, 145, 178, 104, 112, 146, 190, + + // Size 8x4 + 32, 33, 37, 49, 65, 80, 91, 104, 42, 42, 58, 71, 84, 97, 100, 112, 75, + 69, 84, 103, 125, 142, 145, 146, 91, 86, 91, 110, 128, 152, 178, 190, + + // Size 8x16 + 32, 32, 36, 53, 65, 87, 93, 99, 31, 33, 34, 49, 59, 78, 86, 93, 32, 34, + 36, 50, 59, 77, 82, 89, 34, 37, 42, 54, 63, 79, 80, 88, 36, 38, 48, 60, + 68, 84, 86, 90, 44, 43, 53, 71, 79, 95, 94, 97, 48, 46, 56, 76, 85, 102, + 105, 105, 58, 54, 63, 87, 98, 116, 112, 115, 65, 58, 68, 92, 105, 124, + 122, 124, 79, 70, 79, 104, 118, 141, 135, 135, 82, 72, 81, 106, 121, + 144, 149, 146, 91, 80, 88, 106, 130, 148, 162, 159, 97, 86, 94, 107, + 128, 157, 167, 171, 103, 93, 98, 114, 131, 150, 174, 186, 110, 100, 101, + 117, 138, 161, 183, 193, 118, 107, 105, 118, 136, 157, 182, 203, + + // Size 16x8 + 32, 31, 32, 34, 36, 44, 48, 58, 65, 79, 82, 91, 97, 103, 110, 118, 32, + 33, 34, 37, 38, 43, 46, 54, 58, 70, 72, 80, 86, 93, 100, 107, 36, 34, + 36, 42, 48, 53, 56, 63, 68, 79, 81, 88, 94, 98, 101, 105, 53, 49, 50, + 54, 60, 71, 76, 87, 92, 104, 106, 106, 107, 114, 117, 118, 65, 59, 59, + 63, 68, 79, 85, 98, 105, 118, 121, 130, 128, 131, 138, 136, 87, 78, 77, + 79, 84, 95, 102, 116, 124, 141, 144, 148, 157, 150, 161, 157, 93, 86, + 82, 80, 86, 94, 105, 112, 122, 135, 149, 162, 167, 174, 183, 182, 99, + 93, 89, 88, 90, 97, 105, 115, 124, 135, 146, 159, 171, 186, 193, 203, + + // Size 16x32 + 32, 31, 32, 34, 36, 44, 53, 59, 65, 79, 87, 90, 93, 96, 99, 102, 31, 32, + 32, 34, 35, 42, 51, 56, 62, 75, 82, 85, 88, 91, 94, 97, 31, 32, 33, 33, + 34, 41, 49, 54, 59, 72, 78, 82, 86, 90, 93, 97, 31, 32, 33, 34, 35, 41, + 49, 54, 59, 71, 78, 81, 84, 87, 90, 93, 32, 32, 34, 35, 36, 42, 50, 54, + 59, 71, 77, 80, 82, 86, 89, 93, 32, 33, 35, 37, 38, 42, 49, 53, 58, 69, + 75, 78, 82, 86, 89, 92, 34, 34, 37, 39, 42, 48, 54, 58, 63, 73, 79, 78, + 80, 83, 88, 92, 35, 34, 37, 41, 45, 50, 57, 61, 65, 76, 82, 83, 84, 84, + 87, 90, 36, 34, 38, 43, 48, 54, 60, 64, 68, 78, 84, 87, 86, 89, 90, 90, + 39, 37, 40, 45, 50, 58, 65, 69, 73, 84, 89, 89, 91, 91, 93, 96, 44, 41, + 43, 48, 53, 63, 71, 75, 79, 90, 95, 93, 94, 95, 97, 97, 46, 43, 44, 49, + 55, 65, 73, 78, 82, 93, 98, 100, 98, 100, 99, 103, 48, 45, 46, 51, 56, + 67, 76, 80, 85, 96, 102, 102, 105, 102, 105, 104, 53, 49, 50, 54, 60, + 71, 82, 87, 92, 103, 109, 107, 107, 110, 107, 111, 58, 54, 54, 58, 63, + 75, 87, 92, 98, 110, 116, 115, 112, 111, 115, 112, 61, 57, 56, 60, 66, + 77, 89, 95, 101, 114, 120, 118, 119, 118, 116, 120, 65, 60, 58, 63, 68, + 79, 92, 98, 105, 118, 124, 123, 122, 123, 124, 121, 71, 65, 63, 68, 73, + 84, 97, 103, 111, 125, 132, 132, 130, 128, 127, 130, 79, 72, 70, 74, 79, + 90, 104, 110, 118, 133, 141, 136, 135, 135, 135, 131, 81, 74, 71, 75, + 80, 91, 105, 112, 119, 135, 142, 140, 140, 138, 139, 142, 82, 75, 72, + 76, 81, 92, 106, 113, 121, 136, 144, 151, 149, 149, 146, 143, 88, 80, + 77, 80, 85, 97, 108, 115, 126, 142, 149, 153, 153, 152, 152, 154, 91, + 83, 80, 81, 88, 100, 106, 114, 130, 142, 148, 155, 162, 160, 159, 155, + 94, 85, 83, 82, 91, 100, 105, 118, 131, 137, 153, 160, 165, 167, 166, + 168, 97, 88, 86, 85, 94, 100, 107, 123, 128, 140, 157, 161, 167, 173, + 171, 169, 100, 91, 89, 87, 97, 100, 111, 121, 127, 145, 152, 164, 173, + 178, 182, 181, 103, 94, 93, 90, 98, 101, 114, 120, 131, 144, 150, 170, + 174, 180, 186, 183, 107, 97, 96, 93, 100, 104, 117, 119, 136, 142, 155, + 168, 177, 187, 191, 198, 110, 101, 100, 97, 101, 108, 117, 123, 138, + 141, 161, 165, 183, 188, 193, 200, 114, 104, 104, 100, 103, 112, 117, + 127, 137, 146, 159, 167, 185, 190, 201, 206, 118, 108, 107, 103, 105, + 115, 118, 131, 136, 151, 157, 172, 182, 197, 203, 208, 122, 111, 111, + 107, 107, 119, 119, 136, 136, 156, 156, 178, 179, 203, 204, 217, + + // Size 32x16 + 32, 31, 31, 31, 32, 32, 34, 35, 36, 39, 44, 46, 48, 53, 58, 61, 65, 71, + 79, 81, 82, 88, 91, 94, 97, 100, 103, 107, 110, 114, 118, 122, 31, 32, + 32, 32, 32, 33, 34, 34, 34, 37, 41, 43, 45, 49, 54, 57, 60, 65, 72, 74, + 75, 80, 83, 85, 88, 91, 94, 97, 101, 104, 108, 111, 32, 32, 33, 33, 34, + 35, 37, 37, 38, 40, 43, 44, 46, 50, 54, 56, 58, 63, 70, 71, 72, 77, 80, + 83, 86, 89, 93, 96, 100, 104, 107, 111, 34, 34, 33, 34, 35, 37, 39, 41, + 43, 45, 48, 49, 51, 54, 58, 60, 63, 68, 74, 75, 76, 80, 81, 82, 85, 87, + 90, 93, 97, 100, 103, 107, 36, 35, 34, 35, 36, 38, 42, 45, 48, 50, 53, + 55, 56, 60, 63, 66, 68, 73, 79, 80, 81, 85, 88, 91, 94, 97, 98, 100, + 101, 103, 105, 107, 44, 42, 41, 41, 42, 42, 48, 50, 54, 58, 63, 65, 67, + 71, 75, 77, 79, 84, 90, 91, 92, 97, 100, 100, 100, 100, 101, 104, 108, + 112, 115, 119, 53, 51, 49, 49, 50, 49, 54, 57, 60, 65, 71, 73, 76, 82, + 87, 89, 92, 97, 104, 105, 106, 108, 106, 105, 107, 111, 114, 117, 117, + 117, 118, 119, 59, 56, 54, 54, 54, 53, 58, 61, 64, 69, 75, 78, 80, 87, + 92, 95, 98, 103, 110, 112, 113, 115, 114, 118, 123, 121, 120, 119, 123, + 127, 131, 136, 65, 62, 59, 59, 59, 58, 63, 65, 68, 73, 79, 82, 85, 92, + 98, 101, 105, 111, 118, 119, 121, 126, 130, 131, 128, 127, 131, 136, + 138, 137, 136, 136, 79, 75, 72, 71, 71, 69, 73, 76, 78, 84, 90, 93, 96, + 103, 110, 114, 118, 125, 133, 135, 136, 142, 142, 137, 140, 145, 144, + 142, 141, 146, 151, 156, 87, 82, 78, 78, 77, 75, 79, 82, 84, 89, 95, 98, + 102, 109, 116, 120, 124, 132, 141, 142, 144, 149, 148, 153, 157, 152, + 150, 155, 161, 159, 157, 156, 90, 85, 82, 81, 80, 78, 78, 83, 87, 89, + 93, 100, 102, 107, 115, 118, 123, 132, 136, 140, 151, 153, 155, 160, + 161, 164, 170, 168, 165, 167, 172, 178, 93, 88, 86, 84, 82, 82, 80, 84, + 86, 91, 94, 98, 105, 107, 112, 119, 122, 130, 135, 140, 149, 153, 162, + 165, 167, 173, 174, 177, 183, 185, 182, 179, 96, 91, 90, 87, 86, 86, 83, + 84, 89, 91, 95, 100, 102, 110, 111, 118, 123, 128, 135, 138, 149, 152, + 160, 167, 173, 178, 180, 187, 188, 190, 197, 203, 99, 94, 93, 90, 89, + 89, 88, 87, 90, 93, 97, 99, 105, 107, 115, 116, 124, 127, 135, 139, 146, + 152, 159, 166, 171, 182, 186, 191, 193, 201, 203, 204, 102, 97, 97, 93, + 93, 92, 92, 90, 90, 96, 97, 103, 104, 111, 112, 120, 121, 130, 131, 142, + 143, 154, 155, 168, 169, 181, 183, 198, 200, 206, 208, 217, + + // Size 4x16 + 31, 44, 79, 96, 32, 41, 72, 90, 32, 42, 71, 86, 34, 48, 73, 83, 34, 54, + 78, 89, 41, 63, 90, 95, 45, 67, 96, 102, 54, 75, 110, 111, 60, 79, 118, + 123, 72, 90, 133, 135, 75, 92, 136, 149, 83, 100, 142, 160, 88, 100, + 140, 173, 94, 101, 144, 180, 101, 108, 141, 188, 108, 115, 151, 197, + + // Size 16x4 + 31, 32, 32, 34, 34, 41, 45, 54, 60, 72, 75, 83, 88, 94, 101, 108, 44, + 41, 42, 48, 54, 63, 67, 75, 79, 90, 92, 100, 100, 101, 108, 115, 79, 72, + 71, 73, 78, 90, 96, 110, 118, 133, 136, 142, 140, 144, 141, 151, 96, 90, + 86, 83, 89, 95, 102, 111, 123, 135, 149, 160, 173, 180, 188, 197, + + // Size 8x32 + 32, 32, 36, 53, 65, 87, 93, 99, 31, 32, 35, 51, 62, 82, 88, 94, 31, 33, + 34, 49, 59, 78, 86, 93, 31, 33, 35, 49, 59, 78, 84, 90, 32, 34, 36, 50, + 59, 77, 82, 89, 32, 35, 38, 49, 58, 75, 82, 89, 34, 37, 42, 54, 63, 79, + 80, 88, 35, 37, 45, 57, 65, 82, 84, 87, 36, 38, 48, 60, 68, 84, 86, 90, + 39, 40, 50, 65, 73, 89, 91, 93, 44, 43, 53, 71, 79, 95, 94, 97, 46, 44, + 55, 73, 82, 98, 98, 99, 48, 46, 56, 76, 85, 102, 105, 105, 53, 50, 60, + 82, 92, 109, 107, 107, 58, 54, 63, 87, 98, 116, 112, 115, 61, 56, 66, + 89, 101, 120, 119, 116, 65, 58, 68, 92, 105, 124, 122, 124, 71, 63, 73, + 97, 111, 132, 130, 127, 79, 70, 79, 104, 118, 141, 135, 135, 81, 71, 80, + 105, 119, 142, 140, 139, 82, 72, 81, 106, 121, 144, 149, 146, 88, 77, + 85, 108, 126, 149, 153, 152, 91, 80, 88, 106, 130, 148, 162, 159, 94, + 83, 91, 105, 131, 153, 165, 166, 97, 86, 94, 107, 128, 157, 167, 171, + 100, 89, 97, 111, 127, 152, 173, 182, 103, 93, 98, 114, 131, 150, 174, + 186, 107, 96, 100, 117, 136, 155, 177, 191, 110, 100, 101, 117, 138, + 161, 183, 193, 114, 104, 103, 117, 137, 159, 185, 201, 118, 107, 105, + 118, 136, 157, 182, 203, 122, 111, 107, 119, 136, 156, 179, 204, + + // Size 32x8 + 32, 31, 31, 31, 32, 32, 34, 35, 36, 39, 44, 46, 48, 53, 58, 61, 65, 71, + 79, 81, 82, 88, 91, 94, 97, 100, 103, 107, 110, 114, 118, 122, 32, 32, + 33, 33, 34, 35, 37, 37, 38, 40, 43, 44, 46, 50, 54, 56, 58, 63, 70, 71, + 72, 77, 80, 83, 86, 89, 93, 96, 100, 104, 107, 111, 36, 35, 34, 35, 36, + 38, 42, 45, 48, 50, 53, 55, 56, 60, 63, 66, 68, 73, 79, 80, 81, 85, 88, + 91, 94, 97, 98, 100, 101, 103, 105, 107, 53, 51, 49, 49, 50, 49, 54, 57, + 60, 65, 71, 73, 76, 82, 87, 89, 92, 97, 104, 105, 106, 108, 106, 105, + 107, 111, 114, 117, 117, 117, 118, 119, 65, 62, 59, 59, 59, 58, 63, 65, + 68, 73, 79, 82, 85, 92, 98, 101, 105, 111, 118, 119, 121, 126, 130, 131, + 128, 127, 131, 136, 138, 137, 136, 136, 87, 82, 78, 78, 77, 75, 79, 82, + 84, 89, 95, 98, 102, 109, 116, 120, 124, 132, 141, 142, 144, 149, 148, + 153, 157, 152, 150, 155, 161, 159, 157, 156, 93, 88, 86, 84, 82, 82, 80, + 84, 86, 91, 94, 98, 105, 107, 112, 119, 122, 130, 135, 140, 149, 153, + 162, 165, 167, 173, 174, 177, 183, 185, 182, 179, 99, 94, 93, 90, 89, + 89, 88, 87, 90, 93, 97, 99, 105, 107, 115, 116, 124, 127, 135, 139, 146, + 152, 159, 166, 171, 182, 186, 191, 193, 201, 203, 204 + ], + + // Chroma + [ + + // Size 4x4 + 35, 46, 57, 66, 46, 60, 69, 71, 57, 69, 90, 90, 66, 71, 90, 109, + + // Size 8x8 + 31, 38, 47, 50, 57, 63, 67, 71, 38, 47, 46, 47, 52, 57, 62, 67, 47, 46, + 54, 57, 61, 66, 67, 68, 50, 47, 57, 66, 72, 77, 75, 75, 57, 52, 61, 72, + 82, 88, 86, 84, 63, 57, 66, 77, 88, 96, 95, 95, 67, 62, 67, 75, 86, 95, + 104, 107, 71, 67, 68, 75, 84, 95, 107, 113, + + // Size 16x16 + 32, 30, 33, 41, 49, 49, 50, 54, 57, 63, 65, 68, 70, 72, 74, 76, 30, 32, + 35, 42, 46, 45, 46, 49, 52, 57, 58, 62, 64, 67, 70, 72, 33, 35, 39, 45, + 47, 45, 46, 49, 51, 56, 57, 60, 62, 64, 66, 69, 41, 42, 45, 48, 50, 49, + 50, 52, 53, 57, 58, 59, 60, 61, 64, 67, 49, 46, 47, 50, 53, 53, 54, 55, + 56, 60, 61, 64, 64, 65, 66, 66, 49, 45, 45, 49, 53, 58, 60, 62, 63, 67, + 68, 67, 69, 68, 70, 70, 50, 46, 46, 50, 54, 60, 61, 65, 67, 71, 71, 74, + 73, 73, 74, 74, 54, 49, 49, 52, 55, 62, 65, 71, 73, 78, 79, 78, 77, 78, + 78, 78, 57, 52, 51, 53, 56, 63, 67, 73, 76, 82, 83, 84, 84, 84, 82, 83, + 63, 57, 56, 57, 60, 67, 71, 78, 82, 89, 90, 90, 89, 88, 87, 88, 65, 58, + 57, 58, 61, 68, 71, 79, 83, 90, 91, 94, 93, 93, 92, 93, 68, 62, 60, 59, + 64, 67, 74, 78, 84, 90, 94, 98, 99, 98, 98, 98, 70, 64, 62, 60, 64, 69, + 73, 77, 84, 89, 93, 99, 102, 103, 104, 104, 72, 67, 64, 61, 65, 68, 73, + 78, 84, 88, 93, 98, 103, 106, 108, 109, 74, 70, 66, 64, 66, 70, 74, 78, + 82, 87, 92, 98, 104, 108, 111, 112, 76, 72, 69, 67, 66, 70, 74, 78, 83, + 88, 93, 98, 104, 109, 112, 116, + + // Size 32x32 + 32, 31, 30, 32, 33, 36, 41, 45, 49, 48, 49, 50, 50, 52, 54, 56, 57, 60, + 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 78, 31, 31, 31, 33, + 34, 38, 42, 45, 47, 47, 47, 47, 48, 50, 52, 53, 54, 57, 60, 61, 61, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 30, 31, 32, 33, 35, 40, 42, 44, + 46, 45, 45, 45, 46, 47, 49, 51, 52, 54, 57, 58, 58, 61, 62, 63, 64, 66, + 67, 68, 70, 71, 72, 74, 32, 33, 33, 35, 37, 41, 43, 45, 47, 46, 45, 46, + 46, 47, 49, 50, 51, 54, 57, 57, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, + 69, 70, 33, 34, 35, 37, 39, 43, 45, 46, 47, 46, 45, 46, 46, 47, 49, 50, + 51, 53, 56, 57, 57, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 36, 38, + 40, 41, 43, 47, 47, 47, 48, 46, 45, 46, 46, 47, 48, 49, 50, 52, 54, 55, + 55, 57, 58, 59, 61, 62, 64, 65, 66, 67, 68, 69, 41, 42, 42, 43, 45, 47, + 48, 49, 50, 49, 49, 49, 50, 50, 52, 52, 53, 55, 57, 58, 58, 60, 59, 59, + 60, 61, 61, 63, 64, 66, 67, 69, 45, 45, 44, 45, 46, 47, 49, 50, 51, 51, + 51, 51, 52, 52, 53, 54, 55, 57, 59, 59, 60, 61, 61, 62, 63, 63, 63, 63, + 63, 64, 65, 66, 49, 47, 46, 47, 47, 48, 50, 51, 53, 53, 53, 54, 54, 54, + 55, 56, 56, 58, 60, 61, 61, 63, 64, 64, 64, 64, 65, 66, 66, 66, 66, 66, + 48, 47, 45, 46, 46, 46, 49, 51, 53, 54, 55, 56, 56, 57, 58, 59, 60, 61, + 63, 64, 64, 66, 66, 65, 66, 67, 67, 67, 67, 68, 69, 70, 49, 47, 45, 45, + 45, 45, 49, 51, 53, 55, 58, 59, 60, 61, 62, 63, 63, 65, 67, 67, 68, 69, + 67, 68, 69, 68, 68, 69, 70, 70, 70, 70, 50, 47, 45, 46, 46, 46, 49, 51, + 54, 56, 59, 60, 60, 62, 64, 64, 65, 67, 69, 69, 70, 70, 71, 71, 70, 70, + 71, 71, 71, 71, 72, 74, 50, 48, 46, 46, 46, 46, 50, 52, 54, 56, 60, 60, + 61, 63, 65, 66, 67, 68, 71, 71, 71, 73, 74, 72, 73, 74, 73, 73, 74, 74, + 74, 74, 52, 50, 47, 47, 47, 47, 50, 52, 54, 57, 61, 62, 63, 66, 68, 69, + 70, 72, 75, 75, 75, 77, 75, 75, 76, 75, 75, 76, 75, 75, 76, 77, 54, 52, + 49, 49, 49, 48, 52, 53, 55, 58, 62, 64, 65, 68, 71, 72, 73, 75, 78, 78, + 79, 79, 78, 79, 77, 78, 78, 77, 78, 79, 78, 78, 56, 53, 51, 50, 50, 49, + 52, 54, 56, 59, 63, 64, 66, 69, 72, 73, 75, 77, 80, 80, 81, 81, 82, 80, + 81, 81, 79, 81, 80, 79, 81, 82, 57, 54, 52, 51, 51, 50, 53, 55, 56, 60, + 63, 65, 67, 70, 73, 75, 76, 79, 82, 82, 83, 85, 84, 83, 84, 83, 84, 82, + 82, 84, 83, 82, 60, 57, 54, 54, 53, 52, 55, 57, 58, 61, 65, 67, 68, 72, + 75, 77, 79, 82, 85, 85, 86, 88, 86, 87, 85, 86, 85, 85, 86, 84, 85, 86, + 63, 60, 57, 57, 56, 54, 57, 59, 60, 63, 67, 69, 71, 75, 78, 80, 82, 85, + 89, 89, 90, 90, 90, 89, 89, 88, 88, 88, 87, 88, 88, 87, 64, 61, 58, 57, + 57, 55, 58, 59, 61, 64, 67, 69, 71, 75, 78, 80, 82, 85, 89, 90, 91, 92, + 93, 92, 92, 91, 91, 90, 91, 90, 90, 92, 65, 61, 58, 58, 57, 55, 58, 60, + 61, 64, 68, 70, 71, 75, 79, 81, 83, 86, 90, 91, 91, 94, 94, 96, 93, 94, + 93, 94, 92, 93, 93, 92, 67, 63, 61, 60, 59, 57, 60, 61, 63, 66, 69, 70, + 73, 77, 79, 81, 85, 88, 90, 92, 94, 96, 96, 97, 98, 95, 97, 95, 96, 95, + 95, 96, 68, 64, 62, 61, 60, 58, 59, 61, 64, 66, 67, 71, 74, 75, 78, 82, + 84, 86, 90, 93, 94, 96, 98, 98, 99, 100, 98, 99, 98, 98, 98, 97, 69, 65, + 63, 62, 61, 59, 59, 62, 64, 65, 68, 71, 72, 75, 79, 80, 83, 87, 89, 92, + 96, 97, 98, 100, 100, 101, 102, 101, 101, 101, 100, 102, 70, 66, 64, 63, + 62, 61, 60, 63, 64, 66, 69, 70, 73, 76, 77, 81, 84, 85, 89, 92, 93, 98, + 99, 100, 102, 102, 103, 104, 104, 103, 104, 102, 71, 67, 66, 64, 63, 62, + 61, 63, 64, 67, 68, 70, 74, 75, 78, 81, 83, 86, 88, 91, 94, 95, 100, + 101, 102, 104, 104, 105, 106, 107, 105, 107, 72, 68, 67, 65, 64, 64, 61, + 63, 65, 67, 68, 71, 73, 75, 78, 79, 84, 85, 88, 91, 93, 97, 98, 102, + 103, 104, 106, 106, 108, 108, 109, 107, 73, 69, 68, 66, 65, 65, 63, 63, + 66, 67, 69, 71, 73, 76, 77, 81, 82, 85, 88, 90, 94, 95, 99, 101, 104, + 105, 106, 109, 108, 110, 111, 112, 74, 70, 70, 67, 66, 66, 64, 63, 66, + 67, 70, 71, 74, 75, 78, 80, 82, 86, 87, 91, 92, 96, 98, 101, 104, 106, + 108, 108, 111, 111, 112, 113, 75, 71, 71, 68, 68, 67, 66, 64, 66, 68, + 70, 71, 74, 75, 79, 79, 84, 84, 88, 90, 93, 95, 98, 101, 103, 107, 108, + 110, 111, 113, 113, 115, 76, 72, 72, 69, 69, 68, 67, 65, 66, 69, 70, 72, + 74, 76, 78, 81, 83, 85, 88, 90, 93, 95, 98, 100, 104, 105, 109, 111, + 112, 113, 116, 115, 78, 74, 74, 70, 70, 69, 69, 66, 66, 70, 70, 74, 74, + 77, 78, 82, 82, 86, 87, 92, 92, 96, 97, 102, 102, 107, 107, 112, 113, + 115, 115, 118, + + // Size 4x8 + 31, 47, 60, 66, 40, 45, 54, 61, 46, 56, 64, 64, 48, 61, 75, 73, 54, 65, + 85, 82, 61, 69, 92, 92, 64, 68, 90, 102, 68, 71, 87, 105, + + // Size 8x4 + 31, 40, 46, 48, 54, 61, 64, 68, 47, 45, 56, 61, 65, 69, 68, 71, 60, 54, + 64, 75, 85, 92, 90, 87, 66, 61, 64, 73, 82, 92, 102, 105, + + // Size 8x16 + 32, 37, 48, 52, 57, 66, 68, 71, 30, 40, 46, 48, 52, 60, 63, 66, 33, 43, + 47, 47, 51, 59, 60, 63, 42, 47, 50, 50, 53, 60, 59, 62, 49, 48, 53, 54, + 57, 62, 62, 62, 49, 46, 53, 61, 64, 69, 66, 66, 50, 46, 54, 64, 67, 73, + 72, 70, 54, 49, 55, 68, 73, 80, 76, 75, 57, 50, 56, 70, 76, 84, 80, 79, + 63, 55, 60, 75, 82, 92, 87, 84, 64, 56, 61, 75, 83, 93, 93, 89, 68, 59, + 64, 74, 86, 94, 98, 94, 70, 62, 66, 73, 83, 96, 99, 98, 72, 64, 66, 75, + 83, 92, 101, 104, 74, 67, 66, 74, 84, 94, 103, 106, 76, 69, 67, 73, 82, + 91, 101, 109, + + // Size 16x8 + 32, 30, 33, 42, 49, 49, 50, 54, 57, 63, 64, 68, 70, 72, 74, 76, 37, 40, + 43, 47, 48, 46, 46, 49, 50, 55, 56, 59, 62, 64, 67, 69, 48, 46, 47, 50, + 53, 53, 54, 55, 56, 60, 61, 64, 66, 66, 66, 67, 52, 48, 47, 50, 54, 61, + 64, 68, 70, 75, 75, 74, 73, 75, 74, 73, 57, 52, 51, 53, 57, 64, 67, 73, + 76, 82, 83, 86, 83, 83, 84, 82, 66, 60, 59, 60, 62, 69, 73, 80, 84, 92, + 93, 94, 96, 92, 94, 91, 68, 63, 60, 59, 62, 66, 72, 76, 80, 87, 93, 98, + 99, 101, 103, 101, 71, 66, 63, 62, 62, 66, 70, 75, 79, 84, 89, 94, 98, + 104, 106, 109, + + // Size 16x32 + 32, 31, 37, 42, 48, 49, 52, 54, 57, 63, 66, 67, 68, 69, 71, 72, 31, 31, + 38, 42, 47, 47, 50, 52, 54, 60, 63, 64, 65, 66, 67, 68, 30, 32, 40, 42, + 46, 45, 48, 50, 52, 57, 60, 62, 63, 65, 66, 68, 32, 34, 41, 44, 46, 45, + 48, 49, 51, 57, 59, 61, 62, 63, 64, 65, 33, 36, 43, 45, 47, 46, 47, 49, + 51, 56, 59, 60, 60, 62, 63, 65, 37, 40, 47, 47, 47, 45, 47, 48, 50, 54, + 57, 58, 60, 61, 62, 63, 42, 43, 47, 48, 50, 49, 50, 52, 53, 57, 60, 58, + 59, 60, 62, 63, 45, 44, 47, 49, 51, 51, 52, 54, 55, 59, 61, 61, 61, 60, + 61, 61, 49, 46, 48, 50, 53, 53, 54, 55, 57, 60, 62, 63, 62, 63, 62, 62, + 48, 46, 47, 50, 53, 56, 57, 59, 60, 64, 66, 65, 65, 64, 64, 65, 49, 45, + 46, 49, 53, 58, 61, 62, 64, 67, 69, 67, 66, 66, 66, 65, 49, 46, 46, 49, + 53, 59, 62, 64, 65, 69, 71, 70, 68, 68, 67, 68, 50, 46, 46, 50, 54, 59, + 64, 65, 67, 71, 73, 72, 72, 70, 70, 69, 52, 48, 47, 50, 54, 61, 66, 68, + 71, 75, 77, 74, 73, 73, 71, 72, 54, 50, 49, 52, 55, 62, 68, 71, 73, 78, + 80, 78, 76, 74, 75, 73, 55, 51, 49, 52, 56, 63, 69, 72, 75, 80, 82, 80, + 79, 78, 76, 77, 57, 52, 50, 53, 56, 64, 70, 73, 76, 82, 84, 82, 80, 80, + 79, 77, 60, 54, 52, 55, 58, 65, 72, 75, 79, 85, 88, 86, 84, 82, 81, 81, + 63, 57, 55, 58, 60, 67, 75, 78, 82, 89, 92, 88, 87, 85, 84, 81, 64, 58, + 55, 58, 61, 68, 75, 78, 82, 89, 92, 90, 89, 87, 86, 86, 64, 59, 56, 58, + 61, 68, 75, 79, 83, 90, 93, 95, 93, 91, 89, 87, 67, 61, 58, 60, 63, 69, + 76, 79, 85, 92, 95, 96, 94, 92, 91, 91, 68, 62, 59, 60, 64, 71, 74, 78, + 86, 91, 94, 96, 98, 96, 94, 91, 69, 62, 60, 60, 65, 70, 72, 79, 85, 88, + 95, 98, 99, 98, 97, 96, 70, 63, 62, 60, 66, 69, 73, 81, 83, 89, 96, 97, + 99, 101, 98, 97, 71, 64, 63, 61, 67, 68, 74, 79, 82, 90, 93, 98, 102, + 102, 102, 101, 72, 65, 64, 62, 66, 68, 75, 78, 83, 89, 92, 100, 101, + 103, 104, 102, 73, 66, 65, 63, 66, 69, 75, 76, 84, 87, 93, 98, 102, 105, + 106, 107, 74, 67, 67, 64, 66, 70, 74, 77, 84, 86, 94, 96, 103, 105, 106, + 107, 75, 68, 68, 65, 66, 71, 74, 78, 83, 87, 93, 96, 103, 105, 109, 109, + 76, 69, 69, 66, 67, 72, 73, 80, 82, 88, 91, 97, 101, 107, 109, 110, 77, + 70, 70, 67, 67, 73, 73, 81, 81, 90, 90, 99, 99, 108, 108, 113, + + // Size 32x16 + 32, 31, 30, 32, 33, 37, 42, 45, 49, 48, 49, 49, 50, 52, 54, 55, 57, 60, + 63, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 31, 31, 32, 34, + 36, 40, 43, 44, 46, 46, 45, 46, 46, 48, 50, 51, 52, 54, 57, 58, 59, 61, + 62, 62, 63, 64, 65, 66, 67, 68, 69, 70, 37, 38, 40, 41, 43, 47, 47, 47, + 48, 47, 46, 46, 46, 47, 49, 49, 50, 52, 55, 55, 56, 58, 59, 60, 62, 63, + 64, 65, 67, 68, 69, 70, 42, 42, 42, 44, 45, 47, 48, 49, 50, 50, 49, 49, + 50, 50, 52, 52, 53, 55, 58, 58, 58, 60, 60, 60, 60, 61, 62, 63, 64, 65, + 66, 67, 48, 47, 46, 46, 47, 47, 50, 51, 53, 53, 53, 53, 54, 54, 55, 56, + 56, 58, 60, 61, 61, 63, 64, 65, 66, 67, 66, 66, 66, 66, 67, 67, 49, 47, + 45, 45, 46, 45, 49, 51, 53, 56, 58, 59, 59, 61, 62, 63, 64, 65, 67, 68, + 68, 69, 71, 70, 69, 68, 68, 69, 70, 71, 72, 73, 52, 50, 48, 48, 47, 47, + 50, 52, 54, 57, 61, 62, 64, 66, 68, 69, 70, 72, 75, 75, 75, 76, 74, 72, + 73, 74, 75, 75, 74, 74, 73, 73, 54, 52, 50, 49, 49, 48, 52, 54, 55, 59, + 62, 64, 65, 68, 71, 72, 73, 75, 78, 78, 79, 79, 78, 79, 81, 79, 78, 76, + 77, 78, 80, 81, 57, 54, 52, 51, 51, 50, 53, 55, 57, 60, 64, 65, 67, 71, + 73, 75, 76, 79, 82, 82, 83, 85, 86, 85, 83, 82, 83, 84, 84, 83, 82, 81, + 63, 60, 57, 57, 56, 54, 57, 59, 60, 64, 67, 69, 71, 75, 78, 80, 82, 85, + 89, 89, 90, 92, 91, 88, 89, 90, 89, 87, 86, 87, 88, 90, 66, 63, 60, 59, + 59, 57, 60, 61, 62, 66, 69, 71, 73, 77, 80, 82, 84, 88, 92, 92, 93, 95, + 94, 95, 96, 93, 92, 93, 94, 93, 91, 90, 67, 64, 62, 61, 60, 58, 58, 61, + 63, 65, 67, 70, 72, 74, 78, 80, 82, 86, 88, 90, 95, 96, 96, 98, 97, 98, + 100, 98, 96, 96, 97, 99, 68, 65, 63, 62, 60, 60, 59, 61, 62, 65, 66, 68, + 72, 73, 76, 79, 80, 84, 87, 89, 93, 94, 98, 99, 99, 102, 101, 102, 103, + 103, 101, 99, 69, 66, 65, 63, 62, 61, 60, 60, 63, 64, 66, 68, 70, 73, + 74, 78, 80, 82, 85, 87, 91, 92, 96, 98, 101, 102, 103, 105, 105, 105, + 107, 108, 71, 67, 66, 64, 63, 62, 62, 61, 62, 64, 66, 67, 70, 71, 75, + 76, 79, 81, 84, 86, 89, 91, 94, 97, 98, 102, 104, 106, 106, 109, 109, + 108, 72, 68, 68, 65, 65, 63, 63, 61, 62, 65, 65, 68, 69, 72, 73, 77, 77, + 81, 81, 86, 87, 91, 91, 96, 97, 101, 102, 107, 107, 109, 110, 113, + + // Size 4x16 + 31, 49, 63, 69, 32, 45, 57, 65, 36, 46, 56, 62, 43, 49, 57, 60, 46, 53, + 60, 63, 45, 58, 67, 66, 46, 59, 71, 70, 50, 62, 78, 74, 52, 64, 82, 80, + 57, 67, 89, 85, 59, 68, 90, 91, 62, 71, 91, 96, 63, 69, 89, 101, 65, 68, + 89, 103, 67, 70, 86, 105, 69, 72, 88, 107, + + // Size 16x4 + 31, 32, 36, 43, 46, 45, 46, 50, 52, 57, 59, 62, 63, 65, 67, 69, 49, 45, + 46, 49, 53, 58, 59, 62, 64, 67, 68, 71, 69, 68, 70, 72, 63, 57, 56, 57, + 60, 67, 71, 78, 82, 89, 90, 91, 89, 89, 86, 88, 69, 65, 62, 60, 63, 66, + 70, 74, 80, 85, 91, 96, 101, 103, 105, 107, + + // Size 8x32 + 32, 37, 48, 52, 57, 66, 68, 71, 31, 38, 47, 50, 54, 63, 65, 67, 30, 40, + 46, 48, 52, 60, 63, 66, 32, 41, 46, 48, 51, 59, 62, 64, 33, 43, 47, 47, + 51, 59, 60, 63, 37, 47, 47, 47, 50, 57, 60, 62, 42, 47, 50, 50, 53, 60, + 59, 62, 45, 47, 51, 52, 55, 61, 61, 61, 49, 48, 53, 54, 57, 62, 62, 62, + 48, 47, 53, 57, 60, 66, 65, 64, 49, 46, 53, 61, 64, 69, 66, 66, 49, 46, + 53, 62, 65, 71, 68, 67, 50, 46, 54, 64, 67, 73, 72, 70, 52, 47, 54, 66, + 71, 77, 73, 71, 54, 49, 55, 68, 73, 80, 76, 75, 55, 49, 56, 69, 75, 82, + 79, 76, 57, 50, 56, 70, 76, 84, 80, 79, 60, 52, 58, 72, 79, 88, 84, 81, + 63, 55, 60, 75, 82, 92, 87, 84, 64, 55, 61, 75, 82, 92, 89, 86, 64, 56, + 61, 75, 83, 93, 93, 89, 67, 58, 63, 76, 85, 95, 94, 91, 68, 59, 64, 74, + 86, 94, 98, 94, 69, 60, 65, 72, 85, 95, 99, 97, 70, 62, 66, 73, 83, 96, + 99, 98, 71, 63, 67, 74, 82, 93, 102, 102, 72, 64, 66, 75, 83, 92, 101, + 104, 73, 65, 66, 75, 84, 93, 102, 106, 74, 67, 66, 74, 84, 94, 103, 106, + 75, 68, 66, 74, 83, 93, 103, 109, 76, 69, 67, 73, 82, 91, 101, 109, 77, + 70, 67, 73, 81, 90, 99, 108, + + // Size 32x8 + 32, 31, 30, 32, 33, 37, 42, 45, 49, 48, 49, 49, 50, 52, 54, 55, 57, 60, + 63, 64, 64, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 37, 38, 40, 41, + 43, 47, 47, 47, 48, 47, 46, 46, 46, 47, 49, 49, 50, 52, 55, 55, 56, 58, + 59, 60, 62, 63, 64, 65, 67, 68, 69, 70, 48, 47, 46, 46, 47, 47, 50, 51, + 53, 53, 53, 53, 54, 54, 55, 56, 56, 58, 60, 61, 61, 63, 64, 65, 66, 67, + 66, 66, 66, 66, 67, 67, 52, 50, 48, 48, 47, 47, 50, 52, 54, 57, 61, 62, + 64, 66, 68, 69, 70, 72, 75, 75, 75, 76, 74, 72, 73, 74, 75, 75, 74, 74, + 73, 73, 57, 54, 52, 51, 51, 50, 53, 55, 57, 60, 64, 65, 67, 71, 73, 75, + 76, 79, 82, 82, 83, 85, 86, 85, 83, 82, 83, 84, 84, 83, 82, 81, 66, 63, + 60, 59, 59, 57, 60, 61, 62, 66, 69, 71, 73, 77, 80, 82, 84, 88, 92, 92, + 93, 95, 94, 95, 96, 93, 92, 93, 94, 93, 91, 90, 68, 65, 63, 62, 60, 60, + 59, 61, 62, 65, 66, 68, 72, 73, 76, 79, 80, 84, 87, 89, 93, 94, 98, 99, + 99, 102, 101, 102, 103, 103, 101, 99, 71, 67, 66, 64, 63, 62, 62, 61, + 62, 64, 66, 67, 70, 71, 75, 76, 79, 81, 84, 86, 89, 91, 94, 97, 98, 102, + 104, 106, 106, 109, 109, 108 + ] + ] + ]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs new file mode 100644 index 0000000000..b93431b147 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; + +internal class DeQuant +{ + private readonly short[][] dcContent; + private readonly short[][] acContent; + + public DeQuant() + { + this.dcContent = new short[Av1Constants.MaxSegmentCount][]; + this.acContent = new short[Av1Constants.MaxSegmentCount][]; + for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) + { + this.dcContent[segmentId] = new short[Av1Constants.MaxPlanes]; + this.acContent[segmentId] = new short[Av1Constants.MaxPlanes]; + } + } + + public short GetDc(int segmentId, Av1Plane plane) + => this.dcContent[segmentId][(int)plane]; + + public short GetAc(int segmentId, Av1Plane plane) + => this.acContent[segmentId][(int)plane]; + + public void SetAc(int segmentId, Av1Plane plane, short value) + => this.dcContent[segmentId][(int)plane] = value; + + public void SetDc(int segmentId, Av1Plane plane, short value) + => this.dcContent[segmentId][(int)plane] = value; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs new file mode 100644 index 0000000000..e6ca2f400e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs @@ -0,0 +1,190 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; + +internal class QuantizationLookup +{ + // Coefficient scaling and quantization with AV1 TX are tailored to + // the AV1 TX transforms. Regardless of the bit-depth of the input, + // the transform stages scale the coefficient values up by a factor of + // 8 (3 bits) over the scale of the pixel values. Thus, for 8-bit + // input, the coefficients have effectively 11 bits of scale depth + // (8+3), 10-bit input pixels result in 13-bit coefficient depth + // (10+3) and 12-bit pixels yield 15-bit (12+3) coefficient depth. + // All quantizers are built using this invariant of x8, 3-bit scaling, + // thus the Q3 suffix. + + // A partial exception to this rule is large transforms; to avoid + // overflow, TX blocks with > 256 pels (>16x16) are scaled only + // 4-times unity (2 bits) over the pixel depth, and TX blocks with + // over 1024 pixels (>32x32) are scaled up only 2x unity (1 bit). + // This descaling is found via av1_tx_get_scale(). Thus, 16x32, 32x16 + // and 32x32 transforms actually return Q2 coefficients, and 32x64, + // 64x32 and 64x64 transforms return Q1 coefficients. However, the + // quantizers are de-scaled down on-the-fly by the same amount + // (av1_tx_get_scale()) during quantization, and as such the + // dequantized/decoded coefficients, even for large TX blocks, are always + // effectively Q3. Meanwhile, quantized/coded coefficients are Q0 + // because Qn quantizers are applied to Qn tx coefficients. + + // Note that encoder decision making (which uses the quantizer to + // generate several bespoke lamdas for RDO and other heuristics) + // expects quantizers to be larger for higher-bitdepth input. In + // addition, the minimum allowable quantizer is 4; smaller values will + // underflow to 0 in the actual quantization routines. + private static readonly short[] AcQlookup8 = [ + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, + 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, + 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, + 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188, + 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260, + 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366, + 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520, + 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743, + 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066, + 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, 1537, + 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, + ]; + + private static readonly short[] AcQlookup10 = [ + 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, + 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, 131, 136, 140, 145, + 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, + 235, 240, 244, 249, 253, 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, + 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, 379, 384, 388, 392, 396, + 401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, + 555, 563, 571, 579, 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749, + 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, 954, 970, 986, 1002, 1018, 1038, + 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, 1463, + 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, + 2119, 2159, 2199, 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971, + 3027, 3083, 3143, 3203, 3263, 3327, 3391, 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, + 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, + 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, + ]; + + private static readonly short[] AcQlookup12 = [ + 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, + 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, 420, 438, + 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735, + 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, 939, 957, 976, 994, 1012, 1030, + 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317, + 1335, 1352, 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, + 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, 1922, 1954, 1987, 2020, 2052, 2085, 2118, + 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750, + 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619, + 3684, 3748, 3812, 3876, 3941, 4005, 4069, 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791, + 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, 6297, 6410, + 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635, + 8795, 8956, 9132, 9308, 9484, 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661, + 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, 14093, 14365, 14637, 14925, 15213, 15502, 15806, + 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, 20670, 21070, 21486, + 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247, + ]; + + private static readonly short[] DcQlookup8 = [ + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, + 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, 38, 39, 40, + 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, + 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, 68, 69, 70, 70, 71, 72, 73, + 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92, + 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121, + 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 161, 164, + 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, + 223, 226, 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, + 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, 364, 369, 374, 379, 384, + 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, + 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775, + 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, + ]; + + private static readonly short[] DcQlookup10 = [ + 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, + 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, 116, 120, 124, 128, + 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, + 204, 208, 212, 215, 219, 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, + 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, 321, 324, 327, 331, 334, + 337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, + 454, 460, 466, 472, 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592, + 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, 729, 739, 749, 759, 770, 782, + 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, + 1045, 1061, 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, + 1361, 1379, 1398, 1416, 1436, 1456, 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741, + 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363, + 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, 3375, 3478, 3586, 3702, 3823, + 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, + ]; + + private static readonly short[] DcQlookup12 = [ + 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, + 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, 374, 390, + 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643, + 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, 814, 829, 844, 859, 874, 889, + 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122, + 1136, 1151, 1165, 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, + 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, 1618, 1643, 1668, 1692, 1717, 1741, + 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199, + 2233, 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788, + 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517, + 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, 4420, + 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517, + 5591, 5665, 5745, 5825, 5905, 5984, 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867, + 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, 8492, 8635, 8788, + 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409, + 12750, 13118, 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, + ]; + + public static short GetDcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) + { + int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); + switch (bitDepth) + { + case Av1BitDepth.EightBit: + return DcQlookup8[qClamped]; + case Av1BitDepth.TenBit: + return DcQlookup10[qClamped]; + case Av1BitDepth.TwelveBit: + return DcQlookup12[qClamped]; + default: + Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT"); + return -1; + } + } + + public static short GetAcQuant(int qIndex, int dcDeltaQ, Av1BitDepth bitDepth) + { + int qClamped = Av1Math.Clamp(qIndex + dcDeltaQ, 0, Av1Constants.MaxQ); + switch (bitDepth) + { + case Av1BitDepth.EightBit: + return AcQlookup8[qClamped]; + case Av1BitDepth.TenBit: + return AcQlookup10[qClamped]; + case Av1BitDepth.TwelveBit: + return AcQlookup12[qClamped]; + default: + Guard.IsFalse(true, nameof(bitDepth), "bit_depth should be EB_EIGHT_BIT, EB_TEN_BIT or EB_TWELVE_BIT"); + return -1; + } + } + + public static int GetQIndex(ObuSegmentationParameters segmentationParameters, int segmentId, int baseQIndex) + { + if (segmentationParameters.IsFeatureActive(segmentId, ObuSegmentationLevelFeature.AlternativeQuantizer)) + { + int data = segmentationParameters.FeatureData[segmentId, (int)ObuSegmentationLevelFeature.AlternativeQuantizer]; + int qIndex = baseQIndex + data; + return Av1Math.Clamp(qIndex, 0, Av1Constants.MaxQ); + } + else + { + return baseQIndex; + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Readme.md b/src/ImageSharp/Formats/Heif/Av1/Readme.md index c523cde784..9f658e90c8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Readme.md +++ b/src/ImageSharp/Formats/Heif/Av1/Readme.md @@ -63,6 +63,8 @@ The smallest unit in the frame. It determines the parameters for an area of 4 by [AOM's original development implementation](https://github.com/AOMediaCodec/libavif) +[Paper describing the techniques used in AV1](https://arxiv.org/pdf/2008.06091) + # Test images [Netflix image repository](http://download.opencontent.netflix.com/?prefix=AV1/) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 47780069e8..ae3f4ddc47 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -1655,7 +1655,7 @@ private int GetSegmentId(Av1PartitionInfo partitionInfo, int rowIndex, int colum int bh4 = partitionInfo.ModeInfo.BlockSize.Get4x4HighCount(); int xMin = Math.Min(this.FrameHeader.ModeInfoColumnCount - columnIndex, bw4); int yMin = Math.Min(this.FrameHeader.ModeInfoRowCount - rowIndex, bh4); - int segmentId = Av1Constants.MaxSegments - 1; + int segmentId = Av1Constants.MaxSegmentCount - 1; for (int y = 0; y < yMin; y++) { for (int x = 0; x < xMin; x++) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs new file mode 100644 index 0000000000..80a3502751 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -0,0 +1,192 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1BlockDecoder +{ + private readonly ObuSequenceHeader sequenceHeader; + + private readonly ObuFrameHeader frameHeader; + + private readonly Av1FrameInfo frameInfo; + + private readonly Av1FrameBuffer frameBuffer; + + public Av1BlockDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + this.frameInfo = frameInfo; + this.frameBuffer = frameBuffer; + } + + public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo) + { + /* + ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig; + Av1TransformType transformType; + Span coefficients; + Av1TransformSize transformSize; + int transformUnitCount; + bool hasChroma = Av1TileReader.HasChroma(this.sequenceHeader, modeInfoPosition, blockSize); + Av1PartitionInfo partitionInfo = new(modeInfo, superblockInfo, hasChroma, Av1PartitionType.None); + + int maxBlocksWide = partitionInfo.GetMaxBlockWide(blockSize, false); + int maxBlocksHigh = partitionInfo.GetMaxBlockHigh(blockSize, false); + + bool isLossless = this.frameHeader.LosslessArray[modeInfo.SegmentId]; + bool isLosslessBlock = isLossless && ((blockSize >= Av1BlockSize.Block64x64) && (blockSize <= Av1BlockSize.Block128x128)); + int chromaTransformUnitCount = isLosslessBlock + ? (maxBlocksWide * maxBlocksHigh) >> ((colorConfig.SubSamplingX ? 1 : 0) + (colorConfig.SubSamplingY ? 1 : 0)) + : modeInfo.TransformUnitsCount[(int)Av1Plane.U]; + bool highBitDepth = false; + bool is16BitsPipeline = false; + int loopFilterStride = this.frameHeader.ModeInfoStride; + + for (int plane = 0; plane < colorConfig.PlaneCount; plane++) + { + int subX = (plane > 0) ? colorConfig.SubSamplingX ? 1 : 0 : 0; + int subY = (plane > 0) ? colorConfig.SubSamplingY ? 1 : 0 : 0; + + if (plane != 0 && !partitionInfo.IsChroma) + { + continue; + } + + int transformInfoIndex = plane switch + { + 2 => superblockInfo.TransformInfoIndexUv + modeInfo.FirstTransformLocation[plane - 1] + chromaTransformUnitCount, + 1 => superblockInfo.TransformInfoIndexY + modeInfo.FirstTransformLocation[plane], + 0 => superblockInfo.TransformInfoIndexY + modeInfo.FirstTransformLocation[plane], + _ => throw new InvalidImageContentException("Maximum of 3 color planes") + }; + ref Av1TransformInfo transformInfo = ref Unsafe.Add(ref this.frameInfo.GetSuperblockTransform(plane, superblockInfo.Position), transformInfoIndex); + + if (isLosslessBlock) + { + Guard.IsTrue(transformInfo.Size == Av1TransformSize.Size4x4, nameof(transformInfo.Size), "Lossless may only have 4x4 blocks."); + transformUnitCount = (maxBlocksWide * maxBlocksHigh) >> (subX + subY); + } + else + { + transformUnitCount = modeInfo.TransformUnitsCount[Math.Min(1, plane)]; + } + + Guard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), "Must have at least a single transform unit to decode."); + + this.DeriveBlockPointers( + this.reconstructionFrameBuffer, + plane, + (modeInfoPosition.X >> subX) << Av1Constants.ModeInfoSizeLog2, + (modeInfoPosition.Y >> subY) << Av1Constants.ModeInfoSizeLog2, + out Span blockReconstructionBuffer, + out int reconstructionStride, + subX, + subY); + + for (int tu = 0; tu < transformUnitCount; tu++) + { + Span transformBlockReconstructionBuffer; + int transformBlockOffset; + + transformSize = transformInfo.Size; + coefficients = this.currentCoefficients[plane]; + + transformBlockOffset = ((transformInfo.OffsetY * reconstructionStride) + transformInfo.OffsetX) << Av1Constants.ModeInfoSizeLog2; + transformBlockReconstructionBuffer = blockReconstructionBuffer.Slice(transformBlockOffset << (highBitDepth ? 1 : 0)); + + if (this.isLoopFilterEnabled) + { + if (plane != 2) + { + Fill4x4LoopFilterParameters( + this.loopFilterContext, + (modeInfoPosition.X & (~subX)) + (transformInfo.OffsetX << subX), + (modeInfoPosition.Y & (~subY)) + (transformInfo.OffsetY << subY), + loopFilterStride, + transformSize, + subX, + subY, + plane); + } + } + + // if (!inter_block) + if (true) + { + PredictIntra( + partitionInfo, + plane, + transformSize, + tile, + transformBlockReconstructionBuffer, + reconstructionStride, + this.reconstructionFrameBuffer.BitDepth, + transformInfo.OffsetX, + transformInfo.OffsetY); + } + + int numberOfCoefficients = 0; + + if (!modeInfo.Skip && transformInfo.CodeBlockFlag) + { + Span quantizationCoefficients = this.CurrentInverseQuantizationCoefficients; + int inverseQuantizationSize = transformSize.GetWidth() * transformSize.GetHeight(); + quantizationCoefficients[..inverseQuantizationSize].Clear(); + this.CurrentInverseQuantizationCoefficients = quantizationCoefficients[inverseQuantizationSize..]; + transformType = transformInfo.Type; + + numberOfCoefficients = InverseQuantize( + partitionInfo, modeInfo, coefficients, quantizationCoefficients, transformType, transformSize, plane); + if (numberOfCoefficients != 0) + { + this.CurrentCoefficients[plane] += numberOfCoefficients + 1; + + if (this.reconstructionFrameBuffer.BitDepth == Av1BitDepth.EightBit && !is16BitsPipeline) + { + InverseTransformReconstruction8Bit( + quantizationCoefficients, + (Span)transformBlockReconstructionBuffer, + reconstructionStride, + (Span)transformBlockReconstructionBuffer, + reconstructionStride, + transformSize, + transformType, + plane, + numberOfCoefficients, + isLossless); + } + else + { + throw new NotImplementedException("No support for 16 bit pipeline yet."); + } + } + } + + // Store Luma for CFL if required! + if (plane == (int)Av1Plane.Y && StoreChromeFromLumeRequired(colorConfig, partitionInfo, this.frameHeader.IsChroma)) + { + ChromaFromLumaStoreTransform( + partitionInfo, + this.chromaFromLumaContext, + transformInfo.OffsetY, + transformInfo.OffsetX, + transformSize, + blockSize, + colorConfig, + transformBlockReconstructionBuffer, + reconstructionStride, + is16BitsPipeline); + } + + // increment transform pointer + transformInfo = ref Unsafe.Add(ref transformInfo, 1); + } + }*/ + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs deleted file mode 100644 index 981075d83f..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1FrameDecoder.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -internal class Av1FrameDecoder -{ - private readonly ObuSequenceHeader sequenceHeader; - private readonly ObuFrameHeader frameHeader; - private readonly Av1FrameInfo frameInfo; - - public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) - { - this.sequenceHeader = sequenceHeader; - this.frameHeader = frameHeader; - this.frameInfo = frameInfo; - } - - public void DecodeFrame() - { - Guard.NotNull(this.sequenceHeader); - Guard.NotNull(this.frameHeader); - Guard.NotNull(this.frameInfo); - - // TODO: Implement. - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs deleted file mode 100644 index c3213378cb..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseQuantizer.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; - -internal class Av1InverseQuantizer -{ - public static int InverseQuantize(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, ObuPartitionInfo part, Av1BlockModeInfo mode, int[] level, int[] qCoefficients, Av1TransformType txType, Av1TransformSize txSize, Av1Plane plane) - { - Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(txSize, txType); - short[] scanIndices = scanOrder.Scan; - int maxValue = (1 << (7 + sequenceHeader.ColorConfig.BitDepth)) - 1; - int minValue = -(1 << (7 + sequenceHeader.ColorConfig.BitDepth)); - bool usingQuantizationMatrix = frameHeader.QuantizationParameters.IsUsingQMatrix; - bool lossless = frameHeader.LosslessArray[mode.SegmentId]; - short[] dequant = []; // Get from DecModCtx - int qmLevel = (lossless || !usingQuantizationMatrix) ? Av1ScanOrderConstants.QuantizationMatrixLevelCount - 1 : frameHeader.QuantizationParameters.QMatrix[(int)plane]; - byte[] iqMatrix = []; // txType.Is2dTransform() ? Get from DecModCtx - int shift = txSize.GetScale(); - - int coefficientCount = level[0]; - Span levelSpan = level.AsSpan(1); - int lev = levelSpan[0]; - int qCoefficient; - if (lev != 0) - { - int pos = scanIndices[0]; - qCoefficient = (int)(((long)Math.Abs(lev) * GetDequantizationValue(dequant[0], pos, iqMatrix)) & 0xffffff); - qCoefficient >>= shift; - - if (lev < 0) - { - qCoefficient = -qCoefficient; - } - - qCoefficients[0] = Av1Math.Clamp(qCoefficient, minValue, maxValue); - } - - for (int i = 1; i < coefficientCount; i++) - { - lev = levelSpan[i]; - if (lev != 0) - { - int pos = scanIndices[i]; - qCoefficient = (int)(((long)Math.Abs(lev) * GetDequantizationValue(dequant[1], pos, iqMatrix)) & 0xffffff); - qCoefficient >>= shift; - - if (lev < 0) - { - qCoefficient = -qCoefficient; - } - - qCoefficients[pos] = Av1Math.Clamp(qCoefficient, minValue, maxValue); - } - } - - return coefficientCount; - } - - private static int GetDequantizationValue(short dequant, int coefficientIndex, byte[] iqMatrix) - { - int dqv = dequant; - if (iqMatrix != null) - { - dqv = ((iqMatrix[coefficientIndex] * dqv) + (1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1))) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount; - } - - return dqv; - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 37677a3f4b..3766c5a6d8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -135,6 +135,8 @@ internal static class Av1TransformSizeExtensions 2, 3, 4, 5, 6, 3, 2, 4, 3, 5, 4, 6, 5, 4, 2, 5, 3, 6, 4, ]; + public static int GetSize2d(this Av1TransformSize size) => Size2d[(int)size]; + public static int GetScale(this Av1TransformSize size) { int pels = Size2d[(int)size]; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 0a13e08945..44a9fdc97e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -283,7 +283,7 @@ private static ObuSequenceHeader GetDefaultSequenceHeader() MatrixCoefficients = ObuMatrixCoefficients.Unspecified, SubSamplingX = false, SubSamplingY = false, - BitDepth = 8, + BitDepth = Av1BitDepth.EightBit, HasSeparateUvDelta = true, ColorRange = true, }, From 2b60337e81b6dec61516c4bb0ba561e260c86904 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 19 Aug 2024 20:51:31 +0200 Subject: [PATCH 154/190] Rename according to naming scheme --- src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs | 2 +- .../{DeQuant.cs => Av1DeQuantizationContext.cs} | 4 ++-- .../Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/{DeQuant.cs => Av1DeQuantizationContext.cs} (93%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index b4618c7a1f..e8211a3534 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -14,7 +14,7 @@ internal class Av1FrameDecoder private readonly ObuFrameHeader frameHeader; private readonly Av1FrameInfo frameInfo; private readonly Av1InverseQuantizer inverseQuantizer; - private readonly DeQuant deQuants; + private readonly Av1DeQuantizationContext deQuants; public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs similarity index 93% rename from src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs rename to src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs index b93431b147..304b72cdf5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/DeQuant.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1DeQuantizationContext.cs @@ -3,12 +3,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; -internal class DeQuant +internal class Av1DeQuantizationContext { private readonly short[][] dcContent; private readonly short[][] acContent; - public DeQuant() + public Av1DeQuantizationContext() { this.dcContent = new short[Av1Constants.MaxSegmentCount][]; this.acContent = new short[Av1Constants.MaxSegmentCount][]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs index 550c09b951..80904460d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -15,7 +15,7 @@ internal class Av1InverseQuantizer private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; private readonly int[][][] inverseQuantizationMatrix; - private DeQuant? deQuantsDeltaQ; + private Av1DeQuantizationContext? deQuantsDeltaQ; public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { @@ -55,7 +55,7 @@ public Av1InverseQuantizer(ObuSequenceHeader sequenceHeader, ObuFrameHeader fram } } - public void UpdateDequant(DeQuant deQuants, Av1SuperblockInfo superblockInfo) + public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo superblockInfo) { Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth; Guard.NotNull(deQuants, nameof(deQuants)); From dfff87ee7b19220a19bffb6b7bb9b24aa7b9f99d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 19 Aug 2024 20:53:30 +0200 Subject: [PATCH 155/190] Implement interface in HeifMetadata --- src/ImageSharp/Formats/Heif/HeifMetadata.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Heif/HeifMetadata.cs b/src/ImageSharp/Formats/Heif/HeifMetadata.cs index 6bbf8b49e6..96b0512264 100644 --- a/src/ImageSharp/Formats/Heif/HeifMetadata.cs +++ b/src/ImageSharp/Formats/Heif/HeifMetadata.cs @@ -65,4 +65,10 @@ public FormatConnectingMetadata ToFormatConnectingMetadata() /// public HeifMetadata DeepClone() => new(this); + + /// + public void AfterImageApply(Image destination) + where TPixel : unmanaged, IPixel + { + } } From d748a0d8040735c3fec5c12eb2e419aaa67b6d22 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 20 Aug 2024 20:16:31 +0200 Subject: [PATCH 156/190] Introduce DC predictors --- .../Heif/Av1/Prediction/Av1DcFillPredictor.cs | 28 ++++ .../Heif/Av1/Prediction/Av1DcLeftPredictor.cs | 34 +++++ .../Heif/Av1/Prediction/Av1DcPredictor.cs | 40 ++++++ .../Heif/Av1/Prediction/Av1DcTopPredictor.cs | 34 +++++ .../Heif/Av1/Prediction/IAv1Predictor.cs | 19 +++ .../Formats/Heif/Av1/PredictorTests.cs | 123 ++++++++++++++++++ .../Av1/{SymbolTest.cs => SymbolTests.cs} | 2 +- 7 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs rename tests/ImageSharp.Tests/Formats/Heif/Av1/{SymbolTest.cs => SymbolTests.cs} (99%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs new file mode 100644 index 0000000000..d0bc59f1c0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcFillPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcFillPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + const byte expectedDc = 0x80; + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs new file mode 100644 index 0000000000..4aea10bdf4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcLeftPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcLeftPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + for (uint i = 0; i < this.blockHeight; i++) + { + sum += Unsafe.Add(ref left, i); + } + + byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs new file mode 100644 index 0000000000..f3a3cf4ae6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + uint count = this.blockWidth + this.blockHeight; + for (uint i = 0; i < this.blockWidth; i++) + { + sum += Unsafe.Add(ref above, i); + } + + for (uint i = 0; i < this.blockHeight; i++) + { + sum += Unsafe.Add(ref left, i); + } + + byte expectedDc = (byte)((sum + (count >> 1)) / count); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs new file mode 100644 index 0000000000..a79a473938 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1DcTopPredictor : IAv1Predictor +{ + private readonly uint blockWidth; + private readonly uint blockHeight; + + public Av1DcTopPredictor(Size blockSize) + { + this.blockWidth = (uint)blockSize.Width; + this.blockHeight = (uint)blockSize.Height; + } + + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + { + int sum = 0; + for (uint i = 0; i < this.blockWidth; i++) + { + sum += Unsafe.Add(ref above, i); + } + + byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth); + for (uint r = 0; r < this.blockHeight; r++) + { + Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); + destination = ref Unsafe.Add(ref destination, stride); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs new file mode 100644 index 0000000000..3bf8907789 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +/// +/// Interface for predictor implementations. +/// +internal interface IAv1Predictor +{ + /// + /// Predict using scalar logic within the 8-bit pipeline. + /// + /// The destination to write to. + /// The stride of the destination buffer. + /// Pointer to the first element of the block above. + /// Pointer to the first element of the block to the left. + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left); +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs new file mode 100644 index 0000000000..42dfaa568a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs @@ -0,0 +1,123 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class PredictorTests +{ + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcFill(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[1]; + byte[] above = new byte[1]; + byte expected = 0x80; + + // Act + Av1DcFillPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDc(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + int count = width + height; + int sum = Sum(left, height) + Sum(above, width); + byte expected = (byte)((sum + (count >> 1)) / count); + + // Act + Av1DcPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.Equal((5 * height) + (28 * width), sum); + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcLeft(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + byte expected = left[0]; + + // Act + Av1DcLeftPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + [Theory] + [MemberData(nameof(GetTransformSizes))] + public void VerifyDcTop(int width, int height) + { + // Assign + byte[] destination = new byte[width * height]; + byte[] left = new byte[width * height]; + byte[] above = new byte[width * height]; + Array.Fill(left, (byte)5); + Array.Fill(above, (byte)28); + byte expected = above[0]; + + // Act + Av1DcTopPredictor predictor = new(new Size(width, height)); + predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + + // Assert + Assert.All(destination, (b) => AssertValue(expected, b)); + } + + private static void AssertValue(byte expected, byte actual) + { + Assert.NotEqual(0, actual); + Assert.Equal(expected, actual); + } + + private static int Sum(Span values, int length) + { + int sum = 0; + for (int i = 0; i < length; i++) + { + sum += values[i]; + } + + return sum; + } + + public static TheoryData GetTransformSizes() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) + { + Av1TransformSize size = (Av1TransformSize)s; + int width = size.GetWidth(); + int height = size.GetHeight(); + combinations.Add(width, height); + } + + return combinations; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs similarity index 99% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs index 33a47cd807..756319adaf 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTest.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class SymbolTest +public class SymbolTests { [Fact] public void ReadRandomLiteral() From c9baaf019b9380e56722642af4f17f7b0dbf0065 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 24 Aug 2024 14:22:17 +0200 Subject: [PATCH 157/190] Prediction decoding --- .../Formats/Heif/Av1/Av1Constants.cs | 2 + src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 9 +- .../Formats/Heif/Av1/Av1FrameBuffer.cs | 4 + src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 3 + .../Av1BottomRightTopLeftConstants.cs | 505 ++++++++ .../Heif/Av1/Prediction/Av1NeighborNeed.cs | 15 + .../Av1/Prediction/Av1PredictorFactory.cs | 18 + .../Prediction/Av1PreditionModeExtensions.cs | 40 + .../Av1ChromaFromLumaContext.cs | 120 ++ .../ChromaFromLuma/Av1ChromaFromLumaMath.cs | 26 + .../Heif/Av1/Prediction/PredictionDecoder.cs | 1094 +++++++++++++++++ .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 2 + .../Heif/Av1/Tiling/Av1FilterIntraMode.cs | 3 +- .../Heif/Av1/Tiling/Av1PartitionInfo.cs | 63 +- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 18 +- .../Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 16 files changed, 1902 insertions(+), 22 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index cb55ea1ad0..a31bb137fe 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -173,4 +173,6 @@ internal static class Av1Constants /// Total number of Quantification Matrices sets stored. ///
public const int QuantificationMatrixLevelCount = 4; + + public const int AngleStep = 3; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index ff26b2e5cc..6f99ada289 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -10,10 +10,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal class Av1Decoder : IAv1TileReader { private readonly ObuReader obuReader; + private readonly Configuration configuration; private Av1TileReader? tileReader; private Av1FrameDecoder? frameDecoder; - public Av1Decoder() => this.obuReader = new(); + public Av1Decoder(Configuration configuration) + { + this.configuration = configuration; + this.obuReader = new(); + } public ObuFrameHeader? FrameHeader { get; private set; } @@ -40,7 +45,7 @@ public void ReadTile(Span tileData, int tileNum) { this.SequenceHeader = this.obuReader.SequenceHeader; this.FrameHeader = this.obuReader.FrameHeader; - this.tileReader = new Av1TileReader(this.SequenceHeader!, this.FrameHeader!); + this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader!, this.FrameHeader!); } this.tileReader.ReadTile(tileData, tileNum); diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs index a6a0f77954..301804bf65 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs @@ -33,6 +33,8 @@ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHea int topPadding = DecoderPaddingValue; int bottomPadding = DecoderPaddingValue; + this.StartPosition = new Point(leftPadding, topPadding); + this.Width = this.MaxWidth; this.Height = this.MaxHeight; int strideY = this.MaxWidth + leftPadding + rightPadding; @@ -86,6 +88,8 @@ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHea this.BitIncrementCr = null; } + public Point StartPosition { get; private set; } + /// /// Gets the Y luma buffer. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 936186a3e0..a023529493 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -152,4 +152,7 @@ internal static int DivideLog2Ceiling(int value, int n) internal static int Modulus8(int value) => value & 0x07; internal static int DivideBy8Floor(int value) => value >> 3; + + internal static int RoundPowerOf2Signed(int value, int n) + => (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs new file mode 100644 index 0000000000..a3a3e0bd8b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1BottomRightTopLeftConstants.cs @@ -0,0 +1,505 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1BottomRightTopLeftConstants +{ + // Tables to store if the top-right reference pixels are available. The flags + // are represented with bits, packed into 8-bit integers. E.g., for the 32x32 + // blocks in a 128x128 superblock, the index of the "o" block is 10 (in raster + // order), so its flag is stored at the 3rd bit of the 2nd entry in the table, + // i.e. (table[10 / 8] >> (10 % 8)) & 1. + // . . . . + // . . . . + // . . o . + // . . . . + private static readonly byte[] HasTopRight4x4 = [ + 255, 255, 255, 255, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, + 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, + 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 255, 255, + 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, + 119, 119, 119, 119, 85, 85, 85, 85, 255, 127, 255, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, + 85, 85, 127, 127, 127, 127, 85, 85, 85, 85, 119, 119, 119, 119, 85, 85, 85, 85, + ]; + + private static readonly byte[] HasTopRight4x8 = [ + 255, 255, 255, 255, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 127, 255, 127, 119, 119, + 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, 255, 255, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, + 119, 119, 119, 119, 255, 127, 255, 127, 119, 119, 119, 119, 127, 127, 127, 127, 119, 119, 119, 119, + ]; + + private static readonly byte[] HasTopRight8x4 = [ + 255, 255, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 127, 127, 0, 0, 85, 85, + 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, 255, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, + 85, 85, 0, 0, 127, 127, 0, 0, 85, 85, 0, 0, 119, 119, 0, 0, 85, 85, 0, 0, + ]; + + private static readonly byte[] HasTopRight8x8 = [ + 255, 255, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85, + 255, 127, 85, 85, 119, 119, 85, 85, 127, 127, 85, 85, 119, 119, 85, 85, + ]; + + private static readonly byte[] HasTopRight8x16 = [ + 255, + 255, + 119, + 119, + 127, + 127, + 119, + 119, + 255, + 127, + 119, + 119, + 127, + 127, + 119, + 119, + ]; + + private static readonly byte[] HasTopRight16x8 = [ + 255, + 0, + 85, + 0, + 119, + 0, + 85, + 0, + 127, + 0, + 85, + 0, + 119, + 0, + 85, + 0, + ]; + + private static readonly byte[] HasTopRight16x16 = [ + 255, + 85, + 119, + 85, + 127, + 85, + 119, + 85, + ]; + + private static readonly byte[] HasTopRight16x32 = [255, 119, 127, 119]; + private static readonly byte[] HasTopRight32x16 = [15, 5, 7, 5]; + private static readonly byte[] HasTopRight32x32 = [95, 87]; + private static readonly byte[] HasTopRight32x64 = [127]; + private static readonly byte[] HasTopRight64x32 = [19]; + private static readonly byte[] HasTopRight64x64 = [7]; + private static readonly byte[] HasTopRight64x128 = [3]; + private static readonly byte[] HasTopRight128x64 = [1]; + private static readonly byte[] HasTopRight128x128 = [1]; + private static readonly byte[] HasTopRight4x16 = [ + 255, 255, 255, 255, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127, + 255, 255, 255, 127, 127, 127, 127, 127, 255, 127, 255, 127, 127, 127, 127, 127, + ]; + + private static readonly byte[] HasTopRight16x4 = [ + 255, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0, 127, 0, 0, 0, 85, 0, 0, 0, 119, 0, 0, 0, 85, 0, 0, 0, + ]; + + private static readonly byte[] HasTopRight8x32 = [ + 255, + 255, + 127, + 127, + 255, + 127, + 127, + 127, + ]; + + private static readonly byte[] HasTopRight32x8 = [ + 15, + 0, + 5, + 0, + 7, + 0, + 5, + 0, + ]; + + private static readonly byte[] HasTopRight16x64 = [255, 127]; + private static readonly byte[] HasTopRight64x16 = [3, 1]; + + private static readonly byte[][] HasTopRightTables = [ + + // 4X4 + HasTopRight4x4, + + // 4X8, 8X4, 8X8 + HasTopRight4x8, + HasTopRight8x4, + HasTopRight8x8, + + // 8X16, 16X8, 16X16 + HasTopRight8x16, + HasTopRight16x8, + HasTopRight16x16, + + // 16X32, 32X16, 32X32 + HasTopRight16x32, + HasTopRight32x16, + HasTopRight32x32, + + // 32X64, 64X32, 64X64 + HasTopRight32x64, + HasTopRight64x32, + HasTopRight64x64, + + // 64x128, 128x64, 128x128 + HasTopRight64x128, + HasTopRight128x64, + HasTopRight128x128, + + // 4x16, 16x4, 8x32 + HasTopRight4x16, + HasTopRight16x4, + HasTopRight8x32, + + // 32x8, 16x64, 64x16 + HasTopRight32x8, + HasTopRight16x64, + HasTopRight64x16 + ]; + + private static readonly byte[] HasTopRightVertical8x8 = [ + 255, 255, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0, + 255, 127, 0, 0, 119, 119, 0, 0, 127, 127, 0, 0, 119, 119, 0, 0, + ]; + + private static readonly byte[] HasTopRightVertical16x16 = [ + 255, + 0, + 119, + 0, + 127, + 0, + 119, + 0, + ]; + + private static readonly byte[] HasTopRightVertical32x32 = [15, 7]; + private static readonly byte[] HasTopRightVertical64x64 = [3]; + + // The _vert_* tables are like the ordinary tables above, but describe the + // order we visit square blocks when doing a PARTITION_VERT_A or + // PARTITION_VERT_B. This is the same order as normal except for on the last + // split where we go vertically (TL, BL, TR, BR). We treat the rectangular block + // as a pair of squares, which means that these tables work correctly for both + // mixed vertical partition types. + // + // There are tables for each of the square sizes. Vertical rectangles (like + // BLOCK_16X32) use their respective "non-vert" table + private static readonly byte[]?[] HasTopRightVerticalTables = [ + + // 4X4 + null, + + // 4X8, 8X4, 8X8 + HasTopRight4x8, + null, + HasTopRightVertical8x8, + + // 8X16, 16X8, 16X16 + HasTopRight8x16, + null, + HasTopRightVertical16x16, + + // 16X32, 32X16, 32X32 + HasTopRight16x32, + null, + HasTopRightVertical32x32, + + // 32X64, 64X32, 64X64 + HasTopRight32x64, + null, + HasTopRightVertical64x64, + + // 64x128, 128x64, 128x128 + HasTopRight64x128, + null, + HasTopRight128x128 + ]; + + // Similar to the has_tr_* tables, but store if the bottom-left reference + // pixels are available. + private static readonly byte[] HasBottomLeft4x4 = [ + 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, + 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, + 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 1, + 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 1, 0, 84, 85, 85, 85, 16, 17, 17, 17, + 84, 85, 85, 85, 0, 1, 1, 1, 84, 85, 85, 85, 16, 17, 17, 17, 84, 85, 85, 85, 0, 0, 0, 0, + ]; + + private static readonly byte[] HasBottomLeft4x8 = [ + 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0, + 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 1, 0, 16, 17, 17, 17, 0, 1, 1, 1, 16, 17, 17, 17, 0, 0, 0, 0, + ]; + + private static readonly byte[] HasBottomLeft8x4 = [ + 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 1, 254, 255, 84, 85, 254, 255, + 16, 17, 254, 255, 84, 85, 254, 255, 0, 0, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, + 254, 255, 0, 1, 254, 255, 84, 85, 254, 255, 16, 17, 254, 255, 84, 85, 254, 255, 0, 0, + ]; + + private static readonly byte[] HasBottomLeft8x8 = [ + 84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0, + 84, 85, 16, 17, 84, 85, 0, 1, 84, 85, 16, 17, 84, 85, 0, 0, + ]; + + private static readonly byte[] HasBottomLeft8x16 = [ + 16, + 17, + 0, + 1, + 16, + 17, + 0, + 0, + 16, + 17, + 0, + 1, + 16, + 17, + 0, + 0, + ]; + + private static readonly byte[] HasBottomLeft16x8 = [ + 254, + 84, + 254, + 16, + 254, + 84, + 254, + 0, + 254, + 84, + 254, + 16, + 254, + 84, + 254, + 0, + ]; + + private static readonly byte[] HasBottomLeft16x16 = [ + 84, + 16, + 84, + 0, + 84, + 16, + 84, + 0, + ]; + + private static readonly byte[] HasBottomLeft16x32 = [16, 0, 16, 0]; + private static readonly byte[] HasBottomLeft32x16 = [78, 14, 78, 14]; + private static readonly byte[] HasBottomLeft32x32 = [4, 4]; + private static readonly byte[] HasBottomLeft32x64 = [0]; + private static readonly byte[] HasBottomLeft64x32 = [34]; + private static readonly byte[] HasBottomLeft64x64 = [0]; + private static readonly byte[] HasBottomLeft64x128 = [0]; + private static readonly byte[] HasBottomLeft128x64 = [0]; + private static readonly byte[] HasBottomLeft128x128 = [0]; + private static readonly byte[] HasBottomLeft4x16 = [ + 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, + ]; + + private static readonly byte[] HasBottomLeft16x4 = [ + 254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0, + 254, 254, 254, 84, 254, 254, 254, 16, 254, 254, 254, 84, 254, 254, 254, 0, + ]; + + private static readonly byte[] HasBottomLeft8x32 = [ + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + ]; + + private static readonly byte[] HasBottomLeft32x8 = [ + 238, + 78, + 238, + 14, + 238, + 78, + 238, + 14, + ]; + + private static readonly byte[] HasBottomLeft16x64 = [0, 0]; + private static readonly byte[] HasBottomLeft64x16 = [42, 42]; + + private static readonly byte[][] HasBottomLeftTables = [ + + // 4X4 + HasBottomLeft4x4, + + // 4X8, 8X4, 8X8 + HasBottomLeft4x8, + HasBottomLeft8x4, + HasBottomLeft8x8, + + // 8X16, 16X8, 16X16 + HasBottomLeft8x16, + HasBottomLeft16x8, + HasBottomLeft16x16, + + // 16X32, 32X16, 32X32 + HasBottomLeft16x32, + HasBottomLeft32x16, + HasBottomLeft32x32, + + // 32X64, 64X32, 64X64 + HasBottomLeft32x64, + HasBottomLeft64x32, + HasBottomLeft64x64, + + // 64x128, 128x64, 128x128 + HasBottomLeft64x128, + HasBottomLeft128x64, + HasBottomLeft128x128, + + // 4x16, 16x4, 8x32 + HasBottomLeft4x16, + HasBottomLeft16x4, + HasBottomLeft8x32, + + // 32x8, 16x64, 64x16 + HasBottomLeft32x8, + HasBottomLeft16x64, + HasBottomLeft64x16 + ]; + + private static readonly byte[] HasBottomLeftVertical8x8 = [ + 254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0, + 254, 255, 16, 17, 254, 255, 0, 1, 254, 255, 16, 17, 254, 255, 0, 0, + ]; + + private static readonly byte[] HasBottomLeftVertical16x16 = [ + 254, + 16, + 254, + 0, + 254, + 16, + 254, + 0, + ]; + + private static readonly byte[] HasBottomLeftVertical32x32 = [14, 14]; + private static readonly byte[] HasBottomLeftVertical64x64 = [2]; + + // The _vert_* tables are like the ordinary tables above, but describe the + // order we visit square blocks when doing a PARTITION_VERT_A or + // PARTITION_VERT_B. This is the same order as normal except for on the last + // split where we go vertically (TL, BL, TR, BR). We treat the rectangular block + // as a pair of squares, which means that these tables work correctly for both + // mixed vertical partition types. + // + // There are tables for each of the square sizes. Vertical rectangles (like + // BLOCK_16X32) use their respective "non-vert" table + private static readonly byte[]?[] HasBottomLeftVerticalTables = [ + + // 4X4 + null, + + // 4X8, 8X4, 8X8 + HasBottomLeft4x8, + null, + HasBottomLeftVertical8x8, + + // 8X16, 16X8, 16X16 + HasBottomLeft8x16, + null, + HasBottomLeftVertical16x16, + + // 16X32, 32X16, 32X32 + HasBottomLeft16x32, + null, + HasBottomLeftVertical32x32, + + // 32X64, 64X32, 64X64 + HasBottomLeft32x64, + null, + HasBottomLeftVertical64x64, + + // 64x128, 128x64, 128x128 + HasBottomLeft64x128, + null, + HasBottomLeft128x128]; + + public static bool HasTopRight(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex) + { + int index1 = blockIndex / 8; + int index2 = blockIndex % 8; + Span hasBottomLeftTable = GetHasTopRightTable(partitionType, blockSize); + return ((hasBottomLeftTable[index1] >> index2) & 1) > 0; + } + + public static bool HasBottomLeft(Av1PartitionType partitionType, Av1BlockSize blockSize, int blockIndex) + { + int index1 = blockIndex / 8; + int index2 = blockIndex % 8; + Span hasBottomLeftTable = GetHasBottomLeftTable(partitionType, blockSize); + return ((hasBottomLeftTable[index1] >> index2) & 1) > 0; + } + + private static Span GetHasTopRightTable(Av1PartitionType partition, Av1BlockSize blockSize) + { + byte[]? ret; + + // If this is a mixed vertical partition, look up block size in vertical order. + if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB) + { + DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize)); + ret = HasTopRightVerticalTables[(int)blockSize]; + } + else + { + ret = HasTopRightTables[(int)blockSize]; + } + + DebugGuard.NotNull(ret, nameof(ret)); + return ret; + } + + private static Span GetHasBottomLeftTable(Av1PartitionType partition, Av1BlockSize blockSize) + { + byte[]? ret; + + // If this is a mixed vertical partition, look up block size in vertical order. + if (partition is Av1PartitionType.VerticalA or Av1PartitionType.VerticalB) + { + DebugGuard.MustBeLessThan((int)blockSize, (int)Av1BlockSize.SizeS, nameof(blockSize)); + ret = HasBottomLeftVerticalTables[(int)blockSize]; + } + else + { + ret = HasBottomLeftTables[(int)blockSize]; + } + + DebugGuard.NotNull(ret, nameof(ret)); + return ret; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs new file mode 100644 index 0000000000..81408fad31 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1NeighborNeed.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +[Flags] +internal enum Av1NeighborNeed +{ + Nothing = 0, + Left = 2, + Above = 4, + AboveRight = 8, + AboveLeft = 16, + BottomLeft = 32, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs new file mode 100644 index 0000000000..84a0d75d88 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class Av1PredictorFactory +{ + internal static void DcPredictor(bool v1, bool v2, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException(); + + internal static void DirectionalPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException(); + + internal static void FilterIntraPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException(); + + internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs index d3b1df0357..2b2ca3e4f3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PreditionModeExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -23,5 +24,44 @@ internal static class Av1PreditionModeExtensions Av1TransformType.AdstAdst, // PAETH ]; + private static readonly Av1NeighborNeed[] NeedsMap = [ + Av1NeighborNeed.Above | Av1NeighborNeed.Left, // DC + Av1NeighborNeed.Above, // V + Av1NeighborNeed.Left, // H + Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D45 + Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D135 + Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D113 + Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // D157 + Av1NeighborNeed.Left | Av1NeighborNeed.BottomLeft, // D203 + Av1NeighborNeed.Above | Av1NeighborNeed.AboveRight, // D67 + Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH + Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_V + Av1NeighborNeed.Left | Av1NeighborNeed.Above, // SMOOTH_H + Av1NeighborNeed.Left | Av1NeighborNeed.Above | Av1NeighborNeed.AboveLeft, // PAETH + ]; + + private static readonly int[] AngleMap = [ + 0, + 90, + 180, + 45, + 135, + 113, + 157, + 203, + 67, + 0, + 0, + 0, + 0, + ]; + public static Av1TransformType ToTransformType(this Av1PredictionMode mode) => IntraPreditionMode2TransformType[(int)mode]; + + public static bool IsDirectional(this Av1PredictionMode mode) + => mode is >= Av1PredictionMode.Vertical and <= Av1PredictionMode.Directional67Degrees; + + public static Av1NeighborNeed GetNeighborNeed(this Av1PredictionMode mode) => NeedsMap[(int)mode]; + + public static int ToAngle(this Av1PredictionMode mode) => AngleMap[(int)mode]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs new file mode 100644 index 0000000000..c8112028db --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaContext.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; + +internal class Av1ChromaFromLumaContext +{ + private const int BufferLine = 32; + + private int bufferHeight; + private int bufferWidth; + private readonly bool subX; + private readonly bool subY; + + public Av1ChromaFromLumaContext(Configuration configuration, ObuColorConfig colorConfig) + { + this.subX = colorConfig.SubSamplingX; + this.subY = colorConfig.SubSamplingY; + this.Q3Buffer = configuration.MemoryAllocator.Allocate2D(new Size(32, 32), AllocationOptions.Clean); + } + + public Buffer2D Q3Buffer { get; private set; } + + public bool AreParametersComputed { get; private set; } + + public void ComputeParameters(Av1TransformSize transformSize) + { + Guard.IsFalse(this.AreParametersComputed, nameof(this.AreParametersComputed), "Do not call cfl_compute_parameters multiple time on the same values."); + this.Pad(transformSize.GetWidth(), transformSize.GetHeight()); + SubtractAverage(ref this.Q3Buffer[0, 0], transformSize); + this.AreParametersComputed = true; + } + + private void Pad(int width, int height) + { + int diff_width = width - this.bufferWidth; + int diff_height = height - this.bufferHeight; + + if (diff_width > 0) + { + int min_height = height - diff_height; + ref short recon_buf_q3 = ref this.Q3Buffer[width - diff_width, 0]; + for (int j = 0; j < min_height; j++) + { + short last_pixel = Unsafe.Subtract(ref recon_buf_q3, 1); + Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds."); + for (int i = 0; i < diff_width; i++) + { + Unsafe.Add(ref recon_buf_q3, i) = last_pixel; + } + + recon_buf_q3 += BufferLine; + } + + this.bufferWidth = width; + } + + if (diff_height > 0) + { + ref short recon_buf_q3 = ref this.Q3Buffer[0, height - diff_height]; + for (int j = 0; j < diff_height; j++) + { + ref short last_row_q3 = ref Unsafe.Subtract(ref recon_buf_q3, BufferLine); + Guard.IsTrue(Unsafe.IsAddressLessThan(ref Unsafe.Add(ref recon_buf_q3, diff_width), ref this.Q3Buffer[BufferLine, BufferLine]), nameof(recon_buf_q3), "Shall stay within bounds."); + for (int i = 0; i < width; i++) + { + Unsafe.Add(ref recon_buf_q3, i) = Unsafe.Add(ref last_row_q3, i); + } + + recon_buf_q3 += BufferLine; + } + + this.bufferHeight = height; + } + } + + /************************************************************************************************ + * svt_subtract_average_c + * Calculate the DC value by averaging over all sample. Subtract DC value to get AC values In C + ************************************************************************************************/ + private static void SubtractAverage(ref short pred_buf_q3, Av1TransformSize transformSize) + { + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + int roundOffset = (width * height) >> 1; + int pelCountLog2 = transformSize.GetBlockWidthLog2() + transformSize.GetBlockHeightLog2(); + int sum_q3 = 0; + ref short pred_buf = ref pred_buf_q3; + for (int j = 0; j < height; j++) + { + // assert(pred_buf_q3 + tx_width <= cfl->pred_buf_q3 + CFL_BUF_SQUARE); + for (int i = 0; i < width; i++) + { + sum_q3 += Unsafe.Add(ref pred_buf, i); + } + + pred_buf += BufferLine; + } + + int avg_q3 = (sum_q3 + roundOffset) >> pelCountLog2; + + // Loss is never more than 1/2 (in Q3) + // assert(abs((avg_q3 * (1 << num_pel_log2)) - sum_q3) <= 1 << num_pel_log2 >> + // 1); + for (int j = 0; j < height; j++) + { + for (int i = 0; i < width; i++) + { + Unsafe.Add(ref pred_buf_q3, i) -= (short)avg_q3; + } + + pred_buf_q3 += BufferLine; + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs new file mode 100644 index 0000000000..8f3fce0164 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/ChromaFromLuma/Av1ChromaFromLumaMath.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; + +internal static class Av1ChromaFromLumaMath +{ + private const int Signs = 3; + private const int AlphabetSizeLog2 = 4; + + public const int SignZero = 0; + public const int SignNegative = 1; + public const int SignPositive = 2; + + public static int SignU(int jointSign) => ((jointSign + 1) * 11) >> 5; + + public static int SignV(int jointSign) => (jointSign + 1) - (Signs * SignU(jointSign)); + + public static int IndexU(int index) => index >> AlphabetSizeLog2; + + public static int IndexV(int index) => index & (AlphabetSizeLog2 - 1); + + public static int ContextU(int jointSign) => jointSign + 1 - Signs; + + public static int ContextV(int jointSign) => (SignV(jointSign) * Signs) + SignU(jointSign) - Signs; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs new file mode 100644 index 0000000000..0e7f9fb216 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs @@ -0,0 +1,1094 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +internal class PredictionDecoder +{ + private const int MaxUpsampleSize = 16; + + private readonly ObuSequenceHeader sequenceHeader; + private readonly ObuFrameHeader frameHeader; + private readonly bool is16BitPipeline; + + public PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool is16BitPipeline) + { + this.sequenceHeader = sequenceHeader; + this.frameHeader = frameHeader; + this.is16BitPipeline = is16BitPipeline; + } + + public void DecodeFrame( + Av1PartitionInfo partitionInfo, + Av1Plane plane, + Av1TransformSize transformSize, + Av1TileInfo tileInfo, + Av1FrameBuffer frameBuffer, + Av1BitDepth bitDepth, + int blockModeInfoColumnOffset, + int blockModeInfoRowOffset) + { + Buffer2D? pixelBuffer = null; + switch (plane) + { + case Av1Plane.Y: + pixelBuffer = frameBuffer.BufferY; + break; + case Av1Plane.U: + pixelBuffer = frameBuffer.BufferCb; + break; + case Av1Plane.V: + pixelBuffer = frameBuffer.BufferCr; + break; + default: + break; + } + + if (pixelBuffer == null) + { + return; + } + + int bytesPerPixel = (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline) ? 2 : 1; + ref byte pixelRef = ref pixelBuffer[frameBuffer.StartPosition.X, frameBuffer.StartPosition.Y]; + ref byte topNeighbor = ref pixelRef; + ref byte leftNeighbor = ref pixelRef; + int stride = frameBuffer.BufferY!.Width * bytesPerPixel; + topNeighbor = Unsafe.Subtract(ref topNeighbor, stride); + leftNeighbor = Unsafe.Subtract(ref leftNeighbor, 1); + + bool is16BitPipeline = this.is16BitPipeline; + Av1PredictionMode mode = (plane == Av1Plane.Y) ? partitionInfo.ModeInfo.YMode : partitionInfo.ModeInfo.UvMode; + + if (plane != Av1Plane.Y && partitionInfo.ModeInfo.UvMode == Av1PredictionMode.UvChromaFromLuma) + { + this.PredictIntraBlock( + partitionInfo, + plane, + transformSize, + tileInfo, + ref pixelRef, + stride, + ref topNeighbor, + ref leftNeighbor, + stride, + mode, + blockModeInfoColumnOffset, + blockModeInfoRowOffset, + bitDepth); + + this.PredictChromaFromLumaBlock( + partitionInfo, + partitionInfo.ChromaFromLumaContext, + ref pixelBuffer, + stride, + transformSize, + plane); + + return; + } + + this.PredictIntraBlock( + partitionInfo, + plane, + transformSize, + tileInfo, + ref pixelRef, + stride, + ref topNeighbor, + ref leftNeighbor, + stride, + mode, + blockModeInfoColumnOffset, + blockModeInfoRowOffset, + bitDepth); + } + + private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1ChromaFromLumaContext? chromaFromLumaContext, ref Buffer2D pixelBuffer, int stride, Av1TransformSize transformSize, Av1Plane plane) + { + Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; + bool isChromaFromLumaAllowedFlag = IsChromaFromLumaAllowedWithFrameHeader(partitionInfo, this.sequenceHeader.ColorConfig, this.frameHeader); + DebugGuard.IsTrue(isChromaFromLumaAllowedFlag, "Chroma from Luma should be allowed then computing it."); + + if (chromaFromLumaContext == null) + { + throw new InvalidOperationException("CFL context should have been defined already."); + } + + if (!chromaFromLumaContext.AreParametersComputed) + { + chromaFromLumaContext.ComputeParameters(transformSize); + } + + int alphaQ3 = ChromaFromLumaIndexToAlpha(modeInfo.ChromaFromLumaAlphaIndex, modeInfo.ChromaFromLumaAlphaSign, (Av1Plane)((int)plane - 1)); + + // assert((transformSize.GetHeight() - 1) * CFL_BUF_LINE + transformSize.GetWidth() <= CFL_BUF_SQUARE); + Av1BitDepth bitDepth = this.sequenceHeader.ColorConfig.BitDepth; + if ((bitDepth != Av1BitDepth.EightBit) || this.is16BitPipeline) + { + /* 16 bit pipeline + svt_cfl_predict_hbd( + chromaFromLumaContext->recon_buf_q3, + (uint16_t*)dst, + dst_stride, + (uint16_t*)dst, + dst_stride, + alpha_q3, + cc->bit_depth, + tx_size_wide[tx_size], + tx_size_high[tx_size]); + return;*/ + } + + ChromaFromLumaPredict( + chromaFromLumaContext.Q3Buffer!, + pixelBuffer, + stride, + pixelBuffer, + stride, + alphaQ3, + bitDepth, + transformSize.GetWidth(), + transformSize.GetHeight()); + } + + private static bool IsChromaFromLumaAllowedWithFrameHeader(Av1PartitionInfo partitionInfo, ObuColorConfig colorConfig, ObuFrameHeader frameHeader) + { + Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; + Av1BlockSize blockSize = modeInfo.BlockSize; + DebugGuard.MustBeGreaterThan((int)blockSize, (int)Av1BlockSize.AllSizes, nameof(blockSize)); + if (frameHeader.LosslessArray[modeInfo.SegmentId]) + { + // In lossless, CfL is available when the partition size is equal to the + // transform size. + bool subX = colorConfig.SubSamplingX; + bool subY = colorConfig.SubSamplingY; + Av1BlockSize planeBlockSize = blockSize.GetSubsampled(subX, subY); + return planeBlockSize == Av1BlockSize.Block4x4; + } + + // Spec: CfL is available to luma partitions lesser than or equal to 32x32 + return blockSize.GetWidth() <= 32 && blockSize.GetHeight() <= 32; + } + + private static int ChromaFromLumaIndexToAlpha(int alphaIndex, int jointSign, Av1Plane plane) + { + int alphaSign = (plane == Av1Plane.U) ? Av1ChromaFromLumaMath.SignU(jointSign) : Av1ChromaFromLumaMath.SignV(jointSign); + if (alphaSign == Av1ChromaFromLumaMath.SignZero) + { + return 0; + } + + int absAlphaQ3 = (plane == Av1Plane.U) ? Av1ChromaFromLumaMath.IndexU(alphaIndex) : Av1ChromaFromLumaMath.IndexV(alphaIndex); + return (alphaSign == Av1ChromaFromLumaMath.SignPositive) ? absAlphaQ3 + 1 : -absAlphaQ3 - 1; + } + + private static int GetScaledLumaQ0(int alphaQ3, short predictedQ3) + { + int scaledLumaQ6 = alphaQ3 * predictedQ3; + return Av1Math.RoundPowerOf2Signed(scaledLumaQ6, 6); + } + + private static void ChromaFromLumaPredict(Buffer2D predictedBufferQ3, Buffer2D predictedBuffer, int predictedStride, Buffer2D destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height) + { + // TODO: Make SIMD variant of this method. + int maxPixelValue = (1 << bitDepth.GetBitCount()) - 1; + for (int j = 0; j < height; j++) + { + for (int i = 0; i < width; i++) + { + int alphaQ0 = GetScaledLumaQ0(alphaQ3, predictedBufferQ3[i, j]); + destinationBuffer[i, j] = (byte)Av1Math.Clamp(alphaQ0 + predictedBuffer[i, j], 0, maxPixelValue); + } + } + } + + private void PredictIntraBlock( + Av1PartitionInfo partitionInfo, + Av1Plane plane, + Av1TransformSize transformSize, + Av1TileInfo tileInfo, + ref byte pixelBuffer, + int pixelBufferStride, + ref byte topNeighbor, + ref byte leftNeighbor, + int referenceStride, + Av1PredictionMode mode, + int blockModeInfoColumnOffset, + int blockModeInfoRowOffset, + Av1BitDepth bitDepth) + { + // TODO:are_parameters_computed variable for CFL so that cal part for V plane we can skip, + // once we compute for U plane, this parameter is block level parameter. + ObuColorConfig cc = this.sequenceHeader.ColorConfig; + int subX = plane != Av1Plane.Y ? cc.SubSamplingX ? 1 : 0 : 0; + int subY = plane != Av1Plane.Y ? cc.SubSamplingY ? 1 : 0 : 0; + + Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; + + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + + bool usePalette = modeInfo.GetPaletteSize(plane) > 0; + + if (usePalette) + { + return; + } + + Av1FilterIntraMode filterIntraMode = (plane == Av1Plane.Y && modeInfo.FilterIntraModeInfo.UseFilterIntra) + ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.FilterIntraModes; + + int angleDelta = modeInfo.AngleDelta[Math.Min(1, (int)plane)]; + + Av1BlockSize blockSize = modeInfo.BlockSize; + bool haveTop = blockModeInfoRowOffset > 0 || (subY > 0 ? partitionInfo.AvailableAboveForChroma : partitionInfo.AvailableAbove); + bool haveLeft = blockModeInfoColumnOffset > 0 || (subX > 0 ? partitionInfo.AvailableLeftForChroma : partitionInfo.AvailableLeft); + + int modeInfoRow = -partitionInfo.ModeBlockToTopEdge >> (3 + Av1Constants.ModeInfoSizeLog2); + int modeInfoColumn = -partitionInfo.ModeBlockToLeftEdge >> (3 + Av1Constants.ModeInfoSizeLog2); + int xrOffset = 0; + int ydOffset = 0; + + // Distance between right edge of this pred block to frame right edge + int xr = (partitionInfo.ModeBlockToRightEdge >> (3 + subX)) + (partitionInfo.WidthInPixels[(int)plane] - (blockModeInfoColumnOffset << Av1Constants.ModeInfoSizeLog2) - transformWidth) - + xrOffset; + + // Distance between bottom edge of this pred block to frame bottom edge + int yd = (partitionInfo.ModeBlockToBottomEdge >> (3 + subY)) + + (partitionInfo.HeightInPixels[(int)plane] - (blockModeInfoRowOffset << Av1Constants.ModeInfoSizeLog2) - transformHeight) - ydOffset; + bool rightAvailable = modeInfoColumn + ((blockModeInfoColumnOffset + transformWidth) << subX) < tileInfo.ModeInfoColumnEnd; + bool bottomAvailable = (yd > 0) && (modeInfoRow + ((blockModeInfoRowOffset + transformHeight) << subY) < tileInfo.ModeInfoRowEnd); + + Av1PartitionType partition = modeInfo.PartitionType; + + // force 4x4 chroma component block size. + blockSize = ScaleChromaBlockSize(blockSize, subX == 1, subY == 1); + + bool haveTopRight = IntraHasTopRight( + this.sequenceHeader.SuperblockSize, + blockSize, + modeInfoRow, + modeInfoColumn, + haveTop, + rightAvailable, + partition, + transformSize, + blockModeInfoRowOffset, + blockModeInfoColumnOffset, + subX, + subY); + bool haveBottomLeft = IntraHasBottomLeft( + this.sequenceHeader.SuperblockSize, + blockSize, + modeInfoRow, + modeInfoColumn, + bottomAvailable, + haveLeft, + partition, + transformSize, + blockModeInfoRowOffset, + blockModeInfoColumnOffset, + subX, + subY); + + bool disableEdgeFilter = !this.sequenceHeader.EnableIntraEdgeFilter; + + // Calling all other intra predictors except CFL & pallate... + if (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline) + { + this.DecodeBuildIntraPredictors( + partitionInfo, + ref topNeighbor, + ref leftNeighbor, + (nuint)referenceStride, + ref pixelBuffer, + (nuint)pixelBufferStride, + mode, + angleDelta, + filterIntraMode, + transformSize, + disableEdgeFilter, + haveTop ? Math.Min(transformWidth, xr + transformWidth) : 0, + haveTopRight ? Math.Min(transformWidth, xr) : 0, + haveLeft ? Math.Min(transformHeight, yd + transformHeight) : 0, + haveBottomLeft ? Math.Min(transformHeight, yd) : 0, + plane); + } + else + { + /* 16bit + decode_build_intra_predictors_high(xd, + (uint16_t*) top_neigh_array, //As per SVT Enc + (uint16_t*) left_neigh_array, + ref_stride,// As per SVT Enc + (uint16_t*) pv_pred_buf, + pred_stride, + mode, + angle_delta, + filter_intra_mode, + tx_size, + disable_edge_filter, + have_top? AOMMIN(transformWidth, xr + transformWidth) : 0, + have_top_right? AOMMIN(transformWidth, xr) : 0, + have_left? AOMMIN(transformHeight, yd + transformHeight) : 0, + have_bottom_left? AOMMIN(transformHeight, yd) : 0, + plane, + bit_depth); + */ + } + } + + private static Av1BlockSize ScaleChromaBlockSize(Av1BlockSize blockSize, bool subX, bool subY) + { + Av1BlockSize bs = blockSize; + switch (blockSize) + { + case Av1BlockSize.Block4x4: + if (subX && subY) + { + bs = Av1BlockSize.Block8x8; + } + else if (subX) + { + bs = Av1BlockSize.Block8x4; + } + else if (subY) + { + bs = Av1BlockSize.Block4x8; + } + + break; + case Av1BlockSize.Block4x8: + if (subX && subY) + { + bs = Av1BlockSize.Block8x8; + } + else if (subX) + { + bs = Av1BlockSize.Block8x8; + } + else if (subY) + { + bs = Av1BlockSize.Block4x8; + } + + break; + case Av1BlockSize.Block8x4: + if (subX && subY) + { + bs = Av1BlockSize.Block8x8; + } + else if (subX) + { + bs = Av1BlockSize.Block8x4; + } + else if (subY) + { + bs = Av1BlockSize.Block8x8; + } + + break; + case Av1BlockSize.Block4x16: + if (subX && subY) + { + bs = Av1BlockSize.Block8x16; + } + else if (subX) + { + bs = Av1BlockSize.Block8x16; + } + else if (subY) + { + bs = Av1BlockSize.Block4x16; + } + + break; + case Av1BlockSize.Block16x4: + if (subX && subY) + { + bs = Av1BlockSize.Block16x8; + } + else if (subX) + { + bs = Av1BlockSize.Block16x4; + } + else if (subY) + { + bs = Av1BlockSize.Block16x8; + } + + break; + default: + break; + } + + return bs; + } + + private static bool IntraHasBottomLeft(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool bottomAvailable, bool haveLeft, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY) + { + if (!bottomAvailable || !haveLeft) + { + return false; + } + + // Special case for 128x* blocks, when col_off is half the block width. + // This is needed because 128x* superblocks are divided into 64x* blocks in + // raster order + if (blockSize.GetWidth() > 64 && blockModeInfoColumnOffset > 0) + { + int planeBlockWidthInUnits64 = 64 >> subX; + int columnOffset64 = blockModeInfoColumnOffset % planeBlockWidthInUnits64; + if (columnOffset64 == 0) + { + // We are at the left edge of top-right or bottom-right 64x* block. + int planeBlockHeightInUnits64 = 64 >> subY; + int rowOffset64 = blockModeInfoRowOffset % planeBlockHeightInUnits64; + int planeBlockHeightInUnits = Math.Min(blockSize.Get4x4HighCount() >> subY, planeBlockHeightInUnits64); + + // Check if all bottom-left pixels are in the left 64x* block (which is + // already coded). + return rowOffset64 + transformSize.Get4x4HighCount() < planeBlockHeightInUnits; + } + } + + if (blockModeInfoColumnOffset > 0) + { + // Bottom-left pixels are in the bottom-left block, which is not available. + return false; + } + else + { + int blockHeightInUnits = blockSize.GetHeight() >> Av1TransformSize.Size4x4.GetBlockHeightLog2(); + int planeBlockHeightInUnits = Math.Max(blockHeightInUnits >> subY, 1); + int bottomLeftUnitCount = transformSize.Get4x4HighCount(); + + // All bottom-left pixels are in the left block, which is already available. + if (blockModeInfoRowOffset + bottomLeftUnitCount < planeBlockHeightInUnits) + { + return true; + } + + int blockWidthInModeInfoLog2 = blockSize.Get4x4WidthLog2(); + int blockHeightInModeInfoLog2 = blockSize.Get4x4HeightLog2(); + int superblockModeInfoSize = superblockSize.Get4x4HighCount(); + int blockRowInSuperblock = (modeInfoRow & (superblockModeInfoSize - 1)) >> blockHeightInModeInfoLog2; + int blockColumnInSuperblock = (modeInfoColumn & (superblockModeInfoSize - 1)) >> blockWidthInModeInfoLog2; + + // Leftmost column of superblock: so bottom-left pixels maybe in the left + // and/or bottom-left superblocks. But only the left superblock is + // available, so check if all required pixels fall in that superblock. + if (blockColumnInSuperblock == 0) + { + int blockStartRowOffset = blockRowInSuperblock << (blockHeightInModeInfoLog2 + Av1Constants.ModeInfoSizeLog2 - Av1TransformSize.Size4x4.GetBlockWidthLog2()) >> subY; + int rowOffsetInSuperblock = blockStartRowOffset + blockModeInfoRowOffset; + int superblockHeightInUnits = superblockModeInfoSize >> subY; + return rowOffsetInSuperblock + bottomLeftUnitCount < superblockHeightInUnits; + } + + // Bottom row of superblock (and not the leftmost column): so bottom-left + // pixels fall in the bottom superblock, which is not available yet. + if (((blockRowInSuperblock + 1) << blockHeightInModeInfoLog2) >= superblockModeInfoSize) + { + return false; + } + + // General case (neither leftmost column nor bottom row): check if the + // bottom-left block is coded before the current block. + int thisBlockIndex = ((blockRowInSuperblock + 0) << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2 - blockWidthInModeInfoLog2)) + blockColumnInSuperblock + 0; + return Av1BottomRightTopLeftConstants.HasBottomLeft(partition, blockSize, thisBlockIndex); + } + } + + private static bool IntraHasTopRight(Av1BlockSize superblockSize, Av1BlockSize blockSize, int modeInfoRow, int modeInfoColumn, bool haveTop, bool rightAvailable, Av1PartitionType partition, Av1TransformSize transformSize, int blockModeInfoRowOffset, int blockModeInfoColumnOffset, int subX, int subY) + { + if (!haveTop || !rightAvailable) + { + return false; + } + + int blockWideInUnits = blockSize.GetWidth() >> 2; + int planeBlockWidthInUnits = Math.Max(blockWideInUnits >> subX, 1); + int topRightUnitCount = transformSize.Get4x4WideCount(); + + if (blockModeInfoRowOffset > 0) + { // Just need to check if enough pixels on the right. + if (blockSize.GetWidth() > 64) + { + // Special case: For 128x128 blocks, the transform unit whose + // top-right corner is at the center of the block does in fact have + // pixels available at its top-right corner. + if (blockModeInfoRowOffset == 64 >> subY && + blockModeInfoColumnOffset + topRightUnitCount == 64 >> subX) + { + return true; + } + + int planeBlockWidthInUnits64 = 64 >> subX; + int blockModeInfoColumnOffset64 = blockModeInfoColumnOffset % planeBlockWidthInUnits64; + return blockModeInfoColumnOffset64 + topRightUnitCount < planeBlockWidthInUnits64; + } + + return blockModeInfoColumnOffset + topRightUnitCount < planeBlockWidthInUnits; + } + else + { + // All top-right pixels are in the block above, which is already available. + if (blockModeInfoColumnOffset + topRightUnitCount < planeBlockWidthInUnits) + { + return true; + } + + int blockWidthInModeInfoLog2 = blockSize.Get4x4WidthLog2(); + int blockHeightInModeInfeLog2 = blockSize.Get4x4HeightLog2(); + int superBlockModeInfoSize = superblockSize.Get4x4HighCount(); + int blockRowInSuperblock = (modeInfoRow & (superBlockModeInfoSize - 1)) >> blockHeightInModeInfeLog2; + int blockColumnInSuperBlock = (modeInfoColumn & (superBlockModeInfoSize - 1)) >> blockWidthInModeInfoLog2; + + // Top row of superblock: so top-right pixels are in the top and/or + // top-right superblocks, both of which are already available. + if (blockRowInSuperblock == 0) + { + return true; + } + + // Rightmost column of superblock (and not the top row): so top-right pixels + // fall in the right superblock, which is not available yet. + if (((blockColumnInSuperBlock + 1) << blockWidthInModeInfoLog2) >= superBlockModeInfoSize) + { + return false; + } + + // General case (neither top row nor rightmost column): check if the + // top-right block is coded before the current block. + int thisBlockIndex = ((blockRowInSuperblock + 0) << (Av1Constants.MaxSuperBlockSizeLog2 - Av1Constants.ModeInfoSizeLog2 - blockWidthInModeInfoLog2)) + blockColumnInSuperBlock + 0; + return Av1BottomRightTopLeftConstants.HasTopRight(partition, blockSize, thisBlockIndex); + } + } + + private void DecodeBuildIntraPredictors( + Av1PartitionInfo partitionInfo, + ref byte aboveNeighbor, + ref byte leftNeighbor, + nuint referenceStride, + ref byte destination, + nuint destinationStride, + Av1PredictionMode mode, + int angleDelta, + Av1FilterIntraMode filterIntraMode, + Av1TransformSize transformSize, + bool disableEdgeFilter, + int topPixelCount, + int topRightPixelCount, + int leftPixelCount, + int bottomLeftPixelCount, + Av1Plane plane) + { + Span aboveData = stackalloc byte[(Av1Constants.MaxTransformSize * 2) + 32]; + Span leftData = stackalloc byte[(Av1Constants.MaxTransformSize * 2) + 32]; + Span aboveRow = aboveData[16..]; + Span leftColumn = leftData[16..]; + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + bool isDirectionalMode = mode.IsDirectional(); + Av1NeighborNeed need = mode.GetNeighborNeed(); + bool needLeft = (need & Av1NeighborNeed.Left) == Av1NeighborNeed.Left; + bool needAbove = (need & Av1NeighborNeed.Above) == Av1NeighborNeed.Above; + bool needAboveLeft = (need & Av1NeighborNeed.AboveLeft) == Av1NeighborNeed.AboveLeft; + int angle = 0; + bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.FilterIntraModes; + + if (isDirectionalMode) + { + angle = mode.ToAngle() + (angleDelta * Av1Constants.AngleStep); + if (angle <= 90) + { + needAbove = true; + needLeft = false; + needAboveLeft = true; + } + else if (angle < 180) + { + needAbove = true; + needLeft = true; + needAboveLeft = true; + } + else + { + needAbove = false; + needLeft = true; + needAboveLeft = true; + } + } + + if (useFilterIntra) + { + needAbove = true; + needLeft = true; + needAboveLeft = true; + } + + DebugGuard.MustBeGreaterThanOrEqualTo(topPixelCount, 0, nameof(topPixelCount)); + DebugGuard.MustBeGreaterThanOrEqualTo(topRightPixelCount, 0, nameof(topRightPixelCount)); + DebugGuard.MustBeGreaterThanOrEqualTo(leftPixelCount, 0, nameof(leftPixelCount)); + DebugGuard.MustBeGreaterThanOrEqualTo(bottomLeftPixelCount, 0, nameof(bottomLeftPixelCount)); + + if ((!needAbove && leftPixelCount == 0) || (!needLeft && topPixelCount == 0)) + { + byte val; + if (needLeft) + { + val = (byte)((topPixelCount > 0) ? aboveNeighbor : 129); + } + else + { + val = (byte)((leftPixelCount > 0) ? leftNeighbor : 127); + } + + for (int i = 0; i < transformHeight; ++i) + { + Unsafe.InitBlock(ref destination, val, (uint)transformWidth); + destination = ref Unsafe.Add(ref destination, destinationStride); + } + + return; + } + + // NEED_LEFT + if (needLeft) + { + bool needBottom = (need & Av1NeighborNeed.BottomLeft) == Av1NeighborNeed.BottomLeft; + if (useFilterIntra) + { + needBottom = false; + } + + if (isDirectionalMode) + { + needBottom = angle > 180; + } + + uint numLeftPixelsNeeded = (uint)(transformHeight + (needBottom ? transformWidth : 0)); + int i = 0; + if (leftPixelCount > 0) + { + for (; i < leftPixelCount; i++) + { + leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride); + } + + if (needBottom && bottomLeftPixelCount > 0) + { + Guard.IsTrue(i == transformHeight, nameof(i), string.Empty); + for (; i < transformHeight + bottomLeftPixelCount; i++) + { + leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride); + } + } + + if (i < numLeftPixelsNeeded) + { + Unsafe.InitBlock(ref leftColumn[i], leftColumn[i - 1], numLeftPixelsNeeded - (uint)i); + } + } + else + { + if (topPixelCount > 0) + { + Unsafe.InitBlock(ref leftColumn[0], aboveNeighbor, numLeftPixelsNeeded); + } + else + { + Unsafe.InitBlock(ref leftColumn[0], 129, numLeftPixelsNeeded); + } + } + } + + // NEED_ABOVE + if (needAbove) + { + bool needRight = (need & Av1NeighborNeed.AboveRight) == Av1NeighborNeed.AboveRight; + if (useFilterIntra) + { + needRight = false; + } + + if (isDirectionalMode) + { + needRight = angle < 90; + } + + uint numTopPixelsNeeded = (uint)(transformWidth + (needRight ? transformHeight : 0)); + if (topPixelCount > 0) + { + Unsafe.CopyBlock(ref aboveRow[0], ref aboveNeighbor, (uint)topPixelCount); + int i = topPixelCount; + if (needRight && topPixelCount > 0) + { + Guard.IsTrue(topPixelCount == transformWidth, nameof(topPixelCount), string.Empty); + Unsafe.CopyBlock(ref aboveRow[transformWidth], ref Unsafe.Add(ref aboveNeighbor, transformWidth), (uint)topPixelCount); + i += topPixelCount; + } + + if (i < numTopPixelsNeeded) + { + Unsafe.InitBlock(ref aboveRow[i], aboveRow[i - 1], numTopPixelsNeeded - (uint)i); + } + } + else + { + if (leftPixelCount > 0) + { + Unsafe.InitBlock(ref aboveRow[0], leftNeighbor, numTopPixelsNeeded); + } + else + { + Unsafe.InitBlock(ref aboveRow[0], 127, numTopPixelsNeeded); + } + } + } + + if (needAboveLeft) + { + if (topPixelCount > 0 && leftPixelCount > 0) + { + aboveRow[-1] = Unsafe.Subtract(ref aboveNeighbor, 1); + } + else if (topPixelCount > 0) + { + aboveRow[-1] = aboveNeighbor; + } + else if (leftPixelCount > 0) + { + aboveRow[-1] = leftNeighbor; + } + else + { + aboveRow[-1] = 128; + } + + leftColumn[-1] = aboveRow[-1]; + } + + if (useFilterIntra) + { + Av1PredictorFactory.FilterIntraPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode); + return; + } + + if (isDirectionalMode) + { + bool upsampleAbove = false; + bool upsampleLeft = false; + if (!disableEdgeFilter) + { + bool needRight = angle < 90; + bool needBottom = angle > 180; + + bool filterType = GetFilterType(partitionInfo, plane); + + if (angle is not 90 and not 180) + { + int ab_le = needAboveLeft ? 1 : 0; + if (needAbove && needLeft && (transformWidth + transformHeight >= 24)) + { + FilterIntraEdgeCorner(aboveRow, leftColumn); + } + + if (needAbove && topPixelCount > 0) + { + int strength = IntraEdgeFilterStrength(transformWidth, transformHeight, angle - 90, filterType); + int pixelCount = topPixelCount + ab_le + (needRight ? transformHeight : 0); + FilterIntraEdge(ref Unsafe.Subtract(ref aboveRow[0], ab_le), pixelCount, strength); + } + + if (needLeft && leftPixelCount > 0) + { + int strength = IntraEdgeFilterStrength(transformHeight, transformWidth, angle - 180, filterType); + int pixelCount = leftPixelCount + ab_le + (needBottom ? transformWidth : 0); + FilterIntraEdge(ref Unsafe.Subtract(ref leftColumn[0], ab_le), pixelCount, strength); + } + } + + upsampleAbove = UseIntraEdgeUpsample(transformWidth, transformHeight, angle - 90, filterType); + if (needAbove && upsampleAbove) + { + int pixelCount = transformWidth + (needRight ? transformHeight : 0); + + UpsampleIntraEdge(aboveRow, pixelCount); + } + + upsampleLeft = UseIntraEdgeUpsample(transformHeight, transformWidth, angle - 180, filterType); + if (needLeft && upsampleLeft) + { + int pixelCount = transformHeight + (needBottom ? transformWidth : 0); + + UpsampleIntraEdge(leftColumn, pixelCount); + } + } + + Av1PredictorFactory.DirectionalPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle); + return; + } + + // predict + if (mode == Av1PredictionMode.DC) + { + Av1PredictorFactory.DcPredictor(leftPixelCount > 0, topPixelCount > 0, transformSize, ref destination, destinationStride, aboveRow, leftColumn); + } + else + { + Av1PredictorFactory.GeneralPredictor(mode, transformSize, ref destination, destinationStride, aboveRow, leftColumn); + } + } + + private static void UpsampleIntraEdge(Span buffer, int count) + { + // TODO: Consider creating SIMD version + + // interpolate half-sample positions + Guard.MustBeLessThanOrEqualTo(count, MaxUpsampleSize, nameof(count)); + + Span input = stackalloc byte[MaxUpsampleSize + 3]; + byte beforeBuffer = Unsafe.Subtract(ref buffer[0], 1); + + // copy p[-1..(sz-1)] and extend first and last samples + input[0] = beforeBuffer; + input[1] = beforeBuffer; + for (int i = 0; i < count; i++) + { + input[i + 2] = buffer[i]; + } + + input[count + 2] = buffer[count - 1]; + + // interpolate half-sample edge positions + buffer[-2] = input[0]; + for (int i = 0; i < count; i++) + { + int s = -input[i] + (9 * input[i + 1]) + (9 * input[i + 2]) - input[i + 3]; + s = Av1Math.Clamp((s + 8) >> 4, 0, 255); + buffer[(2 * i) - 1] = (byte)s; + buffer[2 * i] = input[i + 2]; + } + } + + private static bool UseIntraEdgeUpsample(int width, int height, int delta, bool type) + { + int d = Math.Abs(delta); + int widthHeight = width + height; + if (d is <= 0 or >= 40) + { + return false; + } + + return type ? (widthHeight <= 8) : (widthHeight <= 16); + } + + private static void FilterIntraEdge(ref byte buffer, int count, int strength) + { + // TODO: Consider creating SIMD version + if (strength == 0) + { + return; + } + + int[][] kernel = [ + [0, 4, 8, 4, 0], [0, 5, 6, 5, 0], [2, 4, 4, 4, 2] + ]; + int filt = strength - 1; + Span edge = stackalloc byte[129]; + + Unsafe.CopyBlock(ref edge[0], ref buffer, (uint)count); + for (int i = 1; i < count; i++) + { + int s = 0; + for (int j = 0; j < 5; j++) + { + int k = i - 2 + j; + k = (k < 0) ? 0 : k; + k = (k > count - 1) ? count - 1 : k; + s += edge[k] * kernel[filt][j]; + } + + s = (s + 8) >> 4; + Unsafe.Add(ref buffer, i) = (byte)s; + } + } + + private static int IntraEdgeFilterStrength(int width, int height, int delta, bool filterType) + { + int d = Math.Abs(delta); + int strength = 0; + int widthHeight = width + height; + if (!filterType) + { + if (widthHeight <= 8) + { + if (d >= 56) + { + strength = 1; + } + } + else if (widthHeight <= 12) + { + if (d >= 40) + { + strength = 1; + } + } + else if (widthHeight <= 16) + { + if (d >= 40) + { + strength = 1; + } + } + else if (widthHeight <= 24) + { + if (d >= 8) + { + strength = 1; + } + + if (d >= 16) + { + strength = 2; + } + + if (d >= 32) + { + strength = 3; + } + } + else if (widthHeight <= 32) + { + if (d >= 1) + { + strength = 1; + } + + if (d >= 4) + { + strength = 2; + } + + if (d >= 32) + { + strength = 3; + } + } + else + { + if (d >= 1) + { + strength = 3; + } + } + } + else + { + if (widthHeight <= 8) + { + if (d >= 40) + { + strength = 1; + } + + if (d >= 64) + { + strength = 2; + } + } + else if (widthHeight <= 16) + { + if (d >= 20) + { + strength = 1; + } + + if (d >= 48) + { + strength = 2; + } + } + else if (widthHeight <= 24) + { + if (d >= 4) + { + strength = 3; + } + } + else + { + if (d >= 1) + { + strength = 3; + } + } + } + + return strength; + } + + private static void FilterIntraEdgeCorner(Span above, Span left) + { + int[] kernel = [5, 6, 5]; + + ref byte aboveRef = ref above[0]; + ref byte leftRef = ref left[0]; + ref byte abovePreviousRef = ref Unsafe.Subtract(ref aboveRef, 1); + ref byte leftPreviousRef = ref Unsafe.Subtract(ref leftRef, 1); + int s = (leftRef * kernel[0]) + (abovePreviousRef * kernel[1]) + (aboveRef * kernel[2]); + s = (s + 8) >> 4; + abovePreviousRef = (byte)s; + leftPreviousRef = (byte)s; + } + + private static bool GetFilterType(Av1PartitionInfo partitionInfo, Av1Plane plane) + { + Av1BlockModeInfo? above; + Av1BlockModeInfo? left; + if (plane == Av1Plane.Y) + { + above = partitionInfo.AboveModeInfo; + left = partitionInfo.LeftModeInfo; + } + else + { + above = partitionInfo.AboveModeInfoForChroma; + left = partitionInfo.LeftModeInfoForChroma; + } + + bool aboveIsSmooth = (above != null) && IsSmooth(above, plane); + bool leftIsSmooth = (left != null) && IsSmooth(left, plane); + return aboveIsSmooth || leftIsSmooth; + } + + private static bool IsSmooth(Av1BlockModeInfo modeInfo, Av1Plane plane) + { + if (plane == Av1Plane.Y) + { + Av1PredictionMode mode = modeInfo.YMode; + return mode is Av1PredictionMode.Smooth or + Av1PredictionMode.SmoothVertical or + Av1PredictionMode.SmoothHorizontal; + } + else + { + // Inter mode not supported here. + Av1PredictionMode uvMode = modeInfo.UvMode; + return uvMode is Av1PredictionMode.Smooth or + Av1PredictionMode.SmoothVertical or + Av1PredictionMode.SmoothHorizontal; + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index dbbbf4f50d..3f04731506 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -62,6 +62,8 @@ public Av1BlockModeInfo(int numPlanes, Av1BlockSize blockSize, Point positionInS public int[] TransformUnitsCount { get; internal set; } + public int GetPaletteSize(Av1Plane plane) => this.paletteSize[Math.Min(1, (int)plane)]; + public int GetPaletteSize(Av1PlaneType planeType) => this.paletteSize[(int)planeType]; public void SetPaletteSizes(int ySize, int uvSize) => this.paletteSize = [ySize, uvSize]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs index f00132db33..eaf33a3d8c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs @@ -9,5 +9,6 @@ internal enum Av1FilterIntraMode Vertical, Horizontal, Directional157, - Paeth + Paeth, + FilterIntraModes, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs index 1c706c34e0..30d506f0cc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PartitionInfo.cs @@ -2,16 +2,12 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1PartitionInfo { - private int modeBlockToLeftEdge; - private int modeBlockToRightEdge; - private int modeBlockToTopEdge; - private int modeBlockToBottomEdge; - public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockInfo, bool isChroma, Av1PartitionType partitionType) { this.ModeInfo = modeInfo; @@ -20,6 +16,8 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI this.Type = partitionType; this.CdefStrength = []; this.ReferenceFrame = [-1, -1]; + this.WidthInPixels = new int[3]; + this.HeightInPixels = new int[3]; } public Av1BlockModeInfo ModeInfo { get; } @@ -67,37 +65,70 @@ public Av1PartitionInfo(Av1BlockModeInfo modeInfo, Av1SuperblockInfo superblockI public Av1BlockModeInfo? LeftModeInfo { get; set; } + public Av1BlockModeInfo? AboveModeInfoForChroma { get; set; } + + public Av1BlockModeInfo? LeftModeInfoForChroma { get; set; } + public int[][] CdefStrength { get; set; } public int[] ReferenceFrame { get; set; } - public int ModeBlockToRightEdge => this.modeBlockToRightEdge; + public int ModeBlockToLeftEdge { get; private set; } + + public int ModeBlockToRightEdge { get; private set; } + + public int ModeBlockToTopEdge { get; private set; } - public int ModeBlockToBottomEdge => this.modeBlockToBottomEdge; + public int ModeBlockToBottomEdge { get; private set; } - public void ComputeBoundaryOffsets(ObuFrameHeader frameHeader, Av1TileInfo tileInfo) + public int[] WidthInPixels { get; private set; } + + public int[] HeightInPixels { get; private set; } + + public Av1ChromaFromLumaContext? ChromaFromLumaContext { get; internal set; } + + public void ComputeBoundaryOffsets(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1TileInfo tileInfo) { Av1BlockSize blockSize = this.ModeInfo.BlockSize; int bw4 = blockSize.Get4x4WideCount(); int bh4 = blockSize.Get4x4HighCount(); + int subX = sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; + int subY = sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; this.AvailableAbove = this.RowIndex > tileInfo.ModeInfoRowStart; this.AvailableLeft = this.ColumnIndex > tileInfo.ModeInfoColumnStart; this.AvailableAboveForChroma = this.AvailableAbove; this.AvailableLeftForChroma = this.AvailableLeft; + int shift = Av1Constants.ModeInfoSizeLog2 + 3; - this.modeBlockToLeftEdge = -this.ColumnIndex << shift; - this.modeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift; - this.modeBlockToTopEdge = -this.RowIndex << shift; - this.modeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift; + this.ModeBlockToLeftEdge = -this.ColumnIndex << shift; + this.ModeBlockToRightEdge = (frameHeader.ModeInfoColumnCount - bw4 - this.ColumnIndex) << shift; + this.ModeBlockToTopEdge = -this.RowIndex << shift; + this.ModeBlockToBottomEdge = (frameHeader.ModeInfoRowCount - bh4 - this.RowIndex) << shift; + + // Block Size width & height in pixels. + // For Luma bock + const int modeInfoSize = 1 << Av1Constants.ModeInfoSizeLog2; + this.WidthInPixels[0] = bw4 * modeInfoSize; + this.HeightInPixels[0] = bh4 * modeInfoSize; + + // For U plane chroma bock + this.WidthInPixels[1] = Math.Max(1, bw4 >> subX) * modeInfoSize; + this.HeightInPixels[1] = Math.Max(1, bh4 >> subY) * modeInfoSize; + + // For V plane chroma bock + this.WidthInPixels[2] = Math.Max(1, bw4 >> subX) * modeInfoSize; + this.HeightInPixels[2] = Math.Max(1, bh4 >> subY) * modeInfoSize; + + this.ChromaFromLumaContext = new Av1ChromaFromLumaContext(configuration, sequenceHeader.ColorConfig); } public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) { int maxBlockWide = blockSize.GetWidth(); - if (this.modeBlockToRightEdge < 0) + if (this.ModeBlockToRightEdge < 0) { int shift = subX ? 4 : 3; - maxBlockWide += this.modeBlockToRightEdge >> shift; + maxBlockWide += this.ModeBlockToRightEdge >> shift; } return maxBlockWide >> 2; @@ -106,10 +137,10 @@ public int GetMaxBlockWide(Av1BlockSize blockSize, bool subX) public int GetMaxBlockHigh(Av1BlockSize blockSize, bool subY) { int maxBlockHigh = blockSize.GetHeight(); - if (this.modeBlockToBottomEdge < 0) + if (this.ModeBlockToBottomEdge < 0) { int shift = subY ? 4 : 3; - maxBlockHigh += this.modeBlockToBottomEdge >> shift; + maxBlockHigh += this.ModeBlockToBottomEdge >> shift; } return maxBlockHigh >> 2; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index ae3f4ddc47..48f45eceeb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -34,10 +34,12 @@ internal class Av1TileReader : IAv1TileReader private readonly int[][] transformUnitCount; private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; + private readonly Configuration configuration; - public Av1TileReader(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) + public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) { this.FrameHeader = frameHeader; + this.configuration = configuration; this.SequenceHeader = sequenceHeader; // init_main_frame_ctxt @@ -284,6 +286,8 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 int block4x4Width = blockSize.Get4x4WideCount(); int block4x4Height = blockSize.Get4x4HighCount(); int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; + int subX = this.SequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; + int subY = this.SequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; Point superblockLocation = superblockInfo.Position * this.SequenceHeader.SuperblockModeInfoSize; Point locationInSuperblock = new Point(modeInfoLocation.X - superblockLocation.X, modeInfoLocation.Y - superblockLocation.Y); Av1BlockModeInfo blockModeInfo = new(planesCount, blockSize, locationInSuperblock); @@ -295,7 +299,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 partitionInfo.ColumnIndex = columnIndex; partitionInfo.RowIndex = rowIndex; superblockInfo.BlockCount++; - partitionInfo.ComputeBoundaryOffsets(this.FrameHeader, tileInfo); + partitionInfo.ComputeBoundaryOffsets(this.configuration, this.SequenceHeader, this.FrameHeader, tileInfo); if (hasChroma) { if (this.SequenceHeader.ColorConfig.SubSamplingY && block4x4Height == 1) @@ -319,6 +323,16 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 partitionInfo.LeftModeInfo = superblockInfo.GetModeInfo(new Point(rowIndex, columnIndex - 1)); } + if (partitionInfo.AvailableAboveForChroma) + { + partitionInfo.AboveModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex & ~subY, columnIndex | subX)); + } + + if (partitionInfo.AvailableLeftForChroma) + { + partitionInfo.LeftModeInfoForChroma = superblockInfo.GetModeInfo(new Point(rowIndex | subY, columnIndex & ~subX)); + } + this.ReadModeInfo(ref reader, partitionInfo); ReadPaletteTokens(ref reader, partitionInfo); this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 1c76aa7ec0..e1ad345605 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -24,7 +24,7 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til IAv1TileReader stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); obuReader.ReadAll(ref bitStreamReader, dataSize, stub); - Av1TileReader tileReader = new(obuReader.SequenceHeader, obuReader.FrameHeader); + Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader); // Act tileReader.ReadTile(tileSpan, 0); From aa1b2992b242f4b755ba1d4423cf6d1cb286fc30 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 24 Aug 2024 22:43:41 +0200 Subject: [PATCH 158/190] Implement Block Decoder --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 5 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 2 +- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 19 ++- .../Quantification/Av1InverseQuantizer.cs | 6 +- ...tionLookup.cs => Av1QuantizationLookup.cs} | 2 +- .../Heif/Av1/Prediction/Av1DcFillPredictor.cs | 10 ++ .../Heif/Av1/Prediction/Av1DcLeftPredictor.cs | 10 ++ .../Heif/Av1/Prediction/Av1DcPredictor.cs | 10 ++ .../Heif/Av1/Prediction/Av1DcTopPredictor.cs | 10 ++ ...tionDecoder.cs => Av1PredictionDecoder.cs} | 48 +++---- .../Av1/Prediction/Av1PredictorFactory.cs | 26 +++- .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 8 ++ .../Heif/Av1/Transform/Av1BlockDecoder.cs | 134 +++++++++++++++--- ...ransform.cs => Av1InverseTransformMath.cs} | 2 +- .../Av1/Transform/Av1InverseTransformer.cs | 15 ++ .../Formats/Heif/Av1/ObuPrettyPrint.cs | 10 +- 16 files changed, 244 insertions(+), 73 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/{QuantizationLookup.cs => Av1QuantizationLookup.cs} (99%) rename src/ImageSharp/Formats/Heif/Av1/Prediction/{PredictionDecoder.cs => Av1PredictionDecoder.cs} (96%) rename src/ImageSharp/Formats/Heif/Av1/Transform/{Av1InverseTransform.cs => Av1InverseTransformMath.cs} (99%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 6f99ada289..e6ed9ab0fd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -26,6 +26,8 @@ public Av1Decoder(Configuration configuration) public Av1FrameInfo? FrameInfo { get; private set; } + public Av1FrameBuffer? FrameBuffer { get; private set; } + public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); @@ -35,7 +37,8 @@ public void Decode(Span buffer) Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); this.FrameInfo = this.tileReader.FrameInfo; - this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo); + this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false); + this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer); this.frameDecoder.DecodeFrame(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index 22eea140c8..b4054bfcf4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -1092,7 +1092,7 @@ private void ReadUncompressedFrameHeader(ref Av1BitStreamReader reader) frameHeader.SegmentationParameters.QMLevel[2] = new int[Av1Constants.MaxSegmentCount]; for (int segmentId = 0; segmentId < Av1Constants.MaxSegmentCount; segmentId++) { - int qIndex = QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex); + int qIndex = Av1QuantizationLookup.GetQIndex(frameHeader.SegmentationParameters, segmentId, frameHeader.QuantizationParameters.BaseQIndex); frameHeader.QuantizationParameters.QIndex[segmentId] = qIndex; frameHeader.LosslessArray[segmentId] = qIndex == 0 && frameHeader.QuantizationParameters.DeltaQDc[(int)Av1Plane.Y] == 0 && diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index e8211a3534..365cd46a54 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -13,16 +13,20 @@ internal class Av1FrameDecoder private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; private readonly Av1FrameInfo frameInfo; + private readonly Av1FrameBuffer frameBuffer; private readonly Av1InverseQuantizer inverseQuantizer; private readonly Av1DeQuantizationContext deQuants; + private readonly Av1BlockDecoder blockDecoder; - public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo) + public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; this.frameInfo = frameInfo; + this.frameBuffer = frameBuffer; this.inverseQuantizer = new(sequenceHeader, frameHeader); this.deQuants = new(); + this.blockDecoder = new(this.sequenceHeader, this.frameHeader, this.frameInfo, this.frameBuffer); } public void DecodeFrame() @@ -86,18 +90,19 @@ private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int sup Av1SuperblockInfo superblockInfo = this.frameInfo.GetSuperblock(new Point(superblockColumn, superblockRow)); - Point modeInfoPosition = new Point(modeInfoColumn, modeInfoRow); - this.DecodeSuperblock(modeInfoPosition, superblockInfo); + Point modeInfoPosition = new(modeInfoColumn, modeInfoRow); + this.DecodeSuperblock(modeInfoPosition, superblockInfo, new Av1TileInfo(tileRow, tileColumn, this.frameHeader)); } } - private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo) + private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { + this.blockDecoder.UpdateSuperblock(superblockInfo); this.inverseQuantizer.UpdateDequant(this.deQuants, superblockInfo); - DecodePartition(modeInfoPosition, superblockInfo); + this.DecodePartition(modeInfoPosition, superblockInfo, tileInfo); } - private static void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo) + private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition); @@ -107,7 +112,7 @@ private static void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo su Av1BlockSize subSize = modeInfo.BlockSize; Point globalPosition = new(modeInfoPosition.X, modeInfoPosition.Y); globalPosition.Offset(subPosition); - Av1BlockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo); + this.blockDecoder.DecodeBlock(modeInfo, globalPosition, subSize, superblockInfo, tileInfo); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs index 80904460d7..e06e9ba0f6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -64,15 +64,15 @@ public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo s { for (int i = 0; i < Av1Constants.MaxSegmentCount; i++) { - int currentQIndex = QuantizationLookup.GetQIndex(this.frameHeader.SegmentationParameters, i, superblockInfo.SuperblockDeltaQ); + int currentQIndex = Av1QuantizationLookup.GetQIndex(this.frameHeader.SegmentationParameters, i, superblockInfo.SuperblockDeltaQ); for (Av1Plane plane = 0; (int)plane < Av1Constants.MaxPlanes; plane++) { int dcDeltaQ = this.frameHeader.QuantizationParameters.DeltaQDc[(int)plane]; int acDeltaQ = this.frameHeader.QuantizationParameters.DeltaQAc[(int)plane]; - this.deQuantsDeltaQ.SetDc(i, plane, QuantizationLookup.GetDcQuant(currentQIndex, dcDeltaQ, bitDepth)); - this.deQuantsDeltaQ.SetAc(i, plane, QuantizationLookup.GetAcQuant(currentQIndex, acDeltaQ, bitDepth)); + this.deQuantsDeltaQ.SetDc(i, plane, Av1QuantizationLookup.GetDcQuant(currentQIndex, dcDeltaQ, bitDepth)); + this.deQuantsDeltaQ.SetAc(i, plane, Av1QuantizationLookup.GetAcQuant(currentQIndex, acDeltaQ, bitDepth)); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs similarity index 99% rename from src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs rename to src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs index e6ca2f400e..205f1f3a19 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/QuantizationLookup.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1QuantizationLookup.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; -internal class QuantizationLookup +internal class Av1QuantizationLookup { // Coefficient scaling and quantization with AV1 TX are tailored to // the AV1 TX transforms. Regardless of the bit-depth of the input, diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs index d0bc59f1c0..719d574b87 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -16,6 +17,15 @@ public Av1DcFillPredictor(Size blockSize) this.blockHeight = (uint)blockSize.Height; } + public Av1DcFillPredictor(Av1TransformSize transformSize) + { + this.blockWidth = (uint)transformSize.GetWidth(); + this.blockHeight = (uint)transformSize.GetHeight(); + } + + public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) + => new Av1DcFillPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) { const byte expectedDc = 0x80; diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs index 4aea10bdf4..1983396f17 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -16,6 +17,15 @@ public Av1DcLeftPredictor(Size blockSize) this.blockHeight = (uint)blockSize.Height; } + public Av1DcLeftPredictor(Av1TransformSize transformSize) + { + this.blockWidth = (uint)transformSize.GetWidth(); + this.blockHeight = (uint)transformSize.GetHeight(); + } + + public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) + => new Av1DcLeftPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) { int sum = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs index f3a3cf4ae6..51eef4b783 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -16,6 +17,15 @@ public Av1DcPredictor(Size blockSize) this.blockHeight = (uint)blockSize.Height; } + public Av1DcPredictor(Av1TransformSize transformSize) + { + this.blockWidth = (uint)transformSize.GetWidth(); + this.blockHeight = (uint)transformSize.GetHeight(); + } + + public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) + => new Av1DcPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) { int sum = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs index a79a473938..d429b64549 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -16,6 +17,15 @@ public Av1DcTopPredictor(Size blockSize) this.blockHeight = (uint)blockSize.Height; } + public Av1DcTopPredictor(Av1TransformSize transformSize) + { + this.blockWidth = (uint)transformSize.GetWidth(); + this.blockHeight = (uint)transformSize.GetHeight(); + } + + public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) + => new Av1DcTopPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) { int sum = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs similarity index 96% rename from src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs index 0e7f9fb216..f9ceab2b83 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/PredictionDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; -internal class PredictionDecoder +internal class Av1PredictionDecoder { private const int MaxUpsampleSize = 16; @@ -18,49 +18,29 @@ internal class PredictionDecoder private readonly ObuFrameHeader frameHeader; private readonly bool is16BitPipeline; - public PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool is16BitPipeline) + public Av1PredictionDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, bool is16BitPipeline) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; this.is16BitPipeline = is16BitPipeline; } - public void DecodeFrame( + public void Decode( Av1PartitionInfo partitionInfo, Av1Plane plane, Av1TransformSize transformSize, Av1TileInfo tileInfo, - Av1FrameBuffer frameBuffer, + Span pixelBuffer, + int pixelStride, Av1BitDepth bitDepth, int blockModeInfoColumnOffset, int blockModeInfoRowOffset) { - Buffer2D? pixelBuffer = null; - switch (plane) - { - case Av1Plane.Y: - pixelBuffer = frameBuffer.BufferY; - break; - case Av1Plane.U: - pixelBuffer = frameBuffer.BufferCb; - break; - case Av1Plane.V: - pixelBuffer = frameBuffer.BufferCr; - break; - default: - break; - } - - if (pixelBuffer == null) - { - return; - } - int bytesPerPixel = (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline) ? 2 : 1; - ref byte pixelRef = ref pixelBuffer[frameBuffer.StartPosition.X, frameBuffer.StartPosition.Y]; + ref byte pixelRef = ref pixelBuffer[0]; ref byte topNeighbor = ref pixelRef; ref byte leftNeighbor = ref pixelRef; - int stride = frameBuffer.BufferY!.Width * bytesPerPixel; + int stride = pixelStride * bytesPerPixel; topNeighbor = Unsafe.Subtract(ref topNeighbor, stride); leftNeighbor = Unsafe.Subtract(ref leftNeighbor, 1); @@ -111,7 +91,7 @@ public void DecodeFrame( bitDepth); } - private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1ChromaFromLumaContext? chromaFromLumaContext, ref Buffer2D pixelBuffer, int stride, Av1TransformSize transformSize, Av1Plane plane) + private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1ChromaFromLumaContext? chromaFromLumaContext, ref Span pixelBuffer, int stride, Av1TransformSize transformSize, Av1Plane plane) { Av1BlockModeInfo modeInfo = partitionInfo.ModeInfo; bool isChromaFromLumaAllowedFlag = IsChromaFromLumaAllowedWithFrameHeader(partitionInfo, this.sequenceHeader.ColorConfig, this.frameHeader); @@ -148,7 +128,7 @@ private void PredictChromaFromLumaBlock(Av1PartitionInfo partitionInfo, Av1Chrom } ChromaFromLumaPredict( - chromaFromLumaContext.Q3Buffer!, + chromaFromLumaContext.Q3Buffer!.DangerousGetSingleSpan(), pixelBuffer, stride, pixelBuffer, @@ -196,7 +176,7 @@ private static int GetScaledLumaQ0(int alphaQ3, short predictedQ3) return Av1Math.RoundPowerOf2Signed(scaledLumaQ6, 6); } - private static void ChromaFromLumaPredict(Buffer2D predictedBufferQ3, Buffer2D predictedBuffer, int predictedStride, Buffer2D destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height) + private static void ChromaFromLumaPredict(Span predictedBufferQ3, Span predictedBuffer, int predictedStride, Span destinationBuffer, int destinationStride, int alphaQ3, Av1BitDepth bitDepth, int width, int height) { // TODO: Make SIMD variant of this method. int maxPixelValue = (1 << bitDepth.GetBitCount()) - 1; @@ -204,9 +184,13 @@ private static void ChromaFromLumaPredict(Buffer2D predictedBufferQ3, Buf { for (int i = 0; i < width; i++) { - int alphaQ0 = GetScaledLumaQ0(alphaQ3, predictedBufferQ3[i, j]); - destinationBuffer[i, j] = (byte)Av1Math.Clamp(alphaQ0 + predictedBuffer[i, j], 0, maxPixelValue); + int alphaQ0 = GetScaledLumaQ0(alphaQ3, predictedBufferQ3[i]); + destinationBuffer[i] = (byte)Av1Math.Clamp(alphaQ0 + predictedBuffer[i], 0, maxPixelValue); } + + destinationBuffer = destinationBuffer[width..]; + predictedBuffer = predictedBuffer[width..]; + predictedBufferQ3 = predictedBufferQ3[width..]; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs index 84a0d75d88..12349cc010 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs @@ -8,7 +8,31 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; internal class Av1PredictorFactory { - internal static void DcPredictor(bool v1, bool v2, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException(); + internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) + { + if (hasLeft) + { + if (hasAbove) + { + Av1DcPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + } + else + { + Av1DcLeftPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + } + } + else + { + if (hasAbove) + { + Av1DcTopPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + } + else + { + Av1DcFillPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + } + } + } internal static void DirectionalPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index f2484c264b..05ac285fdb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -43,4 +43,12 @@ public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position) public ref Av1TransformInfo GetTransformInfoUv() => ref this.frameInfo.GetSuperblockTransformUv(this.Position); public Av1BlockModeInfo GetModeInfo(Point index) => this.frameInfo.GetModeInfo(this.Position, index); + + public Span GetCoefficients(Av1Plane plane) => plane switch + { + Av1Plane.Y => this.CoefficientsY, + Av1Plane.U => this.CoefficientsU, + Av1Plane.V => this.CoefficientsV, + _ => [] + }; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index 80a3502751..9e76b20f4f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -3,6 +3,8 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantification; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -17,20 +19,41 @@ internal class Av1BlockDecoder private readonly Av1FrameBuffer frameBuffer; + private readonly bool isLoopFilterEnabled; + + private readonly int[] currentCoefficientIndex; + public Av1BlockDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; this.frameInfo = frameInfo; this.frameBuffer = frameBuffer; + int ySize = (1 << this.sequenceHeader.SuperblockSizeLog2) * (1 << this.sequenceHeader.SuperblockSizeLog2); + int inverseQuantizationSize = ySize + + (this.sequenceHeader.ColorConfig.SubSamplingX ? ySize >> 2 : ySize) + + (this.sequenceHeader.ColorConfig.SubSamplingY ? ySize >> 2 : ySize); + this.CurrentInverseQuantizationCoefficients = new int[inverseQuantizationSize]; + this.isLoopFilterEnabled = false; + this.currentCoefficientIndex = new int[3]; + } + + public int[] CurrentInverseQuantizationCoefficients { get; private set; } + + public void UpdateSuperblock(Av1SuperblockInfo superblockInfo) + { + this.currentCoefficientIndex[0] = 0; + this.currentCoefficientIndex[1] = 0; + this.currentCoefficientIndex[2] = 0; } - public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo) + /// + /// SVT: svt_aom_decode_block + /// + public void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1BlockSize blockSize, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { - /* ObuColorConfig colorConfig = this.sequenceHeader.ColorConfig; Av1TransformType transformType; - Span coefficients; Av1TransformSize transformSize; int transformUnitCount; bool hasChroma = Av1TileReader.HasChroma(this.sequenceHeader, modeInfoPosition, blockSize); @@ -47,6 +70,8 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition bool highBitDepth = false; bool is16BitsPipeline = false; int loopFilterStride = this.frameHeader.ModeInfoStride; + Av1PredictionDecoder predictionDecoder = new(this.sequenceHeader, this.frameHeader, false); + Av1InverseQuantizer inverseQuantizer = new(this.sequenceHeader, this.frameHeader); for (int plane = 0; plane < colorConfig.PlaneCount; plane++) { @@ -79,8 +104,9 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition Guard.IsFalse(transformUnitCount == 0, nameof(transformUnitCount), "Must have at least a single transform unit to decode."); - this.DeriveBlockPointers( - this.reconstructionFrameBuffer, + // SVT: svt_aom_derive_blk_pointers + DeriveBlockPointers( + this.frameBuffer, plane, (modeInfoPosition.X >> subX) << Av1Constants.ModeInfoSizeLog2, (modeInfoPosition.Y >> subY) << Av1Constants.ModeInfoSizeLog2, @@ -95,15 +121,17 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition int transformBlockOffset; transformSize = transformInfo.Size; - coefficients = this.currentCoefficients[plane]; + Span coefficients = superblockInfo.GetCoefficients((Av1Plane)plane)[this.currentCoefficientIndex[plane]..]; transformBlockOffset = ((transformInfo.OffsetY * reconstructionStride) + transformInfo.OffsetX) << Av1Constants.ModeInfoSizeLog2; transformBlockReconstructionBuffer = blockReconstructionBuffer.Slice(transformBlockOffset << (highBitDepth ? 1 : 0)); if (this.isLoopFilterEnabled) { + /* if (plane != 2) { + // SVT: svt_aom_fill_4x4_lf_param Fill4x4LoopFilterParameters( this.loopFilterContext, (modeInfoPosition.X & (~subX)) + (transformInfo.OffsetX << subX), @@ -113,20 +141,21 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition subX, subY, plane); - } + }*/ } // if (!inter_block) if (true) { - PredictIntra( + // SVT: svt_av1_predict_intra + predictionDecoder.Decode( partitionInfo, - plane, + (Av1Plane)plane, transformSize, - tile, + tileInfo, transformBlockReconstructionBuffer, reconstructionStride, - this.reconstructionFrameBuffer.BitDepth, + this.frameBuffer.BitDepth, transformInfo.OffsetX, transformInfo.OffsetY); } @@ -138,22 +167,23 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition Span quantizationCoefficients = this.CurrentInverseQuantizationCoefficients; int inverseQuantizationSize = transformSize.GetWidth() * transformSize.GetHeight(); quantizationCoefficients[..inverseQuantizationSize].Clear(); - this.CurrentInverseQuantizationCoefficients = quantizationCoefficients[inverseQuantizationSize..]; transformType = transformInfo.Type; - numberOfCoefficients = InverseQuantize( - partitionInfo, modeInfo, coefficients, quantizationCoefficients, transformType, transformSize, plane); + // SVT: svt_aom_inverse_quantize + numberOfCoefficients = inverseQuantizer.InverseQuantize( + modeInfo, coefficients, quantizationCoefficients, transformType, transformSize, (Av1Plane)plane); if (numberOfCoefficients != 0) { - this.CurrentCoefficients[plane] += numberOfCoefficients + 1; + this.currentCoefficientIndex[plane] += numberOfCoefficients + 1; - if (this.reconstructionFrameBuffer.BitDepth == Av1BitDepth.EightBit && !is16BitsPipeline) + if (this.frameBuffer.BitDepth == Av1BitDepth.EightBit && !is16BitsPipeline) { - InverseTransformReconstruction8Bit( + // SVT: svt_aom_inv_transform_recon8bit + Av1InverseTransformer.Reconstruct8Bit( quantizationCoefficients, - (Span)transformBlockReconstructionBuffer, + transformBlockReconstructionBuffer, reconstructionStride, - (Span)transformBlockReconstructionBuffer, + transformBlockReconstructionBuffer, reconstructionStride, transformSize, transformType, @@ -169,8 +199,10 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition } // Store Luma for CFL if required! - if (plane == (int)Av1Plane.Y && StoreChromeFromLumeRequired(colorConfig, partitionInfo, this.frameHeader.IsChroma)) + if (plane == (int)Av1Plane.Y && StoreChromeFromLumeRequired(colorConfig, partitionInfo, hasChroma)) { + /* + // SVT: svt_cfl_store_tx ChromaFromLumaStoreTransform( partitionInfo, this.chromaFromLumaContext, @@ -182,11 +214,71 @@ public static void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition transformBlockReconstructionBuffer, reconstructionStride, is16BitsPipeline); + */ } // increment transform pointer transformInfo = ref Unsafe.Add(ref transformInfo, 1); } - }*/ + } + } + + private static void DeriveBlockPointers(Av1FrameBuffer frameBuffer, int plane, int blockColumnInPixels, int blockRowInPixels, out Span blockReconstructionBuffer, out int reconstructionStride, int subX, int subY) + { + int blockOffset; + + if (plane == 0) + { + blockOffset = ((frameBuffer.OriginY + blockRowInPixels) * frameBuffer.BufferY!.Width) + + (frameBuffer.OriginX + blockColumnInPixels); + reconstructionStride = frameBuffer.BufferY!.Width; + } + else if (plane == 1) + { + blockOffset = (((frameBuffer.OriginY >> subY) + blockRowInPixels) * frameBuffer.BufferCb!.Width) + + ((frameBuffer.OriginX >> subX) + blockColumnInPixels); + reconstructionStride = frameBuffer.BufferCb!.Width; + } + else + { + blockOffset = (((frameBuffer.OriginY >> subY) + blockRowInPixels) * frameBuffer.BufferCr!.Width) + + ((frameBuffer.OriginX >> subX) + blockColumnInPixels); + reconstructionStride = frameBuffer.BufferCr!.Width; + } + + if (frameBuffer.BitDepth != Av1BitDepth.EightBit || frameBuffer.Is16BitPipeline) + { + // 16bit pipeline + blockOffset *= 2; + if (plane == 0) + { + blockReconstructionBuffer = frameBuffer.BufferY!.DangerousGetSingleSpan()[blockOffset..]; + } + else if (plane == 1) + { + blockReconstructionBuffer = frameBuffer.BufferCb!.DangerousGetSingleSpan()[blockOffset..]; + } + else + { + blockReconstructionBuffer = frameBuffer.BufferCr!.DangerousGetSingleSpan()[blockOffset..]; + } + } + else + { + if (plane == 0) + { + blockReconstructionBuffer = frameBuffer.BufferY!.DangerousGetSingleSpan()[blockOffset..]; + } + else if (plane == 1) + { + blockReconstructionBuffer = frameBuffer.BufferCb!.DangerousGetSingleSpan()[blockOffset..]; + } + else + { + blockReconstructionBuffer = frameBuffer.BufferCr!.DangerousGetSingleSpan()[blockOffset..]; + } + } } + + private static bool StoreChromeFromLumeRequired(ObuColorConfig colorConfig, Av1PartitionInfo partitionInfo, bool hasChroma) => false; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs similarity index 99% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs index 4856c47194..52ee9c26c6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransform.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; -internal static class Av1InverseTransform +internal static class Av1InverseTransformMath { public static readonly int[,] AcQLookup = new int[3, 256] { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs new file mode 100644 index 0000000000..1f8855602e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1InverseTransformer +{ + /// + /// SVT: svt_aom_inv_transform_recon8bit + /// + public static void Reconstruct8Bit(Span coefficientsBuffer, Span transformBlockReconstructionBuffer1, int reconstructionStride1, Span transformBlockReconstructionBuffer2, int reconstructionStride2, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) + { + throw new NotImplementedException("Inverse transformation not implemented yet."); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs index 7730383146..13ad34f2af 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuPrettyPrint.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; internal class ObuPrettyPrint { - private static readonly char[] spaces = " ".ToCharArray(); + private static readonly char[] Spaces = " ".ToCharArray(); public static string PrettyPrintProperties(object obj, int indent = 0) { @@ -20,7 +20,7 @@ public static string PrettyPrintProperties(object obj, int indent = 0) MemberInfo[] properties = obj.GetType().FindMembers(MemberTypes.Property, BindingFlags.Instance | BindingFlags.Public, null, null); foreach (MemberInfo member in properties) { - builder.Append(spaces, 0, indent); + builder.Append(Spaces, 0, indent); if (member is PropertyInfo property) { builder.Append(property.Name); @@ -31,7 +31,7 @@ public static string PrettyPrintProperties(object obj, int indent = 0) } indent -= 2; - builder.Append(spaces, 0, indent); + builder.Append(Spaces, 0, indent); builder.AppendLine("}"); return builder.ToString(); } @@ -46,7 +46,7 @@ private static void PrettyPrintValue(StringBuilder builder, object value, int in { builder.AppendLine("["); indent += 2; - builder.Append(spaces, 0, indent); + builder.Append(Spaces, 0, indent); Type elementType = value.GetType().GetElementType(); IList list = value as IList; foreach (object item in list) @@ -55,7 +55,7 @@ private static void PrettyPrintValue(StringBuilder builder, object value, int in } indent -= 2; - builder.Append(spaces, 0, indent); + builder.Append(Spaces, 0, indent); builder.AppendLine("]"); } else if (value.GetType().IsClass) From ad57a99ff19294e44fe3af3617657beefb8adedb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 5 Sep 2024 20:47:41 +0200 Subject: [PATCH 159/190] Skeleton code for Transform --- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 8 + .../Heif/Av1/Transform/Av1CoefficientShape.cs | 12 + .../Transform/Av1DctDctInverseTransformer.cs | 157 +++++++++ .../Av1/Transform/Av1ForwardTransformer.cs | 276 +++++++++++++++ .../Av1/Transform/Av1InverseTransformer.cs | 41 ++- .../Heif/Av1/Transform/Av1SinusConstants.cs | 75 +++++ .../Av1Transform2dFlipConfiguration.cs | 313 ++++++++++++++++++ .../Av1TransformFunctionParameters.cs | 19 ++ .../Av1/Transform/Av1TransformFunctionType.cs | 23 ++ .../Heif/Av1/Transform/Av1TransformType1d.cs | 12 + .../Forward/Av1Adst16ForwardTransformer.cs | 15 + .../Forward/Av1Adst32ForwardTransformer.cs | 15 + .../Forward/Av1Adst4ForwardTransformer.cs | 15 + .../Forward/Av1Adst8ForwardTransformer.cs | 15 + .../Forward/Av1Dct16ForwardTransformer.cs | 15 + .../Forward/Av1Dct32ForwardTransformer.cs | 15 + .../Forward/Av1Dct4ForwardTransformer.cs | 85 +++++ .../Forward/Av1Dct64ForwardTransformer.cs | 15 + .../Forward/Av1Dct8ForwardTransformer.cs | 15 + .../Av1Identity16ForwardTransformer.cs | 15 + .../Av1Identity32ForwardTransformer.cs | 15 + .../Forward/Av1Identity4ForwardTransformer.cs | 15 + .../Av1Identity64ForwardTransformer.cs | 15 + .../Forward/Av1Identity8ForwardTransformer.cs | 15 + .../Transform/ForwardTransformerFactory.cs | 56 ++++ .../Av1/Transform/IAv1ForwardTransformer.cs | 31 ++ .../Transform/InverseTransformerFactory.cs | 19 ++ .../Heif/Av1/Av1ForwardTransformTests.cs | 211 ++++++++++++ .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 273 +++++++++++++++ 29 files changed, 1804 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index a023529493..9e4831f01a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; + namespace SixLabors.ImageSharp.Formats.Heif.Av1; internal static class Av1Math @@ -155,4 +157,10 @@ internal static int DivideLog2Ceiling(int value, int n) internal static int RoundPowerOf2Signed(int value, int n) => (value < 0) ? -RoundPowerOf2(-value, n) : RoundPowerOf2(value, n); + + internal static int RoundShift(long value, int bit) + { + DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit)); + return (int)((value + (1L << (bit - 1))) >> bit); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs new file mode 100644 index 0000000000..a4f9efc6e5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1CoefficientShape.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1CoefficientShape +{ + Default, + N2, + N4, + OnlyDc +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs new file mode 100644 index 0000000000..e7363ed42b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs @@ -0,0 +1,157 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1DctDctInverseTransformer +{ + private const int UnitQuantizationShift = 2; + + internal static void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.IsTrue(transformFunctionParameters.TransformType == Av1TransformType.DctDct, nameof(transformFunctionParameters.TransformType), "This class implements DCT-DCT transformations only."); + + switch (transformFunctionParameters.TransformSize) + { + case Av1TransformSize.Size4x4: + InverseWhalshHadamard4x4(ref coefficients, ref readBuffer[0], readStride, ref writeBuffer[0], writeStride, transformFunctionParameters.EndOfBuffer, transformFunctionParameters.BitDepth); + break; + default: + throw new NotImplementedException("Only 4x4 transformation size supported for now"); + } + } + + /// + /// SVT: highbd_iwht4x4_add + /// + private static void InverseWhalshHadamard4x4(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int endOfBuffer, int bitDepth) + { + if (endOfBuffer > 1) + { + InverseWhalshHadamard4x4Add16(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + else + { + InverseWhalshHadamard4x4Add1(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_16_add_c + /// + private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + 0.5 shifts per pixel. */ + int i; + Span output = stackalloc ushort[16]; + ushort a1, b1, c1, d1, e1; + ref int ip = ref input; + ref ushort op = ref output[0]; + ref ushort opTmp = ref output[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + for (i = 0; i < 4; i++) + { + a1 = (ushort)(ip >> UnitQuantizationShift); + c1 = (ushort)(Unsafe.Add(ref ip, 1) >> UnitQuantizationShift); + d1 = (ushort)(Unsafe.Add(ref ip, 2) >> UnitQuantizationShift); + b1 = (ushort)(Unsafe.Add(ref ip, 3) >> UnitQuantizationShift); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + op = a1; + Unsafe.Add(ref op, 1) = b1; + Unsafe.Add(ref op, 2) = c1; + Unsafe.Add(ref op, 3) = d1; + ip = ref Unsafe.Add(ref ip, 4); + op = ref Unsafe.Add(ref op, 4); + } + + ip = opTmp; + for (i = 0; i < 4; i++) + { + a1 = (ushort)ip; + c1 = (ushort)Unsafe.Add(ref ip, 4); + d1 = (ushort)Unsafe.Add(ref ip, 8); + b1 = (ushort)Unsafe.Add(ref ip, 12); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + /* Disabled in normal build + range_check_value(a1, (int8_t)(bd + 1)); + range_check_value(b1, (int8_t)(bd + 1)); + range_check_value(c1, (int8_t)(bd + 1)); + range_check_value(d1, (int8_t)(bd + 1)); + */ + + destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), d1, bitDepth); + + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_1_add_c + /// + private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + int i; + ushort a1, e1; + Span tmp = stackalloc int[4]; + ref int ip = ref input; + ref int ipTmp = ref tmp[0]; + ref int op = ref tmp[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + a1 = (ushort)(ip >> UnitQuantizationShift); + e1 = (ushort)(a1 >> 1); + a1 -= e1; + op = a1; + Unsafe.Add(ref op, 1) = e1; + Unsafe.Add(ref op, 2) = e1; + Unsafe.Add(ref op, 3) = e1; + + ip = ipTmp; + for (i = 0; i < 4; i++) + { + e1 = (ushort)(ip >> 1); + a1 = (ushort)(ip - e1); + destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), a1, bitDepth); + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } + + private static ushort ClipPixelAdd(ushort value, int trans, int bitDepth) + => ClipPixel(value + trans, bitDepth); + + private static ushort ClipPixel(int value, int bitDepth) + => bitDepth switch + { + 10 => (ushort)Av1Math.Clamp(value, 0, 1023), + 12 => (ushort)Av1Math.Clamp(value, 0, 4095), + _ => (ushort)Av1Math.Clamp(value, 0, 255), + }; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs new file mode 100644 index 0000000000..c0c982cc1e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -0,0 +1,276 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1ForwardTransformer +{ + private const int NewSqrt = 5793; + private const int NewSqrtBitCount = 12; + + private static readonly IAv1ForwardTransformer?[] Transformers = + [ + new Av1Dct4ForwardTransformer(), + new Av1Dct8ForwardTransformer(), + new Av1Dct16ForwardTransformer(), + new Av1Dct32ForwardTransformer(), + new Av1Dct64ForwardTransformer(), + new Av1Adst4ForwardTransformer(), + new Av1Adst8ForwardTransformer(), + new Av1Adst16ForwardTransformer(), + new Av1Adst32ForwardTransformer(), + new Av1Identity4ForwardTransformer(), + new Av1Identity8ForwardTransformer(), + new Av1Identity16ForwardTransformer(), + new Av1Identity32ForwardTransformer(), + new Av1Identity64ForwardTransformer(), + null + ]; + + private static readonly int[] TemporaryCoefficientsBuffer = new int[64 * 64]; + + internal static void Transform2d(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) + { + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + ref int buffer = ref TemporaryCoefficientsBuffer[0]; + IAv1ForwardTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); + IAv1ForwardTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); + if (columnTransformer != null && rowTransformer != null) + { + Transform2dCore(columnTransformer, rowTransformer, ref input[0], stride, ref coefficients[0], config, ref buffer, bitDepth); + } + else + { + throw new InvalidImageContentException($"Cannot find 1d transformer implementation for {config.TransformFunctionTypeColumn} or {config.TransformFunctionTypeRow}."); + } + } + + internal static void Transform2dAvx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) + { + switch (transformSize) + { + case Av1TransformSize.Size4x4: + // Too small for intrinsics, use the scalar codepath instead. + Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); + break; + case Av1TransformSize.Size8x8: + Transform8x8Avx2(input, coefficients, stride, transformType, bitDepth); + break; + default: + Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); + break; + } + } + + /// + /// SVT: svt_av1_fwd_txfm2d_8x8_avx2 + /// + private static void Transform8x8Avx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, int bitDepth) + { + Av1Transform2dFlipConfiguration config = new(transformType, Av1TransformSize.Size8x8); + Span shift = config.Shift; + Span> inVector = stackalloc Vector256[8]; + Span> outVector = stackalloc Vector256[8]; + ref Vector256 inRef = ref inVector[0]; + ref Vector256 outRef = ref outVector[0]; + switch (transformType) + { + case Av1TransformType.DctDct: + /* Pseudo code + Av1Dct8ForwardTransformer dct8 = new(); + LoadBuffer8x8(ref input[0], ref inRef, stride, 0, 0, shift[0]); + dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitColumn, 1); + Column8x8Rounding(ref outRef, -shift[1]); + Transpose8x8Avx2(ref outRef, ref inRef); + dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitRow, 1); + Transpose8x8Avx2(ref outRef, ref inRef); + WriteBuffer8x8(ref inRef, ref coefficients[0]); + break; + */ + throw new NotImplementedException(); + default: + throw new NotImplementedException(); + } + } + + private static IAv1ForwardTransformer? GetTransformer(Av1TransformFunctionType transformerType) + => Transformers[(int)transformerType]; + + /// + /// SVT: av1_tranform_two_d_core_c + /// + private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) + where TColumn : IAv1ForwardTransformer + where TRow : IAv1ForwardTransformer + { + int c, r; + + // Note when assigning txfm_size_col, we use the txfm_size from the + // row configuration and vice versa. This is intentionally done to + // accurately perform rectangular transforms. When the transform is + // rectangular, the number of columns will be the same as the + // txfm_size stored in the row cfg struct. It will make no difference + // for square transforms. + int transformColumnCount = config.TransformSize.GetWidth(); + int transformRowCount = config.TransformSize.GetHeight(); + int transformCount = transformColumnCount * transformRowCount; + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = GetRectangularRatio(transformColumnCount, transformRowCount); + Span stageRangeColumn = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + Span stageRangeRow = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + + // assert(cfg->stage_num_col <= MAX_TXFM_STAGE_NUM); + // assert(cfg->stage_num_row <= MAX_TXFM_STAGE_NUM); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + + // ASSERT(txfm_func_col != NULL); + // ASSERT(txfm_func_row != NULL); + // use output buffer as temp buffer + ref int tempIn = ref output; + ref int tempOut = ref Unsafe.Add(ref output, transformRowCount); + + // Columns + for (c = 0; c < transformColumnCount; ++c) + { + if (!config.FlipUpsideDown) + { + uint t = (uint)c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t += inputStride; + } + } + else + { + uint t = (uint)(c + ((transformRowCount - 1) * (int)inputStride)); + for (r = 0; r < transformRowCount; ++r) + { + // flip upside down + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t -= inputStride; + } + } + + RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c + transformFunctionColumn.Transform(ref tempIn, ref tempOut, cosBitColumn, stageRangeColumn); + RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c + if (!config.FlipLeftToRight) + { + int t = c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + else + { + int t = transformColumnCount - c - 1; + for (r = 0; r < transformRowCount; ++r) + { + // flip from left to right + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + } + + // Rows + for (r = 0; r < transformRowCount; ++r) + { + transformFunctionRow.Transform(ref Unsafe.Add(ref buf, r * transformColumnCount), ref Unsafe.Add(ref output, r * transformColumnCount), cosBitRow, stageRangeRow); + RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); + + if (Math.Abs(rectangleType) == 1) + { + // Multiply everything by Sqrt2 if the transform is rectangular and the + // size difference is a factor of 2. + for (c = 0; c < transformColumnCount; ++c) + { + ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); + current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); + } + } + } + } + + private static void RoundShiftArray(ref int arr, int size, int bit) + { + if (bit == 0) + { + return; + } + else + { + nuint sz = (nuint)size; + if (bit > 0) + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a = Av1Math.RoundShift(a, bit); + } + } + else + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a *= 1 << (-bit); + } + } + } + } + + /// + /// SVT: get_rect_tx_log_ratio + /// + public static int GetRectangularRatio(int col, int row) + { + if (col == row) + { + return 0; + } + + if (col > row) + { + if (col == row * 2) + { + return 1; + } + + if (col == row * 4) + { + return 2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + else + { + if (row == col * 2) + { + return -1; + } + + if (row == col * 4) + { + return -2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + + return 0; // Invalid + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs index 1f8855602e..edb04aeae6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -8,8 +8,45 @@ internal class Av1InverseTransformer /// /// SVT: svt_aom_inv_transform_recon8bit /// - public static void Reconstruct8Bit(Span coefficientsBuffer, Span transformBlockReconstructionBuffer1, int reconstructionStride1, Span transformBlockReconstructionBuffer2, int reconstructionStride2, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) + public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBufferRead, int reconstructionReadStride, Span reconstructionBufferWrite, int reconstructionWriteStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) { - throw new NotImplementedException("Inverse transformation not implemented yet."); + Av1TransformFunctionParameters transformFunctionParameters = new() + { + TransformType = transformType, + TransformSize = transformSize, + EndOfBuffer = numberOfCoefficients, + IsLossless = isLossless, + BitDepth = 8, + Is16BitPipeline = false + }; + + if (reconstructionBufferRead != reconstructionBufferWrite) + { + /* When output pointers to read and write are differents, + * then kernel copy also all buffer from read to write, + * and cannot be limited by End Of Buffer calculations. */ + transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); + } + + InverseTransformerFactory.InverseTransformAdd( + ref coefficientsBuffer[0], reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); + } + + /// + /// SVT: av1_get_max_eob + /// + private static int GetMaxEndOfBuffer(Av1TransformSize transformSize) + { + if (transformSize is Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64) + { + return 1024; + } + + if (transformSize is Av1TransformSize.Size16x64 or Av1TransformSize.Size64x16) + { + return 512; + } + + return transformSize.GetSize2d(); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs new file mode 100644 index 0000000000..432402677f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs @@ -0,0 +1,75 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1SinusConstants +{ + public const int MinimumCosinusBit = 10; + + // av1_cospi_arr[i][j] = (int32_t)round(cos(M_PI*j/128) * (1<<(cos_bit_min+i))); + private static readonly int[][] CosinusPiArray = + [ + [ + 1024, 1024, 1023, 1021, 1019, 1016, 1013, 1009, 1004, 999, 993, 987, 980, 972, 964, 955, + 946, 936, 926, 915, 903, 891, 878, 865, 851, 837, 822, 807, 792, 775, 759, 742, + 724, 706, 688, 669, 650, 630, 610, 590, 569, 548, 526, 505, 483, 460, 438, 415, + 392, 369, 345, 321, 297, 273, 249, 224, 200, 175, 150, 125, 100, 75, 50, 25 + ], + [ + 2048, 2047, 2046, 2042, 2038, 2033, 2026, 2018, 2009, 1998, 1987, 1974, 1960, 1945, 1928, 1911, + 1892, 1872, 1851, 1829, 1806, 1782, 1757, 1730, 1703, 1674, 1645, 1615, 1583, 1551, 1517, 1483, + 1448, 1412, 1375, 1338, 1299, 1260, 1220, 1179, 1138, 1096, 1053, 1009, 965, 921, 876, 830, + 784, 737, 690, 642, 595, 546, 498, 449, 400, 350, 301, 251, 201, 151, 100, 50 + ], + [ + 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889, 3857, 3822, + 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, 3406, 3349, 3290, 3229, 3166, 3102, 3035, 2967, + 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, + 1567, 1474, 1380, 1285, 1189, 1092, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101 + ], + [ + 8192, 8190, 8182, 8170, 8153, 8130, 8103, 8071, 8035, 7993, 7946, 7895, 7839, 7779, 7713, 7643, + 7568, 7489, 7405, 7317, 7225, 7128, 7027, 6921, 6811, 6698, 6580, 6458, 6333, 6203, 6070, 5933, + 5793, 5649, 5501, 5351, 5197, 5040, 4880, 4717, 4551, 4383, 4212, 4038, 3862, 3683, 3503, 3320, + 3135, 2948, 2760, 2570, 2378, 2185, 1990, 1795, 1598, 1401, 1202, 1003, 803, 603, 402, 201 + ], + [ + 16384, 16379, 16364, 16340, 16305, 16261, 16207, 16143, 16069, 15986, 15893, 15791, 15679, 15557, 15426, 15286, + 15137, 14978, 14811, 14635, 14449, 14256, 14053, 13842, 13623, 13395, 13160, 12916, 12665, 12406, 12140, 11866, + 11585, 11297, 11003, 10702, 10394, 10080, 9760, 9434, 9102, 8765, 8423, 8076, 7723, 7366, 7005, 6639, + 6270, 5897, 5520, 5139, 4756, 4370, 3981, 3590, 3196, 2801, 2404, 2006, 1606, 1205, 804, 402 + ], + [ + 32768, 32758, 32729, 32679, 32610, 32522, 32413, 32286, 32138, 31972, 31786, 31581, 31357, 31114, 30853, 30572, + 30274, 29957, 29622, 29269, 28899, 28511, 28106, 27684, 27246, 26791, 26320, 25833, 25330, 24812, 24279, 23732, + 23170, 22595, 22006, 21403, 20788, 20160, 19520, 18868, 18205, 17531, 16846, 16151, 15447, 14733, 14010, 13279, + 12540, 11793, 11039, 10279, 9512, 8740, 7962, 7180, 6393, 5602, 4808, 4011, 3212, 2411, 1608, 804 + ], + [ + 65536, 65516, 65457, 65358, 65220, 65043, 64827, 64571, 64277, 63944, 63572, 63162, 62714, 62228, 61705, 61145, + 60547, 59914, 59244, 58538, 57798, 57022, 56212, 55368, 54491, 53581, 52639, 51665, 50660, 49624, 48559, 47464, + 46341, 45190, 44011, 42806, 41576, 40320, 39040, 37736, 36410, 35062, 33692, 32303, 30893, 29466, 28020, 26558, + 25080, 23586, 22078, 20557, 19024, 17479, 15924, 14359, 12785, 11204, 9616, 8022, 6424, 4821, 3216, 1608 + ] + ]; + + // svt_aom_eb_av1_sinpi_arr_data[i][j] = (int32_t)round((sqrt(2) * sin(j*Pi/9) * 2 / 3) * (1 + // << (cos_bit_min + i))) modified so that elements j=1,2 sum to element j=4. + private static readonly int[][] SinusPiArray = + [ + [0, 330, 621, 836, 951], + [0, 660, 1241, 1672, 1901], + [0, 1321, 2482, 3344, 3803], + [0, 2642, 4964, 6689, 7606], + [0, 5283, 9929, 13377, 15212], + [0, 10566, 19858, 26755, 30424], + [0, 21133, 39716, 53510, 60849] + ]; + + public static Span CosinusPi(int n) => CosinusPiArray[n - MinimumCosinusBit]; + + public static Span SinusPi(int n) => SinusPiArray[n - MinimumCosinusBit]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs new file mode 100644 index 0000000000..534edd3f1d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -0,0 +1,313 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Drawing; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1Transform2dFlipConfiguration +{ + public const int MaxStageNumber = 12; + private const int SmallestTransformSizeLog2 = 2; + + private static readonly Av1TransformType1d[] VerticalType = + [ + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Dct, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Adst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Identity, + Av1TransformType1d.Dct, + Av1TransformType1d.Identity, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Identity, + ]; + + private static readonly Av1TransformType1d[] HorizontalType = + [ + Av1TransformType1d.Dct, + Av1TransformType1d.Dct, + Av1TransformType1d.Adst, + Av1TransformType1d.Adst, + Av1TransformType1d.Dct, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.FlipAdst, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.Identity, + Av1TransformType1d.Dct, + Av1TransformType1d.Identity, + Av1TransformType1d.Adst, + Av1TransformType1d.Identity, + Av1TransformType1d.FlipAdst, + ]; + + private static readonly int[][] ShiftMap = + [ + [2, 0, 0], // 4x4 + [2, -1, 0], // 8x8 + [2, -2, 0], // 16x16 + [2, -4, 0], // 32x32 + [0, -2, -2], // 64x64 + [2, -1, 0], // 4x8 + [2, -1, 0], // 8x4 + [2, -2, 0], // 8x16 + [2, -2, 0], // 16x8 + [2, -4, 0], // 16x32 + [2, -4, 0], // 32x16 + [0, -2, -2], // 32x64 + [2, -4, -2], // 64x32 + [2, -1, 0], // 4x16 + [2, -1, 0], // 16x4 + [2, -2, 0], // 8x32 + [2, -2, 0], // 32x8 + [0, -2, 0], // 16x64 + [2, -4, 0], // 64x16 + ]; + + private static readonly int[][] CosBitColumnMap = + [[13, 13, 13, 0, 0], [13, 13, 13, 12, 0], [13, 13, 13, 12, 13], [0, 13, 13, 12, 13], [0, 0, 13, 12, 13]]; + + private static readonly int[][] CosBitRowMap = + [[13, 13, 12, 0, 0], [13, 13, 13, 12, 0], [13, 13, 12, 13, 12], [0, 12, 13, 12, 11], [0, 0, 12, 11, 10]]; + + private static readonly Av1TransformFunctionType[][] TransformFunctionTypeMap = + [ + [Av1TransformFunctionType.Dct4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Adst4, Av1TransformFunctionType.Identity4], + [Av1TransformFunctionType.Dct8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Adst8, Av1TransformFunctionType.Identity8], + [Av1TransformFunctionType.Dct16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Adst16, Av1TransformFunctionType.Identity16], + [Av1TransformFunctionType.Dct32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Adst32, Av1TransformFunctionType.Identity32], + [Av1TransformFunctionType.Dct64, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Invalid, Av1TransformFunctionType.Identity64] + ]; + + private static readonly int[] StageNumberList = + [ + 4, // TXFM_TYPE_DCT4 + 6, // TXFM_TYPE_DCT8 + 8, // TXFM_TYPE_DCT16 + 10, // TXFM_TYPE_DCT32 + 12, // TXFM_TYPE_DCT64 + 7, // TXFM_TYPE_ADST4 + 8, // TXFM_TYPE_ADST8 + 10, // TXFM_TYPE_ADST16 + 12, // TXFM_TYPE_ADST32 + 1, // TXFM_TYPE_IDENTITY4 + 1, // TXFM_TYPE_IDENTITY8 + 1, // TXFM_TYPE_IDENTITY16 + 1, // TXFM_TYPE_IDENTITY32 + 1, // TXFM_TYPE_IDENTITY64 + ]; + + private static readonly int[][] RangeMulti2List = + [ + [0, 2, 3, 3], // fdct4_range_mult2 + [0, 2, 4, 5, 5, 5], // fdct8_range_mult2 + [0, 2, 4, 6, 7, 7, 7, 7], // fdct16_range_mult2 + [0, 2, 4, 6, 8, 9, 9, 9, 9, 9], // fdct32_range_mult2 + [0, 2, 4, 6, 8, 10, 11, 11, 11, 11, 11, 11], // fdct64_range_mult2 + [0, 2, 4, 3, 3, 3, 3], // fadst4_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 5], // fadst8_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 7, 7, 7], // fadst16_range_mult2 + [0, 0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 9], // fadst32_range_mult2 + [1], // fidtx4_range_mult2 + [2], // fidtx8_range_mult2 + [3], // fidtx16_range_mult2 + [4], // fidtx32_range_mult2 + [5], // fidtx64_range_mult2 + ]; + + private readonly int[] shift; + + public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1TransformSize transformSize) + { + this.TransformSize = transformSize; + this.TransformType = transformType; + this.SetFlip(transformType); + Av1TransformType1d tx_type_1d_col = VerticalType[(int)transformType]; + Av1TransformType1d tx_type_1d_row = HorizontalType[(int)transformType]; + int txw_idx = transformSize.GetBlockWidthLog2() - SmallestTransformSizeLog2; + int txh_idx = transformSize.GetBlockHeightLog2() - SmallestTransformSizeLog2; + this.shift = ShiftMap[(int)transformSize]; + this.CosBitColumn = CosBitColumnMap[txw_idx][txh_idx]; + this.CosBitRow = CosBitRowMap[txw_idx][txh_idx]; + this.TransformFunctionTypeColumn = TransformFunctionTypeMap[txh_idx][(int)tx_type_1d_col]; + this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)tx_type_1d_row]; + this.StageNumberColumn = StageNumberList[(int)this.TransformFunctionTypeColumn]; + this.StageNumberRow = StageNumberList[(int)this.TransformFunctionTypeRow]; + this.StageRangeColumn = new int[12]; + this.StageRangeRow = new int[12]; + this.NonScaleRange(); + } + + public int CosBitColumn { get; } + + public int CosBitRow { get; } + + public Av1TransformFunctionType TransformFunctionTypeColumn { get; } + + public Av1TransformFunctionType TransformFunctionTypeRow { get; } + + public int StageNumberColumn { get; } + + public int StageNumberRow { get; } + + public Av1TransformSize TransformSize { get; } + + public Av1TransformType TransformType { get; } + + public bool FlipUpsideDown { get; private set; } + + public bool FlipLeftToRight { get; private set; } + + public Span Shift => this.shift; + + public int[] StageRangeColumn { get; } + + public int[] StageRangeRow { get; } + + /// + /// SVT: svt_av1_gen_fwd_stage_range + /// + public void GenerateStageRange(int bitDepth) + { + // Take the shift from the larger dimension in the rectangular case. + Span shift = this.Shift; + + // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning + for (int i = 0; i < this.StageNumberColumn && i < MaxStageNumber; ++i) + { + this.StageRangeColumn[i] = this.StageRangeColumn[i] + shift[0] + bitDepth + 1; + } + + // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning + for (int i = 0; i < this.StageNumberRow && i < MaxStageNumber; ++i) + { + this.StageRangeRow[i] = this.StageRangeRow[i] + shift[0] + shift[1] + bitDepth + 1; + } + } + + /// + /// SVT: is_txfm_allowed + /// + public bool IsAllowed() + { + Av1TransformType[] supportedTypes = + [ + Av1TransformType.DctDct, + Av1TransformType.AdstDct, + Av1TransformType.DctAdst, + Av1TransformType.AdstAdst, + Av1TransformType.FlipAdstDct, + Av1TransformType.DctFlipAdst, + Av1TransformType.FlipAdstFlipAdst, + Av1TransformType.AdstFlipAdst, + Av1TransformType.FlipAdstAdst, + Av1TransformType.Identity, + Av1TransformType.VerticalDct, + Av1TransformType.HorizontalDct, + Av1TransformType.VerticalAdst, + Av1TransformType.HorizontalAdst, + Av1TransformType.VerticalFlipAdst, + Av1TransformType.HorizontalFlipAdst, + ]; + + switch (this.TransformSize) + { + case Av1TransformSize.Size32x32: + supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity, Av1TransformType.VerticalDct, Av1TransformType.HorizontalDct]; + break; + case Av1TransformSize.Size32x64: + case Av1TransformSize.Size64x32: + case Av1TransformSize.Size16x64: + case Av1TransformSize.Size64x16: + supportedTypes = [Av1TransformType.DctDct]; + break; + case Av1TransformSize.Size16x32: + case Av1TransformSize.Size32x16: + case Av1TransformSize.Size64x64: + case Av1TransformSize.Size8x32: + case Av1TransformSize.Size32x8: + supportedTypes = [Av1TransformType.DctDct, Av1TransformType.Identity]; + break; + default: + break; + } + + return supportedTypes.Contains(this.TransformType); + } + + private void SetFlip(Av1TransformType transformType) + { + switch (transformType) + { + case Av1TransformType.DctDct: + case Av1TransformType.AdstDct: + case Av1TransformType.DctAdst: + case Av1TransformType.AdstAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = false; + break; + case Av1TransformType.Identity: + case Av1TransformType.VerticalDct: + case Av1TransformType.HorizontalDct: + case Av1TransformType.VerticalAdst: + case Av1TransformType.HorizontalAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = false; + break; + case Av1TransformType.FlipAdstDct: + case Av1TransformType.FlipAdstAdst: + case Av1TransformType.VerticalFlipAdst: + this.FlipUpsideDown = true; + this.FlipLeftToRight = false; + break; + case Av1TransformType.DctFlipAdst: + case Av1TransformType.AdstFlipAdst: + case Av1TransformType.HorizontalFlipAdst: + this.FlipUpsideDown = false; + this.FlipLeftToRight = true; + break; + case Av1TransformType.FlipAdstFlipAdst: + this.FlipUpsideDown = true; + this.FlipLeftToRight = true; + break; + default: + Guard.IsTrue(false, nameof(transformType), "Unknown transform type for determining flip."); + break; + } + } + + /// + /// SVT: set_fwd_txfm_non_scale_range + /// + private void NonScaleRange() + { + Span range_mult2_col = RangeMulti2List[(int)this.TransformFunctionTypeColumn]; + if (this.TransformFunctionTypeColumn != Av1TransformFunctionType.Invalid) + { + int stage_num_col = this.StageNumberColumn; + for (int i = 0; i < stage_num_col; ++i) + { + this.StageRangeColumn[i] = (range_mult2_col[i] + 1) >> 1; + } + } + + if (this.TransformFunctionTypeRow != Av1TransformFunctionType.Invalid) + { + int stage_num_row = this.StageNumberRow; + Span range_mult2_row = RangeMulti2List[(int)this.TransformFunctionTypeRow]; + for (int i = 0; i < stage_num_row; ++i) + { + this.StageRangeRow[i] = (range_mult2_col[this.StageNumberColumn - 1] + range_mult2_row[i] + 1) >> 1; + } + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs new file mode 100644 index 0000000000..ae24e659c3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionParameters.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1TransformFunctionParameters +{ + public Av1TransformType TransformType { get; internal set; } + + public Av1TransformSize TransformSize { get; internal set; } + + public int EndOfBuffer { get; internal set; } + + public bool IsLossless { get; internal set; } + + public int BitDepth { get; internal set; } + + public bool Is16BitPipeline { get; internal set; } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs new file mode 100644 index 0000000000..cd118454f0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformFunctionType.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformFunctionType +{ + Dct4, + Dct8, + Dct16, + Dct32, + Dct64, + Adst4, + Adst8, + Adst16, + Adst32, + Identity4, + Identity8, + Identity16, + Identity32, + Identity64, + Invalid, +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs new file mode 100644 index 0000000000..a201493198 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType1d.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal enum Av1TransformType1d +{ + Dct, + Adst, + FlipAdst, + Identity +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs new file mode 100644 index 0000000000..6bb615acd6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs new file mode 100644 index 0000000000..d64cfa0a2f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs new file mode 100644 index 0000000000..fa70450569 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs new file mode 100644 index 0000000000..66fad050f9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Adst8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs new file mode 100644 index 0000000000..4725952b85 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs new file mode 100644 index 0000000000..a0178da831 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs new file mode 100644 index 0000000000..db0134c36f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs @@ -0,0 +1,85 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException("Too small block for Vector implementation, use TransformSse() method instead."); + + /// + /// SVT: fdct4x4_sse4_1 + /// + public static void TransformSse(ref Vector128 input, ref Vector128 output, byte cosBit, int columnNumber) + { + /* + // We only use stage-2 bit; + // shift[0] is used in load_buffer_4x4() + // shift[1] is used in txfm_func_col() + // shift[2] is used in txfm_func_row() + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + Vector128 cospi32 = Vector128.Create(cospi[32]); + Vector128 cospi48 = Vector128.Create(cospi[48]); + Vector128 cospi16 = Vector128.Create(cospi[16]); + Vector128 rnding = Vector128.Create(1 << (cosBit - 1)); + Vector128 s0, s1, s2, s3; + Vector128 u0, u1, u2, u3; + Vector128 v0, v1, v2, v3; + + int endidx = 3 * columnNumber; + s0 = Sse41.Add(input, Unsafe.Add(ref input, endidx)); + s3 = Sse41.Subtract(input, Unsafe.Add(ref input, endidx)); + endidx -= columnNumber; + s1 = Sse41.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + s2 = Sse41.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + + // btf_32_sse4_1_type0(cospi32, cospi32, s[01], u[02], bit); + u0 = Sse41.MultiplyLow(s0, cospi32); + u1 = Sse41.MultiplyLow(s1, cospi32); + u2 = Sse41.Add(u0, u1); + v0 = Sse41.Subtract(u0, u1); + + u3 = Sse41.Add(u2, rnding); + v1 = Sse41.Add(v0, rnding); + + u0 = Sse41.ShiftRightArithmetic(u3, cosBit); + u2 = Sse41.ShiftRightArithmetic(v1, cosBit); + + // btf_32_sse4_1_type1(cospi48, cospi16, s[23], u[13], bit); + v0 = Sse41.MultiplyLow(s2, cospi48); + v1 = Sse41.MultiplyLow(s3, cospi16); + v2 = Sse41.Add(v0, v1); + + v3 = Sse41.Add(v2, rnding); + u1 = Sse41.ShiftRightArithmetic(v3, cosBit); + + v0 = Sse41.MultiplyLow(s2, cospi16); + v1 = Sse41.MultiplyLow(s3, cospi48); + v2 = Sse41.Subtract(v1, v0); + + v3 = Sse41.Add(v2, rnding); + u3 = Sse41.ShiftRightArithmetic(v3, cosBit); + + // Note: shift[1] and shift[2] are zeros + + // Transpose 4x4 32-bit + v0 = Sse41.UnpackLow(u0, u1); + v1 = Sse41.UnpackHigh(u0, u1); + v2 = Sse41.UnpackLow(u2, u3); + v3 = Sse41.UnpackHigh(u2, u3); + + output = Sse41.UnpackLow(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 1) = Sse41.UnpackHigh(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 2) = Sse41.UnpackLow(v1.AsInt64(), v3.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 3) = Sse41.UnpackHigh(v1.AsInt64(), v3.AsInt64()).AsInt32(); + */ + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs new file mode 100644 index 0000000000..63cfeb2a4e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct64ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs new file mode 100644 index 0000000000..317c1bb4e6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs new file mode 100644 index 0000000000..e3ed9f4a50 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity16ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs new file mode 100644 index 0000000000..baba34c906 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity32ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs new file mode 100644 index 0000000000..4afabf67f2 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity4ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs new file mode 100644 index 0000000000..00ea87cf55 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity64ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs new file mode 100644 index 0000000000..224184e53c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity8ForwardTransformer : IAv1ForwardTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); + + public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs new file mode 100644 index 0000000000..4463f2d225 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class ForwardTransformerFactory +{ + internal static void EstimateTransform( + Span residualBuffer, + uint residualStride, + Span coefficientBuffer, + uint coefficientStride, + Av1TransformSize transformSize, + ref ulong threeQuadEnergy, + int bitDepth, + Av1TransformType transformType, + Av1PlaneType componentType, + Av1CoefficientShape transformCoefficientShape) + { + switch (transformCoefficientShape) + { + case Av1CoefficientShape.Default: + EstimateTransformDefault(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.N2: + EstimateTransformN2(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.N4: + EstimateTransformN4(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + case Av1CoefficientShape.OnlyDc: + EstimateTransformOnlyDc(residualBuffer, residualStride, coefficientBuffer, coefficientStride, transformSize, ref threeQuadEnergy, bitDepth, transformType, componentType); + break; + } + } + + private static void EstimateTransformDefault( + Span residualBuffer, + uint residualStride, + Span coefficientBuffer, + uint coefficientStride, + Av1TransformSize transformSize, + ref ulong threeQuadEnergy, + int bitDepth, + Av1TransformType transformType, + Av1PlaneType componentType) + => Av1ForwardTransformer.Transform2d(residualBuffer, coefficientBuffer, residualStride, transformType, transformSize, bitDepth); + + private static void EstimateTransformN2(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); + + private static void EstimateTransformN4(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); + + private static void EstimateTransformOnlyDc(Span residualBuffer, uint residualStride, Span coefficientBuffer, uint coefficientStride, Av1TransformSize transformSize, ref ulong threeQuadEnergy, int bitDepth, Av1TransformType transformType, Av1PlaneType componentType) => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs new file mode 100644 index 0000000000..c7ef675f3d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +/// +/// Implementation of a specific forward transform function. +/// +internal interface IAv1ForwardTransformer +{ + /// + /// Execute the transformation. + /// + /// Input pixels. + /// Output coefficients. + /// The cosinus bit. + /// Stage ranges. + void Transform(ref int input, ref int output, int cosBit, Span stageRange); + + /// + /// Execute the transformation using instructions. + /// + /// Array of input vectors. + /// Array of output coefficients vectors. + /// The cosinus bit. + /// The column number to process. + void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs new file mode 100644 index 0000000000..feb7dbe76c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class InverseTransformerFactory +{ + internal static unsafe void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + switch (transformFunctionParameters.TransformType) + { + case Av1TransformType.DctDct: + Av1DctDctInverseTransformer.InverseTransformAdd(ref coefficients, readBuffer, readStride, writeBuffer, writeStride, transformFunctionParameters); + break; + default: + throw new InvalidImageContentException("Unknown transform type: " + transformFunctionParameters.TransformType); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs new file mode 100644 index 0000000000..8922920ed8 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -0,0 +1,211 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +/// +/// SVY: test/FwdTxfm2dTest.cc +/// +[Trait("Format", "Avif")] +public class Av1ForwardTransformTests +{ + private static readonly double[] MaximumAllowedError = + [ + 3, // 4x4 transform + 5, // 8x8 transform + 11, // 16x16 transform + 70, // 32x32 transform + 64, // 64x64 transform + 3.9, // 4x8 transform + 4.3, // 8x4 transform + 12, // 8x16 transform + 12, // 16x8 transform + 32, // 16x32 transform + 46, // 32x16 transform + 136, // 32x64 transform + 136, // 64x32 transform + 5, // 4x16 transform + 6, // 16x4 transform + 21, // 8x32 transform + 13, // 32x8 transform + 30, // 16x64 transform + 36, // 64x16 transform + ]; + + private readonly short[] inputOfTest; + private readonly int[] outputOfTest; + private readonly double[] inputReference; + private readonly double[] outputReference; + + public Av1ForwardTransformTests() + { + this.inputOfTest = new short[64 * 64]; + this.outputOfTest = new int[64 * 64]; + this.inputReference = new double[64 * 64]; + this.outputReference = new double[64 * 64]; + } + + [Theory] + [MemberData(nameof(GetCombinations))] + public void Accuracy2dTest(int txSize, int txType, int maxAllowedError) + { + const int bitDepth = 8; + Random rnd = new(0); + const int testBlockCount = 1; // Originally set to: 1000 + Av1TransformSize transformSize = (Av1TransformSize)txSize; + Av1TransformType transformType = (Av1TransformType)txType; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + int width = config.TransformSize.GetWidth(); + int height = config.TransformSize.GetHeight(); + int blockSize = width * height; + double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config, width, height); + + for (int ti = 0; ti < testBlockCount; ++ti) + { + // prepare random test data + for (int ni = 0; ni < blockSize; ++ni) + { + this.inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); + this.inputReference[ni] = this.inputOfTest[ni]; + this.outputReference[ni] = 0; + this.outputOfTest[ni] = 255; + } + + // calculate in forward transform functions + Av1ForwardTransformer.Transform2d( + this.inputOfTest, + this.outputOfTest, + (uint)transformSize.GetWidth(), + transformType, + transformSize, + bitDepth); + + // calculate in reference forward transform functions + Av1ReferenceTransform.ReferenceTransformFunction2d(this.inputReference, this.outputReference, transformType, transformSize, scaleFactor); + + // repack the coefficents for some tx_size + this.RepackCoefficients(width, height); + + // compare for the result is in accuracy + double maximumErrorInTest = 0; + for (int ni = 0; ni < blockSize; ++ni) + { + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(this.outputOfTest[ni] - Math.Round(this.outputReference[ni]))); + } + + maximumErrorInTest /= scaleFactor; + Assert.True(maxAllowedError >= maximumErrorInTest, $"Forward transform 2d test with transform type: {transformType}, transform size: {transformSize} and loop: {ti}"); + } + } + + // The max txb_width or txb_height is 32, as specified in spec 7.12.3. + // Clear the high frequency coefficents and repack it in linear layout. + private void RepackCoefficients(int tx_width, int tx_height) + { + for (int i = 0; i < 2; ++i) + { + uint e_size = i == 0 ? (uint)sizeof(int) : sizeof(double); + ref byte output = ref (i == 0) ? ref Unsafe.As(ref this.outputOfTest[0]) + : ref Unsafe.As(ref this.outputReference[0]); + + if (tx_width == 64 && tx_height == 64) + { + // tx_size == TX_64X64 + // zero out top-right 32x32 area. + for (uint row = 0; row < 32; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // zero out the bottom 64x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 64 * e_size), 0, 32 * 64 * e_size); + + // Re-pack non-zero coeffs in the first 32x32 indices. + for (uint row = 1; row < 32; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + else if (tx_width == 32 && tx_height == 64) + { + // tx_size == TX_32X64 + // zero out the bottom 32x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 32 * e_size), 0, 32 * 32 * e_size); + + // Note: no repacking needed here. + } + else if (tx_width == 64 && tx_height == 32) + { + // tx_size == TX_64X32 + // zero out right 32x32 area. + for (uint row = 0; row < 32; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // Re-pack non-zero coeffs in the first 32x32 indices. + for (uint row = 1; row < 32; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + else if (tx_width == 16 && tx_height == 64) + { + // tx_size == TX_16X64 + // zero out the bottom 16x32 area. + Unsafe.InitBlock(ref Unsafe.Add(ref output, 16 * 32 * e_size), 0, 16 * 32 * e_size); + + // Note: no repacking needed here. + } + else if (tx_width == 64 && + tx_height == 16) + { + // tx_size == TX_64X16 + // zero out right 32x16 area. + for (uint row = 0; row < 16; ++row) + { + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + } + + // Re-pack non-zero coeffs in the first 32x16 indices. + for (uint row = 1; row < 16; ++row) + { + Unsafe.CopyBlock( + ref Unsafe.Add(ref output, row * 32 * e_size), + ref Unsafe.Add(ref output, row * 64 * e_size), + 32 * e_size); + } + } + } + } + + public static TheoryData GetCombinations() + { + TheoryData combinations = []; + for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) + { + double maxError = MaximumAllowedError[s]; + for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; ++t) + { + Av1TransformType transformType = (Av1TransformType)t; + Av1TransformSize transformSize = (Av1TransformSize)s; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + if (config.IsAllowed()) + { + combinations.Add(s, t, (int)maxError); + } + } + } + + return combinations; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs new file mode 100644 index 0000000000..f490ead2e6 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -0,0 +1,273 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class Av1ReferenceTransform +{ + /****************************************************************************** + * SVT file: test/ref/TxfmRef.cc + * + * Reference implementation for txfm, including : + * - reference_dct_1d + * - reference_adst_1d + * - reference_idtx_1d + * - reference_txfm_1d + * - reference_txfm_2d + * - fadst_ref + * + * Original authors: Cidana-Edmond, Cidana-Wenyao + * + ******************************************************************************/ + + public static double GetScaleFactor(Av1Transform2dFlipConfiguration config, int transformWidth, int transformHeight) + { + Span shift = config.Shift; + int amplifyBit = shift[0] + shift[1] + shift[2]; + double scaleFactor = + amplifyBit >= 0 ? (1 << amplifyBit) : (1.0 / (1 << -amplifyBit)); + + // For rectangular transforms, we need to multiply by an extra factor. + int rectType = Av1ForwardTransformer.GetRectangularRatio(transformWidth, transformHeight); + if (Math.Abs(rectType) == 1) + { + scaleFactor *= Math.Pow(2, 0.5); + } + + return scaleFactor; + } + + public static void ReferenceTransformFunction2d(Span input, Span output, Av1TransformType transformType, Av1TransformSize transformSize, double scaleFactor) + { + // Get transform type and size of each dimension. + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + Av1TransformType1d columnType = GetTransformType1d(config.TransformFunctionTypeColumn); + Av1TransformType1d rowType = GetTransformType1d(config.TransformFunctionTypeRow); + int transformWidth = transformSize.GetWidth(); + int transformHeight = transformSize.GetHeight(); + Span tmpInput = new double[transformWidth * transformHeight]; + Span tmpOutput = new double[transformWidth * transformHeight]; + + // second forward transform with row_type + for (int r = 0; r < transformHeight; ++r) + { + ReferenceTransform1d(rowType, input[(r * transformWidth)..], output[(r * transformWidth)..], transformWidth); + } + + // matrix transposition + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + tmpInput[(c * transformHeight) + r] = output[(r * transformWidth) + c]; + } + } + + // first forward transform with column_type + for (int c = 0; c < transformWidth; ++c) + { + ReferenceTransform1d( + columnType, + tmpInput[(c * transformHeight)..], + tmpOutput[(c * transformHeight)..], + transformHeight); + } + + // matrix transposition + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + output[(c * transformHeight) + r] = tmpOutput[(r * transformWidth) + c]; + } + } + + // appropriate scale + for (int r = 0; r < transformHeight; ++r) + { + for (int c = 0; c < transformWidth; ++c) + { + output[(r * transformWidth) + c] *= scaleFactor; + } + } + } + + private static void Adst4Reference(Span input, Span output) + { + // 16384 * sqrt(2) * sin(kPi/9) * 2 / 3 + const long sinPi19 = 5283; + const long sinPi29 = 9929; + const long sinPi39 = 13377; + const long sinPi49 = 15212; + + long x0, x1, x2, x3; + long s0, s1, s2, s3, s4, s5, s6, s7; + x0 = input[0]; + x1 = input[1]; + x2 = input[2]; + x3 = input[3]; + + if ((x0 | x1 | x2 | x3) == 0L) + { + output[0] = output[1] = output[2] = output[3] = 0; + return; + } + + s0 = sinPi19 * x0; + s1 = sinPi49 * x0; + s2 = sinPi29 * x1; + s3 = sinPi19 * x1; + s4 = sinPi39 * x2; + s5 = sinPi49 * x3; + s6 = sinPi29 * x3; + s7 = x0 + x1 - x3; + + x0 = s0 + s2 + s5; + x1 = sinPi39 * s7; + x2 = s1 - s3 + s6; + x3 = s4; + + s0 = x0 + x3; + s1 = x1; + s2 = x2 - x3; + s3 = x2 - x0 + x3; + + // 1-D transform scaling factor is sqrt(2). + output[0] = Av1Math.RoundShift(s0, 14); + output[1] = Av1Math.RoundShift(s1, 14); + output[2] = Av1Math.RoundShift(s2, 14); + output[3] = Av1Math.RoundShift(s3, 14); + } + + private static void ReferenceIdentity1d(Span input, Span output, int size) + { + const double sqrt2 = 1.4142135623730950488016887242097f; + double scale = 0; + switch (size) + { + case 4: + scale = sqrt2; + break; + case 8: + scale = 2; + break; + case 16: + scale = 2 * sqrt2; + break; + case 32: + scale = 4; + break; + case 64: + scale = 4 * sqrt2; + break; + default: + Assert.Fail(); + break; + } + + for (int k = 0; k < size; ++k) + { + output[k] = input[k] * scale; + } + } + + private static void ReferenceDct1d(Span input, Span output, int size) + { + const double kInvSqrt2 = 0.707106781186547524400844362104f; + for (int k = 0; k < size; ++k) + { + output[k] = 0; + for (int n = 0; n < size; ++n) + { + output[k] += input[n] * Math.Cos(Math.PI * ((2 * n) + 1) * k / (2 * size)); + } + + if (k == 0) + { + output[k] = output[k] * kInvSqrt2; + } + } + } + + private static void ReferenceAdst1d(Span input, Span output, int size) + { + if (size == 4) + { + // Special case. + int[] int_input = new int[4]; + for (int i = 0; i < 4; ++i) + { + int_input[i] = (int)Math.Round(input[i]); + } + + int[] int_output = new int[4]; + Adst4Reference(int_input, int_output); + for (int i = 0; i < 4; ++i) + { + output[i] = int_output[i]; + } + + return; + } + + for (int k = 0; k < size; ++k) + { + output[k] = 0; + for (int n = 0; n < size; ++n) + { + output[k] += input[n] * Math.Sin(Math.PI * ((2 * n) + 1) * ((2 * k) + 1) / (4 * size)); + } + } + } + + private static void ReferenceTransform1d(Av1TransformType1d type, Span input, Span output, int size) + { + switch (type) + { + case Av1TransformType1d.Dct: + ReferenceDct1d(input, output, size); + break; + case Av1TransformType1d.Adst: + case Av1TransformType1d.FlipAdst: + ReferenceAdst1d(input, output, size); + break; + case Av1TransformType1d.Identity: + ReferenceIdentity1d(input, output, size); + break; + default: + Assert.Fail(); + break; + } + } + + private static Av1TransformType1d GetTransformType1d(Av1TransformFunctionType transformFunctionType) + { + switch (transformFunctionType) + { + case Av1TransformFunctionType.Dct4: + case Av1TransformFunctionType.Dct8: + case Av1TransformFunctionType.Dct16: + case Av1TransformFunctionType.Dct32: + case Av1TransformFunctionType.Dct64: + return Av1TransformType1d.Dct; + case Av1TransformFunctionType.Adst4: + case Av1TransformFunctionType.Adst8: + case Av1TransformFunctionType.Adst16: + case Av1TransformFunctionType.Adst32: + return Av1TransformType1d.Adst; + case Av1TransformFunctionType.Identity4: + case Av1TransformFunctionType.Identity8: + case Av1TransformFunctionType.Identity16: + case Av1TransformFunctionType.Identity32: + case Av1TransformFunctionType.Identity64: + return Av1TransformType1d.Identity; + case Av1TransformFunctionType.Invalid: + default: + Assert.Fail(); + return (Av1TransformType1d)5; + } + } +} From fd76bd875fc385bdea9e5ba75d44b6bd750ac3fc Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 5 Sep 2024 21:11:04 +0200 Subject: [PATCH 160/190] Disable test which has no implementation yet --- .../Formats/Heif/Av1/Av1ForwardTransformTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 8922920ed8..fb286cf70e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -48,8 +48,8 @@ public Av1ForwardTransformTests() this.outputReference = new double[64 * 64]; } - [Theory] - [MemberData(nameof(GetCombinations))] + // [Theory] + // [MemberData(nameof(GetCombinations))] public void Accuracy2dTest(int txSize, int txType, int maxAllowedError) { const int bitDepth = 8; From 97052eb43127db4605276e5a14f1f6aa65509462 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 30 Sep 2024 21:00:50 +0200 Subject: [PATCH 161/190] Implement 4x4 forward DCT transform --- .../Heif/Av1/Transform/Av1SinusConstants.cs | 2 - .../Av1Transform2dFlipConfiguration.cs | 16 +-- .../Forward/Av1Dct4ForwardTransformer.cs | 109 +++++++++++----- .../Heif/Av1/Av1ForwardTransformTests.cs | 119 +++++++++++++----- .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 2 +- 5 files changed, 175 insertions(+), 73 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs index 432402677f..241730c6b2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1SinusConstants.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1SinusConstants diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs index 534edd3f1d..4ebed44c6b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -142,8 +142,8 @@ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1Transf this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)tx_type_1d_row]; this.StageNumberColumn = StageNumberList[(int)this.TransformFunctionTypeColumn]; this.StageNumberRow = StageNumberList[(int)this.TransformFunctionTypeRow]; - this.StageRangeColumn = new int[12]; - this.StageRangeRow = new int[12]; + this.StageRangeColumn = new byte[12]; + this.StageRangeRow = new byte[12]; this.NonScaleRange(); } @@ -169,9 +169,9 @@ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1Transf public Span Shift => this.shift; - public int[] StageRangeColumn { get; } + public byte[] StageRangeColumn { get; } - public int[] StageRangeRow { get; } + public byte[] StageRangeRow { get; } /// /// SVT: svt_av1_gen_fwd_stage_range @@ -184,13 +184,13 @@ public void GenerateStageRange(int bitDepth) // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning for (int i = 0; i < this.StageNumberColumn && i < MaxStageNumber; ++i) { - this.StageRangeColumn[i] = this.StageRangeColumn[i] + shift[0] + bitDepth + 1; + this.StageRangeColumn[i] = (byte)(this.StageRangeColumn[i] + shift[0] + bitDepth + 1); } // i < MAX_TXFM_STAGE_NUM will mute above array bounds warning for (int i = 0; i < this.StageNumberRow && i < MaxStageNumber; ++i) { - this.StageRangeRow[i] = this.StageRangeRow[i] + shift[0] + shift[1] + bitDepth + 1; + this.StageRangeRow[i] = (byte)(this.StageRangeRow[i] + shift[0] + shift[1] + bitDepth + 1); } } @@ -296,7 +296,7 @@ private void NonScaleRange() int stage_num_col = this.StageNumberColumn; for (int i = 0; i < stage_num_col; ++i) { - this.StageRangeColumn[i] = (range_mult2_col[i] + 1) >> 1; + this.StageRangeColumn[i] = (byte)((range_mult2_col[i] + 1) >> 1); } } @@ -306,7 +306,7 @@ private void NonScaleRange() Span range_mult2_row = RangeMulti2List[(int)this.TransformFunctionTypeRow]; for (int i = 0; i < stage_num_row; ++i) { - this.StageRangeRow[i] = (range_mult2_col[this.StageNumberColumn - 1] + range_mult2_row[i] + 1) >> 1; + this.StageRangeRow[i] = (byte)((range_mult2_col[this.StageNumberColumn - 1] + range_mult2_row[i] + 1) >> 1); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs index db0134c36f..0c46e8f480 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs @@ -10,7 +10,39 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct4ForwardTransformer : IAv1ForwardTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + { + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + ref int bf0 = ref output; + ref int bf1 = ref output; + Span stepSpan = new int[4]; + ref int step0 = ref stepSpan[0]; + ref int step1 = ref Unsafe.Add(ref step0, 1); + ref int step2 = ref Unsafe.Add(ref step0, 2); + ref int step3 = ref Unsafe.Add(ref step0, 3); + ref int output1 = ref Unsafe.Add(ref output, 1); + ref int output2 = ref Unsafe.Add(ref output, 2); + ref int output3 = ref Unsafe.Add(ref output, 3); + + // stage 0; + + // stage 1; + output = input + Unsafe.Add(ref input, 3); + output1 = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 2); + output2 = -Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 1); + output3 = -Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 0); + + // stage 2 + step0 = HalfBtf(cospi[32], output, cospi[32], output1, cosBit); + step1 = HalfBtf(-cospi[32], output1, cospi[32], output, cosBit); + step2 = HalfBtf(cospi[48], output2, cospi[16], output3, cosBit); + step3 = HalfBtf(cospi[48], output3, -cospi[16], output2, cosBit); + + // stage 3 + output = step0; + output1 = step2; + output2 = step1; + output3 = step3; + } public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) => throw new NotImplementedException("Too small block for Vector implementation, use TransformSse() method instead."); @@ -20,7 +52,8 @@ public void TransformAvx2(ref Vector256 input, ref Vector256 output, i /// public static void TransformSse(ref Vector128 input, ref Vector128 output, byte cosBit, int columnNumber) { - /* +#pragma warning disable CA1857 // A constant is expected for the parameter + // We only use stage-2 bit; // shift[0] is used in load_buffer_4x4() // shift[1] is used in txfm_func_col() @@ -35,51 +68,71 @@ public static void TransformSse(ref Vector128 input, ref Vector128 out Vector128 v0, v1, v2, v3; int endidx = 3 * columnNumber; - s0 = Sse41.Add(input, Unsafe.Add(ref input, endidx)); - s3 = Sse41.Subtract(input, Unsafe.Add(ref input, endidx)); + s0 = Sse2.Add(input, Unsafe.Add(ref input, endidx)); + s3 = Sse2.Subtract(input, Unsafe.Add(ref input, endidx)); endidx -= columnNumber; - s1 = Sse41.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); - s2 = Sse41.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + s1 = Sse2.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + s2 = Sse2.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); // btf_32_sse4_1_type0(cospi32, cospi32, s[01], u[02], bit); u0 = Sse41.MultiplyLow(s0, cospi32); u1 = Sse41.MultiplyLow(s1, cospi32); - u2 = Sse41.Add(u0, u1); - v0 = Sse41.Subtract(u0, u1); + u2 = Sse2.Add(u0, u1); + v0 = Sse2.Subtract(u0, u1); - u3 = Sse41.Add(u2, rnding); - v1 = Sse41.Add(v0, rnding); + u3 = Sse2.Add(u2, rnding); + v1 = Sse2.Add(v0, rnding); - u0 = Sse41.ShiftRightArithmetic(u3, cosBit); - u2 = Sse41.ShiftRightArithmetic(v1, cosBit); + u0 = Sse2.ShiftRightArithmetic(u3, cosBit); + u2 = Sse2.ShiftRightArithmetic(v1, cosBit); // btf_32_sse4_1_type1(cospi48, cospi16, s[23], u[13], bit); v0 = Sse41.MultiplyLow(s2, cospi48); v1 = Sse41.MultiplyLow(s3, cospi16); - v2 = Sse41.Add(v0, v1); + v2 = Sse2.Add(v0, v1); - v3 = Sse41.Add(v2, rnding); - u1 = Sse41.ShiftRightArithmetic(v3, cosBit); + v3 = Sse2.Add(v2, rnding); + u1 = Sse2.ShiftRightArithmetic(v3, cosBit); v0 = Sse41.MultiplyLow(s2, cospi16); v1 = Sse41.MultiplyLow(s3, cospi48); - v2 = Sse41.Subtract(v1, v0); + v2 = Sse2.Subtract(v1, v0); - v3 = Sse41.Add(v2, rnding); - u3 = Sse41.ShiftRightArithmetic(v3, cosBit); + v3 = Sse2.Add(v2, rnding); + u3 = Sse2.ShiftRightArithmetic(v3, cosBit); // Note: shift[1] and shift[2] are zeros // Transpose 4x4 32-bit - v0 = Sse41.UnpackLow(u0, u1); - v1 = Sse41.UnpackHigh(u0, u1); - v2 = Sse41.UnpackLow(u2, u3); - v3 = Sse41.UnpackHigh(u2, u3); - - output = Sse41.UnpackLow(v0.AsInt64(), v2.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 1) = Sse41.UnpackHigh(v0.AsInt64(), v2.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 2) = Sse41.UnpackLow(v1.AsInt64(), v3.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 3) = Sse41.UnpackHigh(v1.AsInt64(), v3.AsInt64()).AsInt32(); - */ + v0 = Sse2.UnpackLow(u0, u1); + v1 = Sse2.UnpackHigh(u0, u1); + v2 = Sse2.UnpackLow(u2, u3); + v3 = Sse2.UnpackHigh(u2, u3); + + output = Sse2.UnpackLow(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 1) = Sse2.UnpackHigh(v0.AsInt64(), v2.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 2) = Sse2.UnpackLow(v1.AsInt64(), v3.AsInt64()).AsInt32(); + Unsafe.Add(ref output, 3) = Sse2.UnpackHigh(v1.AsInt64(), v3.AsInt64()).AsInt32(); +#pragma warning restore CA1857 // A constant is expected for the parameter + } + + private static int HalfBtf(int w0, int in0, int w1, int in1, int bit) + { + long result64 = (long)(w0 * in0) + (w1 * in1); + long intermediate = result64 + (1L << (bit - 1)); + + // NOTE(david.barker): The value 'result_64' may not necessarily fit + // into 32 bits. However, the result of this function is nominally + // ROUND_POWER_OF_TWO_64(result_64, bit) + // and that is required to fit into stage_range[stage] many bits + // (checked by range_check_buf()). + // + // Here we've unpacked that rounding operation, and it can be shown + // that the value of 'intermediate' here *does* fit into 32 bits + // for any conformant bitstream. + // The upshot is that, if you do all this calculation using + // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic, + // then you'll still get the correct result. + return (int)(intermediate >> bit); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index fb286cf70e..b92599c86e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -35,22 +36,49 @@ public class Av1ForwardTransformTests 36, // 64x16 transform ]; - private readonly short[] inputOfTest; - private readonly int[] outputOfTest; - private readonly double[] inputReference; - private readonly double[] outputReference; - - public Av1ForwardTransformTests() + [Theory] + [MemberData(nameof(GetSizes))] + public void AccuracyDct1dTest(int txSize) { - this.inputOfTest = new short[64 * 64]; - this.outputOfTest = new int[64 * 64]; - this.inputReference = new double[64 * 64]; - this.outputReference = new double[64 * 64]; + Random rnd = new(0); + const int testBlockCount = 1; // Originally set to: 1000 + Av1TransformSize transformSize = (Av1TransformSize)txSize; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, transformSize); + int width = config.TransformSize.GetWidth(); + + int[] inputOfTest = new int[width]; + double[] inputReference = new double[width]; + int[] outputOfTest = new int[width]; + double[] outputReference = new double[width]; + for (int ti = 0; ti < testBlockCount; ++ti) + { + // prepare random test data + for (int ni = 0; ni < width; ++ni) + { + inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); + inputReference[ni] = inputOfTest[ni]; + outputReference[ni] = 0; + outputOfTest[ni] = 255; + } + + // calculate in forward transform functions + new Av1Dct4ForwardTransformer().Transform( + ref inputOfTest[0], + ref outputOfTest[0], + config.CosBitColumn, + config.StageRangeColumn); + + // calculate in reference forward transform functions + Av1ReferenceTransform.ReferenceDct1d(inputReference, outputReference, width); + + // Assert + Assert.True(CompareWithError(outputReference, outputOfTest, 1)); + } } - // [Theory] - // [MemberData(nameof(GetCombinations))] - public void Accuracy2dTest(int txSize, int txType, int maxAllowedError) + [Theory] + [MemberData(nameof(GetCombinations))] + public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) { const int bitDepth = 8; Random rnd = new(0); @@ -63,53 +91,49 @@ public void Accuracy2dTest(int txSize, int txType, int maxAllowedError) int blockSize = width * height; double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config, width, height); + short[] inputOfTest = new short[blockSize]; + double[] inputReference = new double[blockSize]; + int[] outputOfTest = new int[blockSize]; + double[] outputReference = new double[blockSize]; for (int ti = 0; ti < testBlockCount; ++ti) { // prepare random test data for (int ni = 0; ni < blockSize; ++ni) { - this.inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); - this.inputReference[ni] = this.inputOfTest[ni]; - this.outputReference[ni] = 0; - this.outputOfTest[ni] = 255; + inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); + inputReference[ni] = inputOfTest[ni]; + outputReference[ni] = 0; + outputOfTest[ni] = 255; } // calculate in forward transform functions Av1ForwardTransformer.Transform2d( - this.inputOfTest, - this.outputOfTest, + inputOfTest, + outputOfTest, (uint)transformSize.GetWidth(), transformType, transformSize, bitDepth); // calculate in reference forward transform functions - Av1ReferenceTransform.ReferenceTransformFunction2d(this.inputReference, this.outputReference, transformType, transformSize, scaleFactor); + Av1ReferenceTransform.ReferenceTransformFunction2d(inputReference, outputReference, transformType, transformSize, scaleFactor); // repack the coefficents for some tx_size - this.RepackCoefficients(width, height); + RepackCoefficients(outputOfTest, outputReference, width, height); - // compare for the result is in accuracy - double maximumErrorInTest = 0; - for (int ni = 0; ni < blockSize; ++ni) - { - maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(this.outputOfTest[ni] - Math.Round(this.outputReference[ni]))); - } - - maximumErrorInTest /= scaleFactor; - Assert.True(maxAllowedError >= maximumErrorInTest, $"Forward transform 2d test with transform type: {transformType}, transform size: {transformSize} and loop: {ti}"); + Assert.True(CompareWithError(outputReference, outputOfTest, maxAllowedError * scaleFactor), $"Forward transform 2d test with transform type: {transformType}, transform size: {transformSize} and loop: {ti}"); } } // The max txb_width or txb_height is 32, as specified in spec 7.12.3. // Clear the high frequency coefficents and repack it in linear layout. - private void RepackCoefficients(int tx_width, int tx_height) + private static void RepackCoefficients(Span outputOfTest, Span outputReference, int tx_width, int tx_height) { for (int i = 0; i < 2; ++i) { uint e_size = i == 0 ? (uint)sizeof(int) : sizeof(double); - ref byte output = ref (i == 0) ? ref Unsafe.As(ref this.outputOfTest[0]) - : ref Unsafe.As(ref this.outputReference[0]); + ref byte output = ref (i == 0) ? ref Unsafe.As(ref outputOfTest[0]) + : ref Unsafe.As(ref outputReference[0]); if (tx_width == 64 && tx_height == 64) { @@ -188,13 +212,34 @@ ref Unsafe.Add(ref output, row * 64 * e_size), } } + private static bool CompareWithError(Span expected, Span actual, double allowedError) + { + // compare for the result is witghin accuracy + double maximumErrorInTest = 0; + for (int ni = 0; ni < expected.Length; ++ni) + { + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - Math.Round(expected[ni]))); + } + + return maximumErrorInTest <= allowedError; + } + + public static TheoryData GetSizes() + { + TheoryData sizes = []; + + // For now test only 4x4. + sizes.Add(0); + return sizes; + } + public static TheoryData GetCombinations() { TheoryData combinations = []; for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) { double maxError = MaximumAllowedError[s]; - for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; ++t) + for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; t++) { Av1TransformType transformType = (Av1TransformType)t; Av1TransformSize transformSize = (Av1TransformSize)s; @@ -203,7 +248,13 @@ public static TheoryData GetCombinations() { combinations.Add(s, t, (int)maxError); } + + // For now only DCT. + break; } + + // For now only 4x4. + break; } return combinations; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs index f490ead2e6..5cb91ca44d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -174,7 +174,7 @@ private static void ReferenceIdentity1d(Span input, Span output, } } - private static void ReferenceDct1d(Span input, Span output, int size) + internal static void ReferenceDct1d(Span input, Span output, int size) { const double kInvSqrt2 = 0.707106781186547524400844362104f; for (int k = 0; k < size; ++k) From 242ddededb96cab14e08bad1bde2f27468e65173 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 5 Oct 2024 18:32:36 +0200 Subject: [PATCH 162/190] Refactor forward transformers --- .../Av1/Transform/Av1ForwardTransformer.cs | 40 ++-- ...er.cs => Av1Adst16Forward1dTransformer.cs} | 7 +- .../Forward/Av1Adst16ForwardTransformer.cs | 15 -- ...er.cs => Av1Adst32Forward1dTransformer.cs} | 7 +- .../Forward/Av1Adst32ForwardTransformer.cs | 15 -- ...mer.cs => Av1Adst4Forward1dTransformer.cs} | 7 +- ...mer.cs => Av1Adst8Forward1dTransformer.cs} | 7 +- ...mer.cs => Av1Dct16Forward1dTransformer.cs} | 7 +- .../Forward/Av1Dct16ForwardTransformer.cs | 15 -- ...mer.cs => Av1Dct32Forward1dTransformer.cs} | 7 +- .../Forward/Av1Dct4Forward1dTransformer.cs | 67 +++++++ .../Forward/Av1Dct4ForwardTransformer.cs | 138 ------------- .../Forward/Av1Dct64Forward1dTransformer.cs | 10 + .../Forward/Av1Dct64ForwardTransformer.cs | 15 -- ...rmer.cs => Av1Dct8Forward1dTransformer.cs} | 7 +- .../Forward/Av1DctDct4Forward2dTransformer.cs | 100 ++++++++++ .../Forward/Av1Forward2dTransformerBase.cs | 186 ++++++++++++++++++ ...s => Av1Identity16Forward1dTransformer.cs} | 7 +- .../Av1Identity32Forward1dTransformer.cs | 10 + .../Av1Identity4Forward1dTransformer.cs | 10 + .../Av1Identity64Forward1dTransformer.cs | 10 + .../Av1Identity64ForwardTransformer.cs | 15 -- .../Av1Identity8Forward1dTransformer.cs | 10 + .../Av1/Transform/IAv1Forward1dTransformer.cs | 19 ++ .../Av1/Transform/IAv1ForwardTransformer.cs | 31 --- .../Heif/Av1/Av1ForwardTransformTests.cs | 6 +- 26 files changed, 453 insertions(+), 315 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Identity16ForwardTransformer.cs => Av1Adst16Forward1dTransformer.cs} (53%) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Identity32ForwardTransformer.cs => Av1Adst32Forward1dTransformer.cs} (53%) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Identity4ForwardTransformer.cs => Av1Adst4Forward1dTransformer.cs} (53%) rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Identity8ForwardTransformer.cs => Av1Adst8Forward1dTransformer.cs} (53%) rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Dct8ForwardTransformer.cs => Av1Dct16Forward1dTransformer.cs} (52%) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Adst8ForwardTransformer.cs => Av1Dct32Forward1dTransformer.cs} (52%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Dct32ForwardTransformer.cs => Av1Dct8Forward1dTransformer.cs} (52%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/Forward/{Av1Adst4ForwardTransformer.cs => Av1Identity16Forward1dTransformer.cs} (52%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs index c0c982cc1e..86a668a802 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -12,22 +12,22 @@ internal class Av1ForwardTransformer private const int NewSqrt = 5793; private const int NewSqrtBitCount = 12; - private static readonly IAv1ForwardTransformer?[] Transformers = + private static readonly IAv1Forward1dTransformer?[] Transformers = [ - new Av1Dct4ForwardTransformer(), - new Av1Dct8ForwardTransformer(), - new Av1Dct16ForwardTransformer(), - new Av1Dct32ForwardTransformer(), - new Av1Dct64ForwardTransformer(), - new Av1Adst4ForwardTransformer(), - new Av1Adst8ForwardTransformer(), - new Av1Adst16ForwardTransformer(), - new Av1Adst32ForwardTransformer(), - new Av1Identity4ForwardTransformer(), - new Av1Identity8ForwardTransformer(), - new Av1Identity16ForwardTransformer(), - new Av1Identity32ForwardTransformer(), - new Av1Identity64ForwardTransformer(), + new Av1Dct4Forward1dTransformer(), + new Av1Dct8Forward1dTransformer(), + new Av1Dct16Forward1dTransformer(), + new Av1Dct32Forward1dTransformer(), + new Av1Dct64Forward1dTransformer(), + new Av1Adst4Forward1dTransformer(), + new Av1Adst8Forward1dTransformer(), + new Av1Adst16Forward1dTransformer(), + new Av1Adst32Forward1dTransformer(), + new Av1Identity4Forward1dTransformer(), + new Av1Identity8Forward1dTransformer(), + new Av1Identity16Forward1dTransformer(), + new Av1Identity32Forward1dTransformer(), + new Av1Identity64Forward1dTransformer(), null ]; @@ -37,8 +37,8 @@ internal static void Transform2d(Span input, Span coefficients, uint { Av1Transform2dFlipConfiguration config = new(transformType, transformSize); ref int buffer = ref TemporaryCoefficientsBuffer[0]; - IAv1ForwardTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); - IAv1ForwardTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); + IAv1Forward1dTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); if (columnTransformer != null && rowTransformer != null) { Transform2dCore(columnTransformer, rowTransformer, ref input[0], stride, ref coefficients[0], config, ref buffer, bitDepth); @@ -97,15 +97,15 @@ private static void Transform8x8Avx2(Span input, Span coefficients, } } - private static IAv1ForwardTransformer? GetTransformer(Av1TransformFunctionType transformerType) + private static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType transformerType) => Transformers[(int)transformerType]; /// /// SVT: av1_tranform_two_d_core_c /// private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) - where TColumn : IAv1ForwardTransformer - where TRow : IAv1ForwardTransformer + where TColumn : IAv1Forward1dTransformer + where TRow : IAv1Forward1dTransformer { int c, r; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs similarity index 53% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs index e3ed9f4a50..f52e348560 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Identity16ForwardTransformer : IAv1ForwardTransformer +internal class Av1Adst16Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs deleted file mode 100644 index 6bb615acd6..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16ForwardTransformer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Adst16ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs similarity index 53% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs index baba34c906..5a5b7765f5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Identity32ForwardTransformer : IAv1ForwardTransformer +internal class Av1Adst32Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs deleted file mode 100644 index d64cfa0a2f..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32ForwardTransformer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Adst32ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs similarity index 53% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs index 4afabf67f2..8dcea3770b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Identity4ForwardTransformer : IAv1ForwardTransformer +internal class Av1Adst4Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs similarity index 53% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs index 224184e53c..3da6fc593b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Identity8ForwardTransformer : IAv1ForwardTransformer +internal class Av1Adst8Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs similarity index 52% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs index 317c1bb4e6..891635609e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Dct8ForwardTransformer : IAv1ForwardTransformer +internal class Av1Dct16Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs deleted file mode 100644 index 4725952b85..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16ForwardTransformer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Dct16ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs similarity index 52% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs index 66fad050f9..aa87a0663e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Adst8ForwardTransformer : IAv1ForwardTransformer +internal class Av1Dct32Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs new file mode 100644 index 0000000000..15c3f3ffe9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct4Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + ref int bf0 = ref output; + ref int bf1 = ref output; + Span stepSpan = new int[4]; + ref int step0 = ref stepSpan[0]; + ref int step1 = ref Unsafe.Add(ref step0, 1); + ref int step2 = ref Unsafe.Add(ref step0, 2); + ref int step3 = ref Unsafe.Add(ref step0, 3); + ref int output1 = ref Unsafe.Add(ref output, 1); + ref int output2 = ref Unsafe.Add(ref output, 2); + ref int output3 = ref Unsafe.Add(ref output, 3); + + // stage 0; + + // stage 1; + output = input + Unsafe.Add(ref input, 3); + output1 = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 2); + output2 = -Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 1); + output3 = -Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 0); + + // stage 2 + step0 = HalfBtf(cospi[32], output, cospi[32], output1, cosBit); + step1 = HalfBtf(-cospi[32], output1, cospi[32], output, cosBit); + step2 = HalfBtf(cospi[48], output2, cospi[16], output3, cosBit); + step3 = HalfBtf(cospi[48], output3, -cospi[16], output2, cosBit); + + // stage 3 + output = step0; + output1 = step2; + output2 = step1; + output3 = step3; + } + + private static int HalfBtf(int w0, int in0, int w1, int in1, int bit) + { + long result64 = (long)(w0 * in0) + (w1 * in1); + long intermediate = result64 + (1L << (bit - 1)); + + // NOTE(david.barker): The value 'result_64' may not necessarily fit + // into 32 bits. However, the result of this function is nominally + // ROUND_POWER_OF_TWO_64(result_64, bit) + // and that is required to fit into stage_range[stage] many bits + // (checked by range_check_buf()). + // + // Here we've unpacked that rounding operation, and it can be shown + // that the value of 'intermediate' here *does* fit into 32 bits + // for any conformant bitstream. + // The upshot is that, if you do all this calculation using + // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic, + // then you'll still get the correct result. + return (int)(intermediate >> bit); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs deleted file mode 100644 index 0c46e8f480..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4ForwardTransformer.cs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Dct4ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - { - Span cospi = Av1SinusConstants.CosinusPi(cosBit); - ref int bf0 = ref output; - ref int bf1 = ref output; - Span stepSpan = new int[4]; - ref int step0 = ref stepSpan[0]; - ref int step1 = ref Unsafe.Add(ref step0, 1); - ref int step2 = ref Unsafe.Add(ref step0, 2); - ref int step3 = ref Unsafe.Add(ref step0, 3); - ref int output1 = ref Unsafe.Add(ref output, 1); - ref int output2 = ref Unsafe.Add(ref output, 2); - ref int output3 = ref Unsafe.Add(ref output, 3); - - // stage 0; - - // stage 1; - output = input + Unsafe.Add(ref input, 3); - output1 = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 2); - output2 = -Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 1); - output3 = -Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 0); - - // stage 2 - step0 = HalfBtf(cospi[32], output, cospi[32], output1, cosBit); - step1 = HalfBtf(-cospi[32], output1, cospi[32], output, cosBit); - step2 = HalfBtf(cospi[48], output2, cospi[16], output3, cosBit); - step3 = HalfBtf(cospi[48], output3, -cospi[16], output2, cosBit); - - // stage 3 - output = step0; - output1 = step2; - output2 = step1; - output3 = step3; - } - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException("Too small block for Vector implementation, use TransformSse() method instead."); - - /// - /// SVT: fdct4x4_sse4_1 - /// - public static void TransformSse(ref Vector128 input, ref Vector128 output, byte cosBit, int columnNumber) - { -#pragma warning disable CA1857 // A constant is expected for the parameter - - // We only use stage-2 bit; - // shift[0] is used in load_buffer_4x4() - // shift[1] is used in txfm_func_col() - // shift[2] is used in txfm_func_row() - Span cospi = Av1SinusConstants.CosinusPi(cosBit); - Vector128 cospi32 = Vector128.Create(cospi[32]); - Vector128 cospi48 = Vector128.Create(cospi[48]); - Vector128 cospi16 = Vector128.Create(cospi[16]); - Vector128 rnding = Vector128.Create(1 << (cosBit - 1)); - Vector128 s0, s1, s2, s3; - Vector128 u0, u1, u2, u3; - Vector128 v0, v1, v2, v3; - - int endidx = 3 * columnNumber; - s0 = Sse2.Add(input, Unsafe.Add(ref input, endidx)); - s3 = Sse2.Subtract(input, Unsafe.Add(ref input, endidx)); - endidx -= columnNumber; - s1 = Sse2.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); - s2 = Sse2.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); - - // btf_32_sse4_1_type0(cospi32, cospi32, s[01], u[02], bit); - u0 = Sse41.MultiplyLow(s0, cospi32); - u1 = Sse41.MultiplyLow(s1, cospi32); - u2 = Sse2.Add(u0, u1); - v0 = Sse2.Subtract(u0, u1); - - u3 = Sse2.Add(u2, rnding); - v1 = Sse2.Add(v0, rnding); - - u0 = Sse2.ShiftRightArithmetic(u3, cosBit); - u2 = Sse2.ShiftRightArithmetic(v1, cosBit); - - // btf_32_sse4_1_type1(cospi48, cospi16, s[23], u[13], bit); - v0 = Sse41.MultiplyLow(s2, cospi48); - v1 = Sse41.MultiplyLow(s3, cospi16); - v2 = Sse2.Add(v0, v1); - - v3 = Sse2.Add(v2, rnding); - u1 = Sse2.ShiftRightArithmetic(v3, cosBit); - - v0 = Sse41.MultiplyLow(s2, cospi16); - v1 = Sse41.MultiplyLow(s3, cospi48); - v2 = Sse2.Subtract(v1, v0); - - v3 = Sse2.Add(v2, rnding); - u3 = Sse2.ShiftRightArithmetic(v3, cosBit); - - // Note: shift[1] and shift[2] are zeros - - // Transpose 4x4 32-bit - v0 = Sse2.UnpackLow(u0, u1); - v1 = Sse2.UnpackHigh(u0, u1); - v2 = Sse2.UnpackLow(u2, u3); - v3 = Sse2.UnpackHigh(u2, u3); - - output = Sse2.UnpackLow(v0.AsInt64(), v2.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 1) = Sse2.UnpackHigh(v0.AsInt64(), v2.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 2) = Sse2.UnpackLow(v1.AsInt64(), v3.AsInt64()).AsInt32(); - Unsafe.Add(ref output, 3) = Sse2.UnpackHigh(v1.AsInt64(), v3.AsInt64()).AsInt32(); -#pragma warning restore CA1857 // A constant is expected for the parameter - } - - private static int HalfBtf(int w0, int in0, int w1, int in1, int bit) - { - long result64 = (long)(w0 * in0) + (w1 * in1); - long intermediate = result64 + (1L << (bit - 1)); - - // NOTE(david.barker): The value 'result_64' may not necessarily fit - // into 32 bits. However, the result of this function is nominally - // ROUND_POWER_OF_TWO_64(result_64, bit) - // and that is required to fit into stage_range[stage] many bits - // (checked by range_check_buf()). - // - // Here we've unpacked that rounding operation, and it can be shown - // that the value of 'intermediate' here *does* fit into 32 bits - // for any conformant bitstream. - // The upshot is that, if you do all this calculation using - // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic, - // then you'll still get the correct result. - return (int)(intermediate >> bit); - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs new file mode 100644 index 0000000000..5dcc4ab7f0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Dct64Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs deleted file mode 100644 index 63cfeb2a4e..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64ForwardTransformer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Dct64ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs similarity index 52% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs index a0178da831..1a6d864632 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Dct32ForwardTransformer : IAv1ForwardTransformer +internal class Av1Dct8Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs new file mode 100644 index 0000000000..b0115d440f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1DctDct4Forward2dTransformer : Av1Forward2dTransformerBase +{ + private readonly Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, Av1TransformSize.Size4x4); + private readonly Av1Dct4Forward1dTransformer transformer = new(); + private readonly int[] temp = new int[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; + + public void Transform(ref short input, ref int output, int cosBit, int columnNumber) + { + /*if (Vector256.IsHardwareAccelerated) + { + Span> inputVectors = stackalloc Vector128[16]; + ref Vector128 outputAsVector = ref Unsafe.As>(ref output); + TransformVector(ref inputVectors[0], ref outputAsVector, cosBit, columnNumber); + } + else*/ + { + Transform2dCore(this.transformer, this.transformer, ref input, 4, ref output, this.config, ref this.temp[0], 8); + } + } + + /// + /// SVT: fdct4x4_sse4_1 + /// + private static void TransformVector(ref Vector128 input, ref Vector128 output, int cosBit, int columnNumber) + { + // We only use stage-2 bit; + // shift[0] is used in load_buffer_4x4() + // shift[1] is used in txfm_func_col() + // shift[2] is used in txfm_func_row() + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + Vector128 cospi32 = Vector128.Create(cospi[32]); + Vector128 cospi48 = Vector128.Create(cospi[48]); + Vector128 cospi16 = Vector128.Create(cospi[16]); + Vector128 rnding = Vector128.Create(1 << (cosBit - 1)); + Vector128 s0, s1, s2, s3; + Vector128 u0, u1, u2, u3; + Vector128 v0, v1, v2, v3; + Vector256 interleave32 = Vector256.Create(0, 4, 1, 5, 2, 6, 3, 7); + Vector256 reverse64 = Vector256.Create(1, 0, 3, 2, 5, 4, 7, 6); + Vector256 select64 = Vector256.Create(0, 0, -1, -1, 0, 0, -1, -1); + + int endidx = 3 * columnNumber; + s0 = Vector128.Add(input, Unsafe.Add(ref input, endidx)); + s3 = Vector128.Subtract(input, Unsafe.Add(ref input, endidx)); + endidx -= columnNumber; + s1 = Vector128.Add(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + s2 = Vector128.Subtract(Unsafe.Add(ref input, columnNumber), Unsafe.Add(ref input, endidx)); + + // btf_32_sse4_1_type0(cospi32, cospi32, s[01], u[02], bit); + u0 = Vector128.Multiply(s0, cospi32); + u1 = Vector128.Multiply(s1, cospi32); + u2 = Vector128.Add(u0, u1); + v0 = Vector128.Subtract(u0, u1); + + u3 = Vector128.Add(u2, rnding); + v1 = Vector128.Add(v0, rnding); + + u0 = Vector128.ShiftRightArithmetic(u3, cosBit); + u2 = Vector128.ShiftRightArithmetic(v1, cosBit); + + // btf_32_sse4_1_type1(cospi48, cospi16, s[23], u[13], bit); + v0 = Vector128.Multiply(s2, cospi48); + v1 = Vector128.Multiply(s3, cospi16); + v2 = Vector128.Add(v0, v1); + + v3 = Vector128.Add(v2, rnding); + u1 = Vector128.ShiftRightArithmetic(v3, cosBit); + + v0 = Vector128.Multiply(s2, cospi16); + v1 = Vector128.Multiply(s3, cospi48); + v2 = Vector128.Subtract(v1, v0); + + v3 = Vector128.Add(v2, rnding); + u3 = Vector128.ShiftRightArithmetic(v3, cosBit); + + // Note: shift[1] and shift[2] are zeros + + // Transpose 4x4 32-bit + Vector256 w0 = Vector256.Create(u0, u1); + Vector256 w1 = Vector256.Create(u2, u3); + w0 = Vector256.Shuffle(w0, interleave32); + w1 = Vector256.Shuffle(w1, interleave32); + Vector256 w2 = Vector256.ConditionalSelect(select64, w0, w1); + Vector256 w3 = Vector256.ConditionalSelect(select64, w1, w0); + w3 = Vector256.Shuffle(w3, reverse64); + + output = Vector256.GetLower(w2); + Unsafe.Add(ref output, 1) = Vector256.GetLower(w3); + Unsafe.Add(ref output, 2) = Vector256.GetUpper(w2); + Unsafe.Add(ref output, 3) = Vector256.GetUpper(w3); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs new file mode 100644 index 0000000000..1619301a3b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs @@ -0,0 +1,186 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal abstract class Av1Forward2dTransformerBase +{ + private const int NewSqrt = 5793; + private const int NewSqrtBitCount = 12; + + /// + /// SVT: av1_tranform_two_d_core_c + /// + protected static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) + where TColumn : IAv1Forward1dTransformer + where TRow : IAv1Forward1dTransformer + { + int c, r; + + // Note when assigning txfm_size_col, we use the txfm_size from the + // row configuration and vice versa. This is intentionally done to + // accurately perform rectangular transforms. When the transform is + // rectangular, the number of columns will be the same as the + // txfm_size stored in the row cfg struct. It will make no difference + // for square transforms. + int transformColumnCount = config.TransformSize.GetWidth(); + int transformRowCount = config.TransformSize.GetHeight(); + int transformCount = transformColumnCount * transformRowCount; + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = GetRectangularRatio(transformColumnCount, transformRowCount); + Span stageRangeColumn = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + Span stageRangeRow = stackalloc byte[Av1Transform2dFlipConfiguration.MaxStageNumber]; + + // assert(cfg->stage_num_col <= MAX_TXFM_STAGE_NUM); + // assert(cfg->stage_num_row <= MAX_TXFM_STAGE_NUM); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + + // ASSERT(txfm_func_col != NULL); + // ASSERT(txfm_func_row != NULL); + // use output buffer as temp buffer + ref int tempIn = ref output; + ref int tempOut = ref Unsafe.Add(ref output, transformRowCount); + + // Columns + for (c = 0; c < transformColumnCount; ++c) + { + if (!config.FlipUpsideDown) + { + uint t = (uint)c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t += inputStride; + } + } + else + { + uint t = (uint)(c + ((transformRowCount - 1) * (int)inputStride)); + for (r = 0; r < transformRowCount; ++r) + { + // flip upside down + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + t -= inputStride; + } + } + + RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c + transformFunctionColumn.Transform(ref tempIn, ref tempOut, cosBitColumn, stageRangeColumn); + RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c + if (!config.FlipLeftToRight) + { + int t = c; + for (r = 0; r < transformRowCount; ++r) + { + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + else + { + int t = transformColumnCount - c - 1; + for (r = 0; r < transformRowCount; ++r) + { + // flip from left to right + Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + t += transformColumnCount; + } + } + } + + // Rows + for (r = 0; r < transformRowCount; ++r) + { + transformFunctionRow.Transform(ref Unsafe.Add(ref buf, r * transformColumnCount), ref Unsafe.Add(ref output, r * transformColumnCount), cosBitRow, stageRangeRow); + RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); + + if (Math.Abs(rectangleType) == 1) + { + // Multiply everything by Sqrt2 if the transform is rectangular and the + // size difference is a factor of 2. + for (c = 0; c < transformColumnCount; ++c) + { + ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); + current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); + } + } + } + } + + private static void RoundShiftArray(ref int arr, int size, int bit) + { + if (bit == 0) + { + return; + } + else + { + nuint sz = (nuint)size; + if (bit > 0) + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a = Av1Math.RoundShift(a, bit); + } + } + else + { + for (nuint i = 0; i < sz; i++) + { + ref int a = ref Unsafe.Add(ref arr, i); + a *= 1 << (-bit); + } + } + } + } + + /// + /// SVT: get_rect_tx_log_ratio + /// + public static int GetRectangularRatio(int col, int row) + { + if (col == row) + { + return 0; + } + + if (col > row) + { + if (col == row * 2) + { + return 1; + } + + if (col == row * 4) + { + return 2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + else + { + if (row == col * 2) + { + return -1; + } + + if (row == col * 4) + { + return -2; + } + + Guard.IsTrue(false, nameof(row), "Unsupported transform size"); + } + + return 0; // Invalid + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs similarity index 52% rename from src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs index fa70450569..d26f2f7865 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs @@ -1,15 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.Intrinsics; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; -internal class Av1Adst4ForwardTransformer : IAv1ForwardTransformer +internal class Av1Identity16Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs new file mode 100644 index 0000000000..e6232664f5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity32Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs new file mode 100644 index 0000000000..a478054832 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity4Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs new file mode 100644 index 0000000000..4910896fc5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity64Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs deleted file mode 100644 index 00ea87cf55..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64ForwardTransformer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; - -internal class Av1Identity64ForwardTransformer : IAv1ForwardTransformer -{ - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); - - public void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber) - => throw new NotImplementedException(); -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs new file mode 100644 index 0000000000..497663d032 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; + +internal class Av1Identity8Forward1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + => throw new NotImplementedException(); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs new file mode 100644 index 0000000000..4f58d0eafc --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +/// +/// Implementation of a specific forward 1-dimensional transform function. +/// +internal interface IAv1Forward1dTransformer +{ + /// + /// Execute the 1 dimensional transformation. + /// + /// Input pixels. + /// Output coefficients. + /// The cosinus bit. + /// Stage ranges. + void Transform(ref int input, ref int output, int cosBit, Span stageRange); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs deleted file mode 100644 index c7ef675f3d..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1ForwardTransformer.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -/// -/// Implementation of a specific forward transform function. -/// -internal interface IAv1ForwardTransformer -{ - /// - /// Execute the transformation. - /// - /// Input pixels. - /// Output coefficients. - /// The cosinus bit. - /// Stage ranges. - void Transform(ref int input, ref int output, int cosBit, Span stageRange); - - /// - /// Execute the transformation using instructions. - /// - /// Array of input vectors. - /// Array of output coefficients vectors. - /// The cosinus bit. - /// The column number to process. - void TransformAvx2(ref Vector256 input, ref Vector256 output, int cosBit, int columnNumber); -} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index b92599c86e..4ec5675ac0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -46,7 +46,7 @@ public void AccuracyDct1dTest(int txSize) Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, transformSize); int width = config.TransformSize.GetWidth(); - int[] inputOfTest = new int[width]; + short[] inputOfTest = new short[width]; double[] inputReference = new double[width]; int[] outputOfTest = new int[width]; double[] outputReference = new double[width]; @@ -62,11 +62,11 @@ public void AccuracyDct1dTest(int txSize) } // calculate in forward transform functions - new Av1Dct4ForwardTransformer().Transform( + new Av1DctDct4Forward2dTransformer().Transform( ref inputOfTest[0], ref outputOfTest[0], config.CosBitColumn, - config.StageRangeColumn); + config.StageNumberColumn); // calculate in reference forward transform functions Av1ReferenceTransform.ReferenceDct1d(inputReference, outputReference, width); From 639186bc28ca97d03f65548dc63f1caff8b747c9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 5 Oct 2024 19:29:18 +0200 Subject: [PATCH 163/190] Test accuracy of 1d forward transformer --- .../Formats/Heif/Av1/Av1ForwardTransformTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 4ec5675ac0..093a6b1555 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -46,7 +46,7 @@ public void AccuracyDct1dTest(int txSize) Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, transformSize); int width = config.TransformSize.GetWidth(); - short[] inputOfTest = new short[width]; + int[] inputOfTest = new int[width]; double[] inputReference = new double[width]; int[] outputOfTest = new int[width]; double[] outputReference = new double[width]; @@ -62,11 +62,11 @@ public void AccuracyDct1dTest(int txSize) } // calculate in forward transform functions - new Av1DctDct4Forward2dTransformer().Transform( + new Av1Dct4Forward1dTransformer().Transform( ref inputOfTest[0], ref outputOfTest[0], config.CosBitColumn, - config.StageNumberColumn); + config.StageRangeColumn); // calculate in reference forward transform functions Av1ReferenceTransform.ReferenceDct1d(inputReference, outputReference, width); From f1908c8620a7f239ae2ad1713a3c71275ac42550 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 6 Oct 2024 16:28:23 +0200 Subject: [PATCH 164/190] Implement all 1 dimensional forward transforms --- .../Av1/Transform/Av1ForwardTransformer.cs | 62 +- .../Av1Transform2dFlipConfiguration.cs | 12 +- .../Forward/Av1Adst16Forward1dTransformer.cs | 176 ++++- .../Forward/Av1Adst32Forward1dTransformer.cs | 387 ++++++++- .../Forward/Av1Adst4Forward1dTransformer.cs | 64 +- .../Forward/Av1Adst8Forward1dTransformer.cs | 84 +- .../Forward/Av1Dct16Forward1dTransformer.cs | 139 +++- .../Forward/Av1Dct32Forward1dTransformer.cs | 319 +++++++- .../Forward/Av1Dct4Forward1dTransformer.cs | 10 +- .../Forward/Av1Dct64Forward1dTransformer.cs | 739 +++++++++++++++++- .../Forward/Av1Dct8Forward1dTransformer.cs | 63 +- .../Forward/Av1Forward2dTransformerBase.cs | 6 +- .../Av1Identity16Forward1dTransformer.cs | 26 +- .../Av1Identity32Forward1dTransformer.cs | 21 +- .../Av1Identity4Forward1dTransformer.cs | 12 +- .../Av1Identity64Forward1dTransformer.cs | 31 +- .../Av1Identity8Forward1dTransformer.cs | 16 +- .../Heif/Av1/Av1ForwardTransformTests.cs | 148 ++-- .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 4 +- 19 files changed, 2190 insertions(+), 129 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs index 86a668a802..3528842677 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -31,7 +31,7 @@ internal class Av1ForwardTransformer null ]; - private static readonly int[] TemporaryCoefficientsBuffer = new int[64 * 64]; + private static readonly int[] TemporaryCoefficientsBuffer = new int[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; internal static void Transform2d(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) { @@ -49,54 +49,6 @@ internal static void Transform2d(Span input, Span coefficients, uint } } - internal static void Transform2dAvx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) - { - switch (transformSize) - { - case Av1TransformSize.Size4x4: - // Too small for intrinsics, use the scalar codepath instead. - Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); - break; - case Av1TransformSize.Size8x8: - Transform8x8Avx2(input, coefficients, stride, transformType, bitDepth); - break; - default: - Transform2d(input, coefficients, stride, transformType, transformSize, bitDepth); - break; - } - } - - /// - /// SVT: svt_av1_fwd_txfm2d_8x8_avx2 - /// - private static void Transform8x8Avx2(Span input, Span coefficients, uint stride, Av1TransformType transformType, int bitDepth) - { - Av1Transform2dFlipConfiguration config = new(transformType, Av1TransformSize.Size8x8); - Span shift = config.Shift; - Span> inVector = stackalloc Vector256[8]; - Span> outVector = stackalloc Vector256[8]; - ref Vector256 inRef = ref inVector[0]; - ref Vector256 outRef = ref outVector[0]; - switch (transformType) - { - case Av1TransformType.DctDct: - /* Pseudo code - Av1Dct8ForwardTransformer dct8 = new(); - LoadBuffer8x8(ref input[0], ref inRef, stride, 0, 0, shift[0]); - dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitColumn, 1); - Column8x8Rounding(ref outRef, -shift[1]); - Transpose8x8Avx2(ref outRef, ref inRef); - dct8.TransformAvx2(ref inRef, ref outRef, config.CosBitRow, 1); - Transpose8x8Avx2(ref outRef, ref inRef); - WriteBuffer8x8(ref inRef, ref coefficients[0]); - break; - */ - throw new NotImplementedException(); - default: - throw new NotImplementedException(); - } - } - private static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType transformerType) => Transformers[(int)transformerType]; @@ -155,7 +107,7 @@ private static void Transform2dCore(TColumn transformFunctionColu uint t = (uint)(c + ((transformRowCount - 1) * (int)inputStride)); for (r = 0; r < transformRowCount; ++r) { - // flip upside down + // Flip upside down Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); t -= inputStride; } @@ -188,17 +140,23 @@ private static void Transform2dCore(TColumn transformFunctionColu // Rows for (r = 0; r < transformRowCount; ++r) { - transformFunctionRow.Transform(ref Unsafe.Add(ref buf, r * transformColumnCount), ref Unsafe.Add(ref output, r * transformColumnCount), cosBitRow, stageRangeRow); + transformFunctionRow.Transform( + ref Unsafe.Add(ref buf, r * transformColumnCount), + ref Unsafe.Add(ref output, r * transformColumnCount), + cosBitRow, + stageRangeRow); RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); if (Math.Abs(rectangleType) == 1) { // Multiply everything by Sqrt2 if the transform is rectangular and the // size difference is a factor of 2. + int t = r * transformColumnCount; for (c = 0; c < transformColumnCount; ++c) { - ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); + ref int current = ref Unsafe.Add(ref output, t); current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); + t++; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs index 4ebed44c6b..7fd6d6d95d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -131,15 +131,15 @@ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1Transf this.TransformSize = transformSize; this.TransformType = transformType; this.SetFlip(transformType); - Av1TransformType1d tx_type_1d_col = VerticalType[(int)transformType]; - Av1TransformType1d tx_type_1d_row = HorizontalType[(int)transformType]; + this.TransformTypeColumn = VerticalType[(int)transformType]; + this.TransformTypeRow = HorizontalType[(int)transformType]; int txw_idx = transformSize.GetBlockWidthLog2() - SmallestTransformSizeLog2; int txh_idx = transformSize.GetBlockHeightLog2() - SmallestTransformSizeLog2; this.shift = ShiftMap[(int)transformSize]; this.CosBitColumn = CosBitColumnMap[txw_idx][txh_idx]; this.CosBitRow = CosBitRowMap[txw_idx][txh_idx]; - this.TransformFunctionTypeColumn = TransformFunctionTypeMap[txh_idx][(int)tx_type_1d_col]; - this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)tx_type_1d_row]; + this.TransformFunctionTypeColumn = TransformFunctionTypeMap[txh_idx][(int)this.TransformTypeColumn]; + this.TransformFunctionTypeRow = TransformFunctionTypeMap[txw_idx][(int)this.TransformTypeRow]; this.StageNumberColumn = StageNumberList[(int)this.TransformFunctionTypeColumn]; this.StageNumberRow = StageNumberList[(int)this.TransformFunctionTypeRow]; this.StageRangeColumn = new byte[12]; @@ -151,6 +151,10 @@ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1Transf public int CosBitRow { get; } + public Av1TransformType1d TransformTypeColumn { get; } + + public Av1TransformType1d TransformTypeRow { get; } + public Av1TransformFunctionType TransformFunctionTypeColumn { get; } public Av1TransformFunctionType TransformFunctionTypeRow { get; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs index f52e348560..b3c343538c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs @@ -1,10 +1,184 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst16Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[16]; + Span temp1 = stackalloc int[16]; + + // stage 0; + + // stage 1; + Guard.IsFalse(output == input, nameof(output), "Cannot operate on same buffer for input and output."); + temp1[0] = input; + temp1[1] = -Unsafe.Add(ref input, 15); + temp1[2] = -Unsafe.Add(ref input, 7); + temp1[3] = Unsafe.Add(ref input, 8); + temp1[4] = -Unsafe.Add(ref input, 3); + temp1[5] = Unsafe.Add(ref input, 12); + temp1[6] = Unsafe.Add(ref input, 4); + temp1[7] = -Unsafe.Add(ref input, 11); + temp1[8] = -Unsafe.Add(ref input, 1); + temp1[9] = Unsafe.Add(ref input, 14); + temp1[10] = Unsafe.Add(ref input, 6); + temp1[11] = -Unsafe.Add(ref input, 9); + temp1[12] = Unsafe.Add(ref input, 2); + temp1[13] = -Unsafe.Add(ref input, 13); + temp1[14] = -Unsafe.Add(ref input, 5); + temp1[15] = Unsafe.Add(ref input, 10); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], cospi[32], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], -cospi[32], temp1[3], cosBit); + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], -cospi[32], temp1[7], cosBit); + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], cospi[32], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], -cospi[32], temp1[11], cosBit); + temp0[12] = temp1[12]; + temp0[13] = temp1[13]; + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], cospi[32], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], -cospi[32], temp1[15], cosBit); + + // stage 3 + temp1[0] = temp0[0] + temp0[2]; + temp1[1] = temp0[1] + temp0[3]; + temp1[2] = temp0[0] - temp0[2]; + temp1[3] = temp0[1] - temp0[3]; + temp1[4] = temp0[4] + temp0[6]; + temp1[5] = temp0[5] + temp0[7]; + temp1[6] = temp0[4] - temp0[6]; + temp1[7] = temp0[5] - temp0[7]; + temp1[8] = temp0[8] + temp0[10]; + temp1[9] = temp0[9] + temp0[11]; + temp1[10] = temp0[8] - temp0[10]; + temp1[11] = temp0[9] - temp0[11]; + temp1[12] = temp0[12] + temp0[14]; + temp1[13] = temp0[13] + temp0[15]; + temp1[14] = temp0[12] - temp0[14]; + temp1[15] = temp0[13] - temp0[15]; + + // stage 4 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[4], cospi[48], temp1[5], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[4], -cospi[16], temp1[5], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[6], cospi[16], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[6], cospi[48], temp1[7], cosBit); + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = temp1[10]; + temp0[11] = temp1[11]; + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[12], cospi[48], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[12], -cospi[16], temp1[13], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[14], cospi[16], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[14], cospi[48], temp1[15], cosBit); + + // stage 5 + temp1[0] = temp0[0] + temp0[4]; + temp1[1] = temp0[1] + temp0[5]; + temp1[2] = temp0[2] + temp0[6]; + temp1[3] = temp0[3] + temp0[7]; + temp1[4] = temp0[0] - temp0[4]; + temp1[5] = temp0[1] - temp0[5]; + temp1[6] = temp0[2] - temp0[6]; + temp1[7] = temp0[3] - temp0[7]; + temp1[8] = temp0[8] + temp0[12]; + temp1[9] = temp0[9] + temp0[13]; + temp1[10] = temp0[10] + temp0[14]; + temp1[11] = temp0[11] + temp0[15]; + temp1[12] = temp0[8] - temp0[12]; + temp1[13] = temp0[9] - temp0[13]; + temp1[14] = temp0[10] - temp0[14]; + temp1[15] = temp0[11] - temp0[15]; + + // stage 6 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = temp1[6]; + temp0[7] = temp1[7]; + temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[8], cospi[56], temp1[9], cosBit); + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[8], -cospi[8], temp1[9], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[10], cospi[24], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[10], -cospi[40], temp1[11], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[12], cospi[8], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[12], cospi[56], temp1[13], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[14], cospi[40], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[14], cospi[24], temp1[15], cosBit); + + // stage 7 + temp1[0] = temp0[0] + temp0[8]; + temp1[1] = temp0[1] + temp0[9]; + temp1[2] = temp0[2] + temp0[10]; + temp1[3] = temp0[3] + temp0[11]; + temp1[4] = temp0[4] + temp0[12]; + temp1[5] = temp0[5] + temp0[13]; + temp1[6] = temp0[6] + temp0[14]; + temp1[7] = temp0[7] + temp0[15]; + temp1[8] = temp0[0] - temp0[8]; + temp1[9] = temp0[1] - temp0[9]; + temp1[10] = temp0[2] - temp0[10]; + temp1[11] = temp0[3] - temp0[11]; + temp1[12] = temp0[4] - temp0[12]; + temp1[13] = temp0[5] - temp0[13]; + temp1[14] = temp0[6] - temp0[14]; + temp1[15] = temp0[7] - temp0[15]; + + // stage 8 + temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[2], temp1[0], cospi[62], temp1[1], cosBit); + temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[0], -cospi[2], temp1[1], cosBit); + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[10], temp1[2], cospi[54], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[2], -cospi[10], temp1[3], cosBit); + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[18], temp1[4], cospi[46], temp1[5], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[4], -cospi[18], temp1[5], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[26], temp1[6], cospi[38], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[6], -cospi[26], temp1[7], cosBit); + temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[34], temp1[8], cospi[30], temp1[9], cosBit); + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[8], -cospi[34], temp1[9], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[42], temp1[10], cospi[22], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[10], -cospi[42], temp1[11], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[50], temp1[12], cospi[14], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[12], -cospi[50], temp1[13], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[58], temp1[14], cospi[6], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[14], -cospi[58], temp1[15], cosBit); + + // stage 9 + output = temp0[1]; + Unsafe.Add(ref output, 1) = temp0[14]; + Unsafe.Add(ref output, 2) = temp0[3]; + Unsafe.Add(ref output, 3) = temp0[12]; + Unsafe.Add(ref output, 4) = temp0[5]; + Unsafe.Add(ref output, 5) = temp0[10]; + Unsafe.Add(ref output, 6) = temp0[7]; + Unsafe.Add(ref output, 7) = temp0[8]; + Unsafe.Add(ref output, 8) = temp0[9]; + Unsafe.Add(ref output, 9) = temp0[6]; + Unsafe.Add(ref output, 10) = temp0[11]; + Unsafe.Add(ref output, 11) = temp0[4]; + Unsafe.Add(ref output, 12) = temp0[13]; + Unsafe.Add(ref output, 13) = temp0[2]; + Unsafe.Add(ref output, 14) = temp0[15]; + Unsafe.Add(ref output, 15) = temp0[0]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs index 5a5b7765f5..9f707ae639 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs @@ -1,10 +1,395 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst32Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int outputRef, int cosBit) + { + Span temp0 = stackalloc int[32]; + Span temp1 = stackalloc int[32]; + + // stage 0; + + // stage 1; + temp1[0] = Unsafe.Add(ref input, 31); + temp1[1] = input; + temp1[2] = Unsafe.Add(ref input, 29); + temp1[3] = Unsafe.Add(ref input, 2); + temp1[4] = Unsafe.Add(ref input, 27); + temp1[5] = Unsafe.Add(ref input, 4); + temp1[6] = Unsafe.Add(ref input, 25); + temp1[7] = Unsafe.Add(ref input, 6); + temp1[8] = Unsafe.Add(ref input, 23); + temp1[9] = Unsafe.Add(ref input, 8); + temp1[10] = Unsafe.Add(ref input, 21); + temp1[11] = Unsafe.Add(ref input, 10); + temp1[12] = Unsafe.Add(ref input, 19); + temp1[13] = Unsafe.Add(ref input, 12); + temp1[14] = Unsafe.Add(ref input, 17); + temp1[15] = Unsafe.Add(ref input, 14); + temp1[16] = Unsafe.Add(ref input, 15); + temp1[17] = Unsafe.Add(ref input, 16); + temp1[18] = Unsafe.Add(ref input, 13); + temp1[19] = Unsafe.Add(ref input, 18); + temp1[20] = Unsafe.Add(ref input, 11); + temp1[21] = Unsafe.Add(ref input, 20); + temp1[22] = Unsafe.Add(ref input, 9); + temp1[23] = Unsafe.Add(ref input, 22); + temp1[24] = Unsafe.Add(ref input, 7); + temp1[25] = Unsafe.Add(ref input, 24); + temp1[26] = Unsafe.Add(ref input, 5); + temp1[27] = Unsafe.Add(ref input, 26); + temp1[28] = Unsafe.Add(ref input, 3); + temp1[29] = Unsafe.Add(ref input, 28); + temp1[30] = Unsafe.Add(ref input, 1); + temp1[31] = Unsafe.Add(ref input, 30); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[1], temp1[0], cospi[63], temp1[1], cosBit); + temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[1], temp1[1], cospi[63], temp1[0], cosBit); + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[5], temp1[2], cospi[59], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[5], temp1[3], cospi[59], temp1[2], cosBit); + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[9], temp1[4], cospi[55], temp1[5], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[9], temp1[5], cospi[55], temp1[4], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[13], temp1[6], cospi[51], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[13], temp1[7], cospi[51], temp1[6], cosBit); + temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[17], temp1[8], cospi[47], temp1[9], cosBit); + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[17], temp1[9], cospi[47], temp1[8], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[21], temp1[10], cospi[43], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[21], temp1[11], cospi[43], temp1[10], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[25], temp1[12], cospi[39], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[25], temp1[13], cospi[39], temp1[12], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[29], temp1[14], cospi[35], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[29], temp1[15], cospi[35], temp1[14], cosBit); + temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[33], temp1[16], cospi[31], temp1[17], cosBit); + temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[33], temp1[17], cospi[31], temp1[16], cosBit); + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[37], temp1[18], cospi[27], temp1[19], cosBit); + temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[37], temp1[19], cospi[27], temp1[18], cosBit); + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[41], temp1[20], cospi[23], temp1[21], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[41], temp1[21], cospi[23], temp1[20], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[45], temp1[22], cospi[19], temp1[23], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[45], temp1[23], cospi[19], temp1[22], cosBit); + temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[49], temp1[24], cospi[15], temp1[25], cosBit); + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[49], temp1[25], cospi[15], temp1[24], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[53], temp1[26], cospi[11], temp1[27], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[53], temp1[27], cospi[11], temp1[26], cosBit); + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[57], temp1[28], cospi[7], temp1[29], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[57], temp1[29], cospi[7], temp1[28], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[61], temp1[30], cospi[3], temp1[31], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[61], temp1[31], cospi[3], temp1[30], cosBit); + + // stage 3 + temp1[0] = temp0[0] + temp0[16]; + temp1[1] = temp0[1] + temp0[17]; + temp1[2] = temp0[2] + temp0[18]; + temp1[3] = temp0[3] + temp0[19]; + temp1[4] = temp0[4] + temp0[20]; + temp1[5] = temp0[5] + temp0[21]; + temp1[6] = temp0[6] + temp0[22]; + temp1[7] = temp0[7] + temp0[23]; + temp1[8] = temp0[8] + temp0[24]; + temp1[9] = temp0[9] + temp0[25]; + temp1[10] = temp0[10] + temp0[26]; + temp1[11] = temp0[11] + temp0[27]; + temp1[12] = temp0[12] + temp0[28]; + temp1[13] = temp0[13] + temp0[29]; + temp1[14] = temp0[14] + temp0[30]; + temp1[15] = temp0[15] + temp0[31]; + temp1[16] = -temp0[16] + temp0[0]; + temp1[17] = -temp0[17] + temp0[1]; + temp1[18] = -temp0[18] + temp0[2]; + temp1[19] = -temp0[19] + temp0[3]; + temp1[20] = -temp0[20] + temp0[4]; + temp1[21] = -temp0[21] + temp0[5]; + temp1[22] = -temp0[22] + temp0[6]; + temp1[23] = -temp0[23] + temp0[7]; + temp1[24] = -temp0[24] + temp0[8]; + temp1[25] = -temp0[25] + temp0[9]; + temp1[26] = -temp0[26] + temp0[10]; + temp1[27] = -temp0[27] + temp0[11]; + temp1[28] = -temp0[28] + temp0[12]; + temp1[29] = -temp0[29] + temp0[13]; + temp1[30] = -temp0[30] + temp0[14]; + temp1[31] = -temp0[31] + temp0[15]; + + // stage 4 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = temp1[6]; + temp0[7] = temp1[7]; + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = temp1[10]; + temp0[11] = temp1[11]; + temp0[12] = temp1[12]; + temp0[13] = temp1[13]; + temp0[14] = temp1[14]; + temp0[15] = temp1[15]; + temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp1[16], cospi[60], temp1[17], cosBit); + temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[4], temp1[17], cospi[60], temp1[16], cosBit); + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp1[18], cospi[44], temp1[19], cosBit); + temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[20], temp1[19], cospi[44], temp1[18], cosBit); + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp1[20], cospi[28], temp1[21], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[36], temp1[21], cospi[28], temp1[20], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp1[22], cospi[12], temp1[23], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[52], temp1[23], cospi[12], temp1[22], cosBit); + temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[60], temp1[24], cospi[4], temp1[25], cosBit); + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[25], cospi[4], temp1[24], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[44], temp1[26], cospi[20], temp1[27], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[27], cospi[20], temp1[26], cosBit); + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[28], temp1[28], cospi[36], temp1[29], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[29], cospi[36], temp1[28], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[12], temp1[30], cospi[52], temp1[31], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[31], cospi[52], temp1[30], cosBit); + + // stage 5 + temp1[0] = temp0[0] + temp0[8]; + temp1[1] = temp0[1] + temp0[9]; + temp1[2] = temp0[2] + temp0[10]; + temp1[3] = temp0[3] + temp0[11]; + temp1[4] = temp0[4] + temp0[12]; + temp1[5] = temp0[5] + temp0[13]; + temp1[6] = temp0[6] + temp0[14]; + temp1[7] = temp0[7] + temp0[15]; + temp1[8] = -temp0[8] + temp0[0]; + temp1[9] = -temp0[9] + temp0[1]; + temp1[10] = -temp0[10] + temp0[2]; + temp1[11] = -temp0[11] + temp0[3]; + temp1[12] = -temp0[12] + temp0[4]; + temp1[13] = -temp0[13] + temp0[5]; + temp1[14] = -temp0[14] + temp0[6]; + temp1[15] = -temp0[15] + temp0[7]; + temp1[16] = temp0[16] + temp0[24]; + temp1[17] = temp0[17] + temp0[25]; + temp1[18] = temp0[18] + temp0[26]; + temp1[19] = temp0[19] + temp0[27]; + temp1[20] = temp0[20] + temp0[28]; + temp1[21] = temp0[21] + temp0[29]; + temp1[22] = temp0[22] + temp0[30]; + temp1[23] = temp0[23] + temp0[31]; + temp1[24] = -temp0[24] + temp0[16]; + temp1[25] = -temp0[25] + temp0[17]; + temp1[26] = -temp0[26] + temp0[18]; + temp1[27] = -temp0[27] + temp0[19]; + temp1[28] = -temp0[28] + temp0[20]; + temp1[29] = -temp0[29] + temp0[21]; + temp1[30] = -temp0[30] + temp0[22]; + temp1[31] = -temp0[31] + temp0[23]; + + // stage 6 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = temp1[6]; + temp0[7] = temp1[7]; + temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[8], cospi[56], temp1[9], cosBit); + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[9], cospi[56], temp1[8], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[10], cospi[24], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[11], cospi[24], temp1[10], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[12], cospi[8], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[13], cospi[8], temp1[12], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[14], cospi[40], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[15], cospi[40], temp1[14], cosBit); + temp0[16] = temp1[16]; + temp0[17] = temp1[17]; + temp0[18] = temp1[18]; + temp0[19] = temp1[19]; + temp0[20] = temp1[20]; + temp0[21] = temp1[21]; + temp0[22] = temp1[22]; + temp0[23] = temp1[23]; + temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[24], cospi[56], temp1[25], cosBit); + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[25], cospi[56], temp1[24], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[26], cospi[24], temp1[27], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[27], cospi[24], temp1[26], cosBit); + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[28], cospi[8], temp1[29], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[29], cospi[8], temp1[28], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[30], cospi[40], temp1[31], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[31], cospi[40], temp1[30], cosBit); + + // stage 7 + temp1[0] = temp0[0] + temp0[4]; + temp1[1] = temp0[1] + temp0[5]; + temp1[2] = temp0[2] + temp0[6]; + temp1[3] = temp0[3] + temp0[7]; + temp1[4] = -temp0[4] + temp0[0]; + temp1[5] = -temp0[5] + temp0[1]; + temp1[6] = -temp0[6] + temp0[2]; + temp1[7] = -temp0[7] + temp0[3]; + temp1[8] = temp0[8] + temp0[12]; + temp1[9] = temp0[9] + temp0[13]; + temp1[10] = temp0[10] + temp0[14]; + temp1[11] = temp0[11] + temp0[15]; + temp1[12] = -temp0[12] + temp0[8]; + temp1[13] = -temp0[13] + temp0[9]; + temp1[14] = -temp0[14] + temp0[10]; + temp1[15] = -temp0[15] + temp0[11]; + temp1[16] = temp0[16] + temp0[20]; + temp1[17] = temp0[17] + temp0[21]; + temp1[18] = temp0[18] + temp0[22]; + temp1[19] = temp0[19] + temp0[23]; + temp1[20] = -temp0[20] + temp0[16]; + temp1[21] = -temp0[21] + temp0[17]; + temp1[22] = -temp0[22] + temp0[18]; + temp1[23] = -temp0[23] + temp0[19]; + temp1[24] = temp0[24] + temp0[28]; + temp1[25] = temp0[25] + temp0[29]; + temp1[26] = temp0[26] + temp0[30]; + temp1[27] = temp0[27] + temp0[31]; + temp1[28] = -temp0[28] + temp0[24]; + temp1[29] = -temp0[29] + temp0[25]; + temp1[30] = -temp0[30] + temp0[26]; + temp1[31] = -temp0[31] + temp0[27]; + + // stage 8 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[4], cospi[48], temp1[5], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[5], cospi[48], temp1[4], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[6], cospi[16], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[7], cospi[16], temp1[6], cosBit); + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = temp1[10]; + temp0[11] = temp1[11]; + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[12], cospi[48], temp1[13], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[13], cospi[48], temp1[12], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[14], cospi[16], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[15], cospi[16], temp1[14], cosBit); + temp0[16] = temp1[16]; + temp0[17] = temp1[17]; + temp0[18] = temp1[18]; + temp0[19] = temp1[19]; + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[20], cospi[48], temp1[21], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[21], cospi[48], temp1[20], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[22], cospi[16], temp1[23], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[23], cospi[16], temp1[22], cosBit); + temp0[24] = temp1[24]; + temp0[25] = temp1[25]; + temp0[26] = temp1[26]; + temp0[27] = temp1[27]; + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[28], cospi[48], temp1[29], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[29], cospi[48], temp1[28], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[30], cospi[16], temp1[31], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[31], cospi[16], temp1[30], cosBit); + + // stage 9 + temp1[0] = temp0[0] + temp0[2]; + temp1[1] = temp0[1] + temp0[3]; + temp1[2] = -temp0[2] + temp0[0]; + temp1[3] = -temp0[3] + temp0[1]; + temp1[4] = temp0[4] + temp0[6]; + temp1[5] = temp0[5] + temp0[7]; + temp1[6] = -temp0[6] + temp0[4]; + temp1[7] = -temp0[7] + temp0[5]; + temp1[8] = temp0[8] + temp0[10]; + temp1[9] = temp0[9] + temp0[11]; + temp1[10] = -temp0[10] + temp0[8]; + temp1[11] = -temp0[11] + temp0[9]; + temp1[12] = temp0[12] + temp0[14]; + temp1[13] = temp0[13] + temp0[15]; + temp1[14] = -temp0[14] + temp0[12]; + temp1[15] = -temp0[15] + temp0[13]; + temp1[16] = temp0[16] + temp0[18]; + temp1[17] = temp0[17] + temp0[19]; + temp1[18] = -temp0[18] + temp0[16]; + temp1[19] = -temp0[19] + temp0[17]; + temp1[20] = temp0[20] + temp0[22]; + temp1[21] = temp0[21] + temp0[23]; + temp1[22] = -temp0[22] + temp0[20]; + temp1[23] = -temp0[23] + temp0[21]; + temp1[24] = temp0[24] + temp0[26]; + temp1[25] = temp0[25] + temp0[27]; + temp1[26] = -temp0[26] + temp0[24]; + temp1[27] = -temp0[27] + temp0[25]; + temp1[28] = temp0[28] + temp0[30]; + temp1[29] = temp0[29] + temp0[31]; + temp1[30] = -temp0[30] + temp0[28]; + temp1[31] = -temp0[31] + temp0[29]; + + // stage 10 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[2], cospi[32], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[3], cospi[32], temp1[2], cosBit); + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[7], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[7], cospi[32], temp1[6], cosBit); + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[10], cospi[32], temp1[11], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[11], cospi[32], temp1[10], cosBit); + temp0[12] = temp1[12]; + temp0[13] = temp1[13]; + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[14], cospi[32], temp1[15], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[15], cospi[32], temp1[14], cosBit); + temp0[16] = temp1[16]; + temp0[17] = temp1[17]; + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[18], cospi[32], temp1[19], cosBit); + temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[19], cospi[32], temp1[18], cosBit); + temp0[20] = temp1[20]; + temp0[21] = temp1[21]; + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[22], cospi[32], temp1[23], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[23], cospi[32], temp1[22], cosBit); + temp0[24] = temp1[24]; + temp0[25] = temp1[25]; + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[26], cospi[32], temp1[27], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[27], cospi[32], temp1[26], cosBit); + temp0[28] = temp1[28]; + temp0[29] = temp1[29]; + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[30], cospi[32], temp1[31], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[31], cospi[32], temp1[30], cosBit); + + // stage 11 + outputRef = temp0[0]; + Unsafe.Add(ref outputRef, 1) = -temp0[16]; + Unsafe.Add(ref outputRef, 2) = temp0[24]; + Unsafe.Add(ref outputRef, 3) = -temp0[8]; + Unsafe.Add(ref outputRef, 4) = temp0[12]; + Unsafe.Add(ref outputRef, 5) = -temp0[28]; + Unsafe.Add(ref outputRef, 6) = temp0[20]; + Unsafe.Add(ref outputRef, 7) = -temp0[4]; + Unsafe.Add(ref outputRef, 8) = temp0[6]; + Unsafe.Add(ref outputRef, 9) = -temp0[22]; + Unsafe.Add(ref outputRef, 10) = temp0[30]; + Unsafe.Add(ref outputRef, 11) = -temp0[14]; + Unsafe.Add(ref outputRef, 12) = temp0[10]; + Unsafe.Add(ref outputRef, 13) = -temp0[26]; + Unsafe.Add(ref outputRef, 14) = temp0[18]; + Unsafe.Add(ref outputRef, 15) = -temp0[2]; + Unsafe.Add(ref outputRef, 16) = temp0[3]; + Unsafe.Add(ref outputRef, 17) = -temp0[19]; + Unsafe.Add(ref outputRef, 18) = temp0[27]; + Unsafe.Add(ref outputRef, 19) = -temp0[11]; + Unsafe.Add(ref outputRef, 20) = temp0[15]; + Unsafe.Add(ref outputRef, 21) = -temp0[31]; + Unsafe.Add(ref outputRef, 22) = temp0[23]; + Unsafe.Add(ref outputRef, 23) = -temp0[7]; + Unsafe.Add(ref outputRef, 24) = temp0[5]; + Unsafe.Add(ref outputRef, 25) = -temp0[21]; + Unsafe.Add(ref outputRef, 26) = temp0[29]; + Unsafe.Add(ref outputRef, 27) = -temp0[13]; + Unsafe.Add(ref outputRef, 28) = temp0[9]; + Unsafe.Add(ref outputRef, 29) = -temp0[25]; + Unsafe.Add(ref outputRef, 30) = temp0[17]; + Unsafe.Add(ref outputRef, 31) = -temp0[1]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs index 8dcea3770b..2b4952873a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs @@ -1,10 +1,72 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst4Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span sinpi = Av1SinusConstants.SinusPi(cosBit); + int x0, x1, x2, x3; + int s0, s1, s2, s3, s4, s5, s6, s7; + + // stage 0 + x0 = input; + x1 = Unsafe.Add(ref input, 1); + x2 = Unsafe.Add(ref input, 2); + x3 = Unsafe.Add(ref input, 3); + + if (!(x0 != 0 | x1 != 0 | x2 != 0 | x3 != 0)) + { + output = 0; + Unsafe.Add(ref output, 1) = 0; + Unsafe.Add(ref output, 2) = 0; + Unsafe.Add(ref output, 3) = 0; + return; + } + + // stage 1 + s0 = sinpi[1] * x0; + s1 = sinpi[4] * x0; + s2 = sinpi[2] * x1; + s3 = sinpi[1] * x1; + s4 = sinpi[3] * x2; + s5 = sinpi[4] * x3; + s6 = sinpi[2] * x3; + s7 = x0 + x1; + + // stage 2 + s7 -= x3; + + // stage 3 + x0 = s0 + s2; + x1 = sinpi[3] * s7; + x2 = s1 - s3; + x3 = s4; + + // stage 4 + x0 += s5; + x2 += s6; + + // stage 5 + s0 = x0 + x3; + s1 = x1; + s2 = x2 - x3; + s3 = x2 - x0; + + // stage 6 + s3 += x3; + + // 1-D transform scaling factor is sqrt(2). + output = Av1Math.RoundShift(s0, cosBit); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift(s1, cosBit); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift(s2, cosBit); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift(s3, cosBit); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs index 3da6fc593b..8019df88c5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs @@ -1,10 +1,92 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst8Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[8]; + Span temp1 = stackalloc int[8]; + + // stage 0; + + // stage 1; + Guard.IsFalse(output == input, nameof(output), "Cannot operate on same buffer for input and output."); + temp0[0] = input; + temp0[1] = -Unsafe.Add(ref input, 7); + temp0[2] = -Unsafe.Add(ref input, 3); + temp0[3] = Unsafe.Add(ref input, 4); + temp0[4] = -Unsafe.Add(ref input, 1); + temp0[5] = Unsafe.Add(ref input, 6); + temp0[6] = Unsafe.Add(ref input, 2); + temp0[7] = -Unsafe.Add(ref input, 5); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[2], cospi[32], temp0[3], cosBit); + temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[2], -cospi[32], temp0[3], cosBit); + temp1[4] = temp0[4]; + temp1[5] = temp0[5]; + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[7], cosBit); + temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], -cospi[32], temp0[7], cosBit); + + // stage 3 + temp0[0] = temp1[0] + temp1[2]; + temp0[1] = temp1[1] + temp1[3]; + temp0[2] = temp1[0] - temp1[2]; + temp0[3] = temp1[1] - temp1[3]; + temp0[4] = temp1[4] + temp1[6]; + temp0[5] = temp1[5] + temp1[7]; + temp0[6] = temp1[4] - temp1[6]; + temp0[7] = temp1[5] - temp1[7]; + + // stage 4 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[4], cospi[48], temp0[5], cosBit); + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[4], -cospi[16], temp0[5], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[6], cospi[16], temp0[7], cosBit); + temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[6], cospi[48], temp0[7], cosBit); + + // stage 5 + temp0[0] = temp1[0] + temp1[4]; + temp0[1] = temp1[1] + temp1[5]; + temp0[2] = temp1[2] + temp1[6]; + temp0[3] = temp1[3] + temp1[7]; + temp0[4] = temp1[0] - temp1[4]; + temp0[5] = temp1[1] - temp1[5]; + temp0[6] = temp1[2] - temp1[6]; + temp0[7] = temp1[3] - temp1[7]; + + // stage 6 + temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp0[0], cospi[60], temp0[1], cosBit); + temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[0], -cospi[4], temp0[1], cosBit); + temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp0[2], cospi[44], temp0[3], cosBit); + temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[2], -cospi[20], temp0[3], cosBit); + temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp0[4], cospi[28], temp0[5], cosBit); + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[4], -cospi[36], temp0[5], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp0[6], cospi[12], temp0[7], cosBit); + temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[6], -cospi[52], temp0[7], cosBit); + + // stage 7 + output = temp1[1]; + Unsafe.Add(ref output, 1) = temp1[6]; + Unsafe.Add(ref output, 2) = temp1[3]; + Unsafe.Add(ref output, 3) = temp1[4]; + Unsafe.Add(ref output, 4) = temp1[5]; + Unsafe.Add(ref output, 5) = temp1[2]; + Unsafe.Add(ref output, 6) = temp1[7]; + Unsafe.Add(ref output, 7) = temp1[0]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs index 891635609e..1173c56de7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs @@ -1,10 +1,147 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct16Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[16]; + Span temp1 = stackalloc int[16]; + + // stage 0; + + // stage 1; + temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 15); + temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 14); + temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 13); + temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 12); + temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 11); + temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 10); + temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 9); + temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 8); + temp0[8] = -Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 7); + temp0[9] = -Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 6); + temp0[10] = -Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 5); + temp0[11] = -Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 4); + temp0[12] = -Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 3); + temp0[13] = -Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 2); + temp0[14] = -Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 1); + temp0[15] = -Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 0); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp1[0] = temp0[0] + temp0[7]; + temp1[1] = temp0[1] + temp0[6]; + temp1[2] = temp0[2] + temp0[5]; + temp1[3] = temp0[3] + temp0[4]; + temp1[4] = -temp0[4] + temp0[3]; + temp1[5] = -temp0[5] + temp0[2]; + temp1[6] = -temp0[6] + temp0[1]; + temp1[7] = -temp0[7] + temp0[0]; + temp1[8] = temp0[8]; + temp1[9] = temp0[9]; + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[10], cospi[32], temp0[13], cosBit); + temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[11], cospi[32], temp0[12], cosBit); + temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[12], cospi[32], temp0[11], cosBit); + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[13], cospi[32], temp0[10], cosBit); + temp1[14] = temp0[14]; + temp1[15] = temp0[15]; + + // stage 3 + temp0[0] = temp1[0] + temp1[3]; + temp0[1] = temp1[1] + temp1[2]; + temp0[2] = -temp1[2] + temp1[1]; + temp0[3] = -temp1[3] + temp1[0]; + temp0[4] = temp1[4]; + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[5], cospi[32], temp1[6], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[5], cosBit); + temp0[7] = temp1[7]; + temp0[8] = temp1[8] + temp1[11]; + temp0[9] = temp1[9] + temp1[10]; + temp0[10] = -temp1[10] + temp1[9]; + temp0[11] = -temp1[11] + temp1[8]; + temp0[12] = -temp1[12] + temp1[15]; + temp0[13] = -temp1[13] + temp1[14]; + temp0[14] = temp1[14] + temp1[13]; + temp0[15] = temp1[15] + temp1[12]; + + // stage 4 + temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit); + temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[1], cospi[32], temp0[0], cosBit); + temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[2], cospi[16], temp0[3], cosBit); + temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[3], -cospi[16], temp0[2], cosBit); + temp1[4] = temp0[4] + temp0[5]; + temp1[5] = -temp0[5] + temp0[4]; + temp1[6] = -temp0[6] + temp0[7]; + temp1[7] = temp0[7] + temp0[6]; + temp1[8] = temp0[8]; + temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[9], cospi[48], temp0[14], cosBit); + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[10], -cospi[16], temp0[13], cosBit); + temp1[11] = temp0[11]; + temp1[12] = temp0[12]; + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[13], -cospi[16], temp0[10], cosBit); + temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[14], cospi[48], temp0[9], cosBit); + temp1[15] = temp0[15]; + + // stage 5 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[4], cospi[8], temp1[7], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[5], cospi[40], temp1[6], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[6], -cospi[40], temp1[5], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[7], -cospi[8], temp1[4], cosBit); + temp0[8] = temp1[8] + temp1[9]; + temp0[9] = -temp1[9] + temp1[8]; + temp0[10] = -temp1[10] + temp1[11]; + temp0[11] = temp1[11] + temp1[10]; + temp0[12] = temp1[12] + temp1[13]; + temp0[13] = -temp1[13] + temp1[12]; + temp0[14] = -temp1[14] + temp1[15]; + temp0[15] = temp1[15] + temp1[14]; + + // stage 6 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = temp0[4]; + temp1[5] = temp0[5]; + temp1[6] = temp0[6]; + temp1[7] = temp0[7]; + temp1[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[8], cospi[4], temp0[15], cosBit); + temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[9], cospi[36], temp0[14], cosBit); + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[10], cospi[20], temp0[13], cosBit); + temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[11], cospi[52], temp0[12], cosBit); + temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[12], -cospi[52], temp0[11], cosBit); + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[13], -cospi[20], temp0[10], cosBit); + temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[14], -cospi[36], temp0[9], cosBit); + temp1[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[15], -cospi[4], temp0[8], cosBit); + + // stage 7 + output = temp1[0]; + Unsafe.Add(ref output, 1) = temp1[8]; + Unsafe.Add(ref output, 2) = temp1[4]; + Unsafe.Add(ref output, 3) = temp1[12]; + Unsafe.Add(ref output, 4) = temp1[2]; + Unsafe.Add(ref output, 5) = temp1[10]; + Unsafe.Add(ref output, 6) = temp1[6]; + Unsafe.Add(ref output, 7) = temp1[14]; + Unsafe.Add(ref output, 8) = temp1[1]; + Unsafe.Add(ref output, 9) = temp1[9]; + Unsafe.Add(ref output, 10) = temp1[5]; + Unsafe.Add(ref output, 11) = temp1[13]; + Unsafe.Add(ref output, 12) = temp1[3]; + Unsafe.Add(ref output, 13) = temp1[11]; + Unsafe.Add(ref output, 14) = temp1[7]; + Unsafe.Add(ref output, 15) = temp1[15]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs index aa87a0663e..c0068072d3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs @@ -1,10 +1,327 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct32Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[32]; + Span temp1 = stackalloc int[32]; + + // stage 0; + + // stage 1; + temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 31); + temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 30); + temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 29); + temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 28); + temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 27); + temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 26); + temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 25); + temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 24); + temp0[8] = Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 23); + temp0[9] = Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 22); + temp0[10] = Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 21); + temp0[11] = Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 20); + temp0[12] = Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 19); + temp0[13] = Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 18); + temp0[14] = Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 17); + temp0[15] = Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 16); + temp0[16] = -Unsafe.Add(ref input, 16) + Unsafe.Add(ref input, 15); + temp0[17] = -Unsafe.Add(ref input, 17) + Unsafe.Add(ref input, 14); + temp0[18] = -Unsafe.Add(ref input, 18) + Unsafe.Add(ref input, 13); + temp0[19] = -Unsafe.Add(ref input, 19) + Unsafe.Add(ref input, 12); + temp0[20] = -Unsafe.Add(ref input, 20) + Unsafe.Add(ref input, 11); + temp0[21] = -Unsafe.Add(ref input, 21) + Unsafe.Add(ref input, 10); + temp0[22] = -Unsafe.Add(ref input, 22) + Unsafe.Add(ref input, 9); + temp0[23] = -Unsafe.Add(ref input, 23) + Unsafe.Add(ref input, 8); + temp0[24] = -Unsafe.Add(ref input, 24) + Unsafe.Add(ref input, 7); + temp0[25] = -Unsafe.Add(ref input, 25) + Unsafe.Add(ref input, 6); + temp0[26] = -Unsafe.Add(ref input, 26) + Unsafe.Add(ref input, 5); + temp0[27] = -Unsafe.Add(ref input, 27) + Unsafe.Add(ref input, 4); + temp0[28] = -Unsafe.Add(ref input, 28) + Unsafe.Add(ref input, 3); + temp0[29] = -Unsafe.Add(ref input, 29) + Unsafe.Add(ref input, 2); + temp0[30] = -Unsafe.Add(ref input, 30) + Unsafe.Add(ref input, 1); + temp0[31] = -Unsafe.Add(ref input, 31) + Unsafe.Add(ref input, 0); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp1[0] = temp0[0] + temp0[15]; + temp1[1] = temp0[1] + temp0[14]; + temp1[2] = temp0[2] + temp0[13]; + temp1[3] = temp0[3] + temp0[12]; + temp1[4] = temp0[4] + temp0[11]; + temp1[5] = temp0[5] + temp0[10]; + temp1[6] = temp0[6] + temp0[9]; + temp1[7] = temp0[7] + temp0[8]; + temp1[8] = -temp0[8] + temp0[7]; + temp1[9] = -temp0[9] + temp0[6]; + temp1[10] = -temp0[10] + temp0[5]; + temp1[11] = -temp0[11] + temp0[4]; + temp1[12] = -temp0[12] + temp0[3]; + temp1[13] = -temp0[13] + temp0[2]; + temp1[14] = -temp0[14] + temp0[1]; + temp1[15] = -temp0[15] + temp0[0]; + temp1[16] = temp0[16]; + temp1[17] = temp0[17]; + temp1[18] = temp0[18]; + temp1[19] = temp0[19]; + temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[20], cospi[32], temp0[27], cosBit); + temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[21], cospi[32], temp0[26], cosBit); + temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[22], cospi[32], temp0[25], cosBit); + temp1[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[23], cospi[32], temp0[24], cosBit); + temp1[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[24], cospi[32], temp0[23], cosBit); + temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[25], cospi[32], temp0[22], cosBit); + temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[26], cospi[32], temp0[21], cosBit); + temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[27], cospi[32], temp0[20], cosBit); + temp1[28] = temp0[28]; + temp1[29] = temp0[29]; + temp1[30] = temp0[30]; + temp1[31] = temp0[31]; + + // stage 3 + temp0[0] = temp1[0] + temp1[7]; + temp0[1] = temp1[1] + temp1[6]; + temp0[2] = temp1[2] + temp1[5]; + temp0[3] = temp1[3] + temp1[4]; + temp0[4] = -temp1[4] + temp1[3]; + temp0[5] = -temp1[5] + temp1[2]; + temp0[6] = -temp1[6] + temp1[1]; + temp0[7] = -temp1[7] + temp1[0]; + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[10], cospi[32], temp1[13], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[11], cospi[32], temp1[12], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[12], cospi[32], temp1[11], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[13], cospi[32], temp1[10], cosBit); + temp0[14] = temp1[14]; + temp0[15] = temp1[15]; + temp0[16] = temp1[16] + temp1[23]; + temp0[17] = temp1[17] + temp1[22]; + temp0[18] = temp1[18] + temp1[21]; + temp0[19] = temp1[19] + temp1[20]; + temp0[20] = -temp1[20] + temp1[19]; + temp0[21] = -temp1[21] + temp1[18]; + temp0[22] = -temp1[22] + temp1[17]; + temp0[23] = -temp1[23] + temp1[16]; + temp0[24] = -temp1[24] + temp1[31]; + temp0[25] = -temp1[25] + temp1[30]; + temp0[26] = -temp1[26] + temp1[29]; + temp0[27] = -temp1[27] + temp1[28]; + temp0[28] = temp1[28] + temp1[27]; + temp0[29] = temp1[29] + temp1[26]; + temp0[30] = temp1[30] + temp1[25]; + temp0[31] = temp1[31] + temp1[24]; + + // stage 4 + temp1[0] = temp0[0] + temp0[3]; + temp1[1] = temp0[1] + temp0[2]; + temp1[2] = -temp0[2] + temp0[1]; + temp1[3] = -temp0[3] + temp0[0]; + temp1[4] = temp0[4]; + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[5], cospi[32], temp0[6], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[5], cosBit); + temp1[7] = temp0[7]; + temp1[8] = temp0[8] + temp0[11]; + temp1[9] = temp0[9] + temp0[10]; + temp1[10] = -temp0[10] + temp0[9]; + temp1[11] = -temp0[11] + temp0[8]; + temp1[12] = -temp0[12] + temp0[15]; + temp1[13] = -temp0[13] + temp0[14]; + temp1[14] = temp0[14] + temp0[13]; + temp1[15] = temp0[15] + temp0[12]; + temp1[16] = temp0[16]; + temp1[17] = temp0[17]; + temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[18], cospi[48], temp0[29], cosBit); + temp1[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[19], cospi[48], temp0[28], cosBit); + temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[20], -cospi[16], temp0[27], cosBit); + temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[21], -cospi[16], temp0[26], cosBit); + temp1[22] = temp0[22]; + temp1[23] = temp0[23]; + temp1[24] = temp0[24]; + temp1[25] = temp0[25]; + temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[26], -cospi[16], temp0[21], cosBit); + temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[27], -cospi[16], temp0[20], cosBit); + temp1[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[28], cospi[48], temp0[19], cosBit); + temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[29], cospi[48], temp0[18], cosBit); + temp1[30] = temp0[30]; + temp1[31] = temp0[31]; + + // stage 5 + temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[0], cospi[32], temp1[1], cosBit); + temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[1], cospi[32], temp1[0], cosBit); + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[2], cospi[16], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[3], -cospi[16], temp1[2], cosBit); + temp0[4] = temp1[4] + temp1[5]; + temp0[5] = -temp1[5] + temp1[4]; + temp0[6] = -temp1[6] + temp1[7]; + temp0[7] = temp1[7] + temp1[6]; + temp0[8] = temp1[8]; + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[9], cospi[48], temp1[14], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[10], -cospi[16], temp1[13], cosBit); + temp0[11] = temp1[11]; + temp0[12] = temp1[12]; + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[13], -cospi[16], temp1[10], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[14], cospi[48], temp1[9], cosBit); + temp0[15] = temp1[15]; + temp0[16] = temp1[16] + temp1[19]; + temp0[17] = temp1[17] + temp1[18]; + temp0[18] = -temp1[18] + temp1[17]; + temp0[19] = -temp1[19] + temp1[16]; + temp0[20] = -temp1[20] + temp1[23]; + temp0[21] = -temp1[21] + temp1[22]; + temp0[22] = temp1[22] + temp1[21]; + temp0[23] = temp1[23] + temp1[20]; + temp0[24] = temp1[24] + temp1[27]; + temp0[25] = temp1[25] + temp1[26]; + temp0[26] = -temp1[26] + temp1[25]; + temp0[27] = -temp1[27] + temp1[24]; + temp0[28] = -temp1[28] + temp1[31]; + temp0[29] = -temp1[29] + temp1[30]; + temp0[30] = temp1[30] + temp1[29]; + temp0[31] = temp1[31] + temp1[28]; + + // stage 6 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[4], cospi[8], temp0[7], cosBit); + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[5], cospi[40], temp0[6], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[6], -cospi[40], temp0[5], cosBit); + temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[7], -cospi[8], temp0[4], cosBit); + temp1[8] = temp0[8] + temp0[9]; + temp1[9] = -temp0[9] + temp0[8]; + temp1[10] = -temp0[10] + temp0[11]; + temp1[11] = temp0[11] + temp0[10]; + temp1[12] = temp0[12] + temp0[13]; + temp1[13] = -temp0[13] + temp0[12]; + temp1[14] = -temp0[14] + temp0[15]; + temp1[15] = temp0[15] + temp0[14]; + temp1[16] = temp0[16]; + temp1[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[17], cospi[56], temp0[30], cosBit); + temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[18], -cospi[8], temp0[29], cosBit); + temp1[19] = temp0[19]; + temp1[20] = temp0[20]; + temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[21], cospi[24], temp0[26], cosBit); + temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[22], -cospi[40], temp0[25], cosBit); + temp1[23] = temp0[23]; + temp1[24] = temp0[24]; + temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[25], -cospi[40], temp0[22], cosBit); + temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[26], cospi[24], temp0[21], cosBit); + temp1[27] = temp0[27]; + temp1[28] = temp0[28]; + temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[29], -cospi[8], temp0[18], cosBit); + temp1[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[30], cospi[56], temp0[17], cosBit); + temp1[31] = temp0[31]; + + // stage 7 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = temp1[6]; + temp0[7] = temp1[7]; + temp0[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[8], cospi[4], temp1[15], cosBit); + temp0[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[9], cospi[36], temp1[14], cosBit); + temp0[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[10], cospi[20], temp1[13], cosBit); + temp0[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[11], cospi[52], temp1[12], cosBit); + temp0[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp1[12], -cospi[52], temp1[11], cosBit); + temp0[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp1[13], -cospi[20], temp1[10], cosBit); + temp0[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp1[14], -cospi[36], temp1[9], cosBit); + temp0[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp1[15], -cospi[4], temp1[8], cosBit); + temp0[16] = temp1[16] + temp1[17]; + temp0[17] = -temp1[17] + temp1[16]; + temp0[18] = -temp1[18] + temp1[19]; + temp0[19] = temp1[19] + temp1[18]; + temp0[20] = temp1[20] + temp1[21]; + temp0[21] = -temp1[21] + temp1[20]; + temp0[22] = -temp1[22] + temp1[23]; + temp0[23] = temp1[23] + temp1[22]; + temp0[24] = temp1[24] + temp1[25]; + temp0[25] = -temp1[25] + temp1[24]; + temp0[26] = -temp1[26] + temp1[27]; + temp0[27] = temp1[27] + temp1[26]; + temp0[28] = temp1[28] + temp1[29]; + temp0[29] = -temp1[29] + temp1[28]; + temp0[30] = -temp1[30] + temp1[31]; + temp0[31] = temp1[31] + temp1[30]; + + // stage 8 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = temp0[4]; + temp1[5] = temp0[5]; + temp1[6] = temp0[6]; + temp1[7] = temp0[7]; + temp1[8] = temp0[8]; + temp1[9] = temp0[9]; + temp1[10] = temp0[10]; + temp1[11] = temp0[11]; + temp1[12] = temp0[12]; + temp1[13] = temp0[13]; + temp1[14] = temp0[14]; + temp1[15] = temp0[15]; + temp1[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp0[16], cospi[2], temp0[31], cosBit); + temp1[17] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp0[17], cospi[34], temp0[30], cosBit); + temp1[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp0[18], cospi[18], temp0[29], cosBit); + temp1[19] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp0[19], cospi[50], temp0[28], cosBit); + temp1[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp0[20], cospi[10], temp0[27], cosBit); + temp1[21] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp0[21], cospi[42], temp0[26], cosBit); + temp1[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp0[22], cospi[26], temp0[25], cosBit); + temp1[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp0[23], cospi[58], temp0[24], cosBit); + temp1[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp0[24], -cospi[58], temp0[23], cosBit); + temp1[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp0[25], -cospi[26], temp0[22], cosBit); + temp1[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp0[26], -cospi[42], temp0[21], cosBit); + temp1[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp0[27], -cospi[10], temp0[20], cosBit); + temp1[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp0[28], -cospi[50], temp0[19], cosBit); + temp1[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp0[29], -cospi[18], temp0[18], cosBit); + temp1[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp0[30], -cospi[34], temp0[17], cosBit); + temp1[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp0[31], -cospi[2], temp0[16], cosBit); + + // stage 9 + Unsafe.Add(ref output, 0) = temp1[0]; + Unsafe.Add(ref output, 1) = temp1[16]; + Unsafe.Add(ref output, 2) = temp1[8]; + Unsafe.Add(ref output, 3) = temp1[24]; + Unsafe.Add(ref output, 4) = temp1[4]; + Unsafe.Add(ref output, 5) = temp1[20]; + Unsafe.Add(ref output, 6) = temp1[12]; + Unsafe.Add(ref output, 7) = temp1[28]; + Unsafe.Add(ref output, 8) = temp1[2]; + Unsafe.Add(ref output, 9) = temp1[18]; + Unsafe.Add(ref output, 10) = temp1[10]; + Unsafe.Add(ref output, 11) = temp1[26]; + Unsafe.Add(ref output, 12) = temp1[6]; + Unsafe.Add(ref output, 13) = temp1[22]; + Unsafe.Add(ref output, 14) = temp1[14]; + Unsafe.Add(ref output, 15) = temp1[30]; + Unsafe.Add(ref output, 16) = temp1[1]; + Unsafe.Add(ref output, 17) = temp1[17]; + Unsafe.Add(ref output, 18) = temp1[9]; + Unsafe.Add(ref output, 19) = temp1[25]; + Unsafe.Add(ref output, 20) = temp1[5]; + Unsafe.Add(ref output, 21) = temp1[21]; + Unsafe.Add(ref output, 22) = temp1[13]; + Unsafe.Add(ref output, 23) = temp1[29]; + Unsafe.Add(ref output, 24) = temp1[3]; + Unsafe.Add(ref output, 25) = temp1[19]; + Unsafe.Add(ref output, 26) = temp1[11]; + Unsafe.Add(ref output, 27) = temp1[27]; + Unsafe.Add(ref output, 28) = temp1[7]; + Unsafe.Add(ref output, 29) = temp1[23]; + Unsafe.Add(ref output, 30) = temp1[15]; + Unsafe.Add(ref output, 31) = temp1[31]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs index 15c3f3ffe9..41a5234594 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs @@ -33,10 +33,10 @@ private static void TransformScalar(ref int input, ref int output, int cosBit) output3 = -Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 0); // stage 2 - step0 = HalfBtf(cospi[32], output, cospi[32], output1, cosBit); - step1 = HalfBtf(-cospi[32], output1, cospi[32], output, cosBit); - step2 = HalfBtf(cospi[48], output2, cospi[16], output3, cosBit); - step3 = HalfBtf(cospi[48], output3, -cospi[16], output2, cosBit); + step0 = HalfButterfly(cospi[32], output, cospi[32], output1, cosBit); + step1 = HalfButterfly(-cospi[32], output1, cospi[32], output, cosBit); + step2 = HalfButterfly(cospi[48], output2, cospi[16], output3, cosBit); + step3 = HalfButterfly(cospi[48], output3, -cospi[16], output2, cosBit); // stage 3 output = step0; @@ -45,7 +45,7 @@ private static void TransformScalar(ref int input, ref int output, int cosBit) output3 = step3; } - private static int HalfBtf(int w0, int in0, int w1, int in1, int bit) + internal static int HalfButterfly(int w0, int in0, int w1, int in1, int bit) { long result64 = (long)(w0 * in0) + (w1 * in1); long intermediate = result64 + (1L << (bit - 1)); diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs index 5dcc4ab7f0..c9149f2973 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs @@ -1,10 +1,747 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct64Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransforScalar(ref input, ref output, cosBit); + + private static void TransforScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[64]; + Span temp1 = stackalloc int[64]; + + // stage 0; + + // stage 1; + temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 63); + temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 62); + temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 61); + temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 60); + temp0[4] = Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 59); + temp0[5] = Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 58); + temp0[6] = Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 57); + temp0[7] = Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 56); + temp0[8] = Unsafe.Add(ref input, 8) + Unsafe.Add(ref input, 55); + temp0[9] = Unsafe.Add(ref input, 9) + Unsafe.Add(ref input, 54); + temp0[10] = Unsafe.Add(ref input, 10) + Unsafe.Add(ref input, 53); + temp0[11] = Unsafe.Add(ref input, 11) + Unsafe.Add(ref input, 52); + temp0[12] = Unsafe.Add(ref input, 12) + Unsafe.Add(ref input, 51); + temp0[13] = Unsafe.Add(ref input, 13) + Unsafe.Add(ref input, 50); + temp0[14] = Unsafe.Add(ref input, 14) + Unsafe.Add(ref input, 49); + temp0[15] = Unsafe.Add(ref input, 15) + Unsafe.Add(ref input, 48); + temp0[16] = Unsafe.Add(ref input, 16) + Unsafe.Add(ref input, 47); + temp0[17] = Unsafe.Add(ref input, 17) + Unsafe.Add(ref input, 46); + temp0[18] = Unsafe.Add(ref input, 18) + Unsafe.Add(ref input, 45); + temp0[19] = Unsafe.Add(ref input, 19) + Unsafe.Add(ref input, 44); + temp0[20] = Unsafe.Add(ref input, 20) + Unsafe.Add(ref input, 43); + temp0[21] = Unsafe.Add(ref input, 21) + Unsafe.Add(ref input, 42); + temp0[22] = Unsafe.Add(ref input, 22) + Unsafe.Add(ref input, 41); + temp0[23] = Unsafe.Add(ref input, 23) + Unsafe.Add(ref input, 40); + temp0[24] = Unsafe.Add(ref input, 24) + Unsafe.Add(ref input, 39); + temp0[25] = Unsafe.Add(ref input, 25) + Unsafe.Add(ref input, 38); + temp0[26] = Unsafe.Add(ref input, 26) + Unsafe.Add(ref input, 37); + temp0[27] = Unsafe.Add(ref input, 27) + Unsafe.Add(ref input, 36); + temp0[28] = Unsafe.Add(ref input, 28) + Unsafe.Add(ref input, 35); + temp0[29] = Unsafe.Add(ref input, 29) + Unsafe.Add(ref input, 34); + temp0[30] = Unsafe.Add(ref input, 30) + Unsafe.Add(ref input, 33); + temp0[31] = Unsafe.Add(ref input, 31) + Unsafe.Add(ref input, 32); + temp0[32] = -Unsafe.Add(ref input, 32) + Unsafe.Add(ref input, 31); + temp0[33] = -Unsafe.Add(ref input, 33) + Unsafe.Add(ref input, 30); + temp0[34] = -Unsafe.Add(ref input, 34) + Unsafe.Add(ref input, 29); + temp0[35] = -Unsafe.Add(ref input, 35) + Unsafe.Add(ref input, 28); + temp0[36] = -Unsafe.Add(ref input, 36) + Unsafe.Add(ref input, 27); + temp0[37] = -Unsafe.Add(ref input, 37) + Unsafe.Add(ref input, 26); + temp0[38] = -Unsafe.Add(ref input, 38) + Unsafe.Add(ref input, 25); + temp0[39] = -Unsafe.Add(ref input, 39) + Unsafe.Add(ref input, 24); + temp0[40] = -Unsafe.Add(ref input, 40) + Unsafe.Add(ref input, 23); + temp0[41] = -Unsafe.Add(ref input, 41) + Unsafe.Add(ref input, 22); + temp0[42] = -Unsafe.Add(ref input, 42) + Unsafe.Add(ref input, 21); + temp0[43] = -Unsafe.Add(ref input, 43) + Unsafe.Add(ref input, 20); + temp0[44] = -Unsafe.Add(ref input, 44) + Unsafe.Add(ref input, 19); + temp0[45] = -Unsafe.Add(ref input, 45) + Unsafe.Add(ref input, 18); + temp0[46] = -Unsafe.Add(ref input, 46) + Unsafe.Add(ref input, 17); + temp0[47] = -Unsafe.Add(ref input, 47) + Unsafe.Add(ref input, 16); + temp0[48] = -Unsafe.Add(ref input, 48) + Unsafe.Add(ref input, 15); + temp0[49] = -Unsafe.Add(ref input, 49) + Unsafe.Add(ref input, 14); + temp0[50] = -Unsafe.Add(ref input, 50) + Unsafe.Add(ref input, 13); + temp0[51] = -Unsafe.Add(ref input, 51) + Unsafe.Add(ref input, 12); + temp0[52] = -Unsafe.Add(ref input, 52) + Unsafe.Add(ref input, 11); + temp0[53] = -Unsafe.Add(ref input, 53) + Unsafe.Add(ref input, 10); + temp0[54] = -Unsafe.Add(ref input, 54) + Unsafe.Add(ref input, 9); + temp0[55] = -Unsafe.Add(ref input, 55) + Unsafe.Add(ref input, 8); + temp0[56] = -Unsafe.Add(ref input, 56) + Unsafe.Add(ref input, 7); + temp0[57] = -Unsafe.Add(ref input, 57) + Unsafe.Add(ref input, 6); + temp0[58] = -Unsafe.Add(ref input, 58) + Unsafe.Add(ref input, 5); + temp0[59] = -Unsafe.Add(ref input, 59) + Unsafe.Add(ref input, 4); + temp0[60] = -Unsafe.Add(ref input, 60) + Unsafe.Add(ref input, 3); + temp0[61] = -Unsafe.Add(ref input, 61) + Unsafe.Add(ref input, 2); + temp0[62] = -Unsafe.Add(ref input, 62) + Unsafe.Add(ref input, 1); + temp0[63] = -Unsafe.Add(ref input, 63) + Unsafe.Add(ref input, 0); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp1[0] = temp0[0] + temp0[31]; + temp1[1] = temp0[1] + temp0[30]; + temp1[2] = temp0[2] + temp0[29]; + temp1[3] = temp0[3] + temp0[28]; + temp1[4] = temp0[4] + temp0[27]; + temp1[5] = temp0[5] + temp0[26]; + temp1[6] = temp0[6] + temp0[25]; + temp1[7] = temp0[7] + temp0[24]; + temp1[8] = temp0[8] + temp0[23]; + temp1[9] = temp0[9] + temp0[22]; + temp1[10] = temp0[10] + temp0[21]; + temp1[11] = temp0[11] + temp0[20]; + temp1[12] = temp0[12] + temp0[19]; + temp1[13] = temp0[13] + temp0[18]; + temp1[14] = temp0[14] + temp0[17]; + temp1[15] = temp0[15] + temp0[16]; + temp1[16] = -temp0[16] + temp0[15]; + temp1[17] = -temp0[17] + temp0[14]; + temp1[18] = -temp0[18] + temp0[13]; + temp1[19] = -temp0[19] + temp0[12]; + temp1[20] = -temp0[20] + temp0[11]; + temp1[21] = -temp0[21] + temp0[10]; + temp1[22] = -temp0[22] + temp0[9]; + temp1[23] = -temp0[23] + temp0[8]; + temp1[24] = -temp0[24] + temp0[7]; + temp1[25] = -temp0[25] + temp0[6]; + temp1[26] = -temp0[26] + temp0[5]; + temp1[27] = -temp0[27] + temp0[4]; + temp1[28] = -temp0[28] + temp0[3]; + temp1[29] = -temp0[29] + temp0[2]; + temp1[30] = -temp0[30] + temp0[1]; + temp1[31] = -temp0[31] + temp0[0]; + temp1[32] = temp0[32]; + temp1[33] = temp0[33]; + temp1[34] = temp0[34]; + temp1[35] = temp0[35]; + temp1[36] = temp0[36]; + temp1[37] = temp0[37]; + temp1[38] = temp0[38]; + temp1[39] = temp0[39]; + temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[40], cospi[32], temp0[55], cosBit); + temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[41], cospi[32], temp0[54], cosBit); + temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[42], cospi[32], temp0[53], cosBit); + temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[43], cospi[32], temp0[52], cosBit); + temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[44], cospi[32], temp0[51], cosBit); + temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[45], cospi[32], temp0[50], cosBit); + temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[46], cospi[32], temp0[49], cosBit); + temp1[47] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[47], cospi[32], temp0[48], cosBit); + temp1[48] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[48], cospi[32], temp0[47], cosBit); + temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[49], cospi[32], temp0[46], cosBit); + temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[50], cospi[32], temp0[45], cosBit); + temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[51], cospi[32], temp0[44], cosBit); + temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[52], cospi[32], temp0[43], cosBit); + temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[53], cospi[32], temp0[42], cosBit); + temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[54], cospi[32], temp0[41], cosBit); + temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[55], cospi[32], temp0[40], cosBit); + temp1[56] = temp0[56]; + temp1[57] = temp0[57]; + temp1[58] = temp0[58]; + temp1[59] = temp0[59]; + temp1[60] = temp0[60]; + temp1[61] = temp0[61]; + temp1[62] = temp0[62]; + temp1[63] = temp0[63]; + + // stage 3 + temp0[0] = temp1[0] + temp1[15]; + temp0[1] = temp1[1] + temp1[14]; + temp0[2] = temp1[2] + temp1[13]; + temp0[3] = temp1[3] + temp1[12]; + temp0[4] = temp1[4] + temp1[11]; + temp0[5] = temp1[5] + temp1[10]; + temp0[6] = temp1[6] + temp1[9]; + temp0[7] = temp1[7] + temp1[8]; + temp0[8] = -temp1[8] + temp1[7]; + temp0[9] = -temp1[9] + temp1[6]; + temp0[10] = -temp1[10] + temp1[5]; + temp0[11] = -temp1[11] + temp1[4]; + temp0[12] = -temp1[12] + temp1[3]; + temp0[13] = -temp1[13] + temp1[2]; + temp0[14] = -temp1[14] + temp1[1]; + temp0[15] = -temp1[15] + temp1[0]; + temp0[16] = temp1[16]; + temp0[17] = temp1[17]; + temp0[18] = temp1[18]; + temp0[19] = temp1[19]; + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[20], cospi[32], temp1[27], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[21], cospi[32], temp1[26], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[22], cospi[32], temp1[25], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[23], cospi[32], temp1[24], cosBit); + temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[24], cospi[32], temp1[23], cosBit); + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[25], cospi[32], temp1[22], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[26], cospi[32], temp1[21], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[27], cospi[32], temp1[20], cosBit); + temp0[28] = temp1[28]; + temp0[29] = temp1[29]; + temp0[30] = temp1[30]; + temp0[31] = temp1[31]; + temp0[32] = temp1[32] + temp1[47]; + temp0[33] = temp1[33] + temp1[46]; + temp0[34] = temp1[34] + temp1[45]; + temp0[35] = temp1[35] + temp1[44]; + temp0[36] = temp1[36] + temp1[43]; + temp0[37] = temp1[37] + temp1[42]; + temp0[38] = temp1[38] + temp1[41]; + temp0[39] = temp1[39] + temp1[40]; + temp0[40] = -temp1[40] + temp1[39]; + temp0[41] = -temp1[41] + temp1[38]; + temp0[42] = -temp1[42] + temp1[37]; + temp0[43] = -temp1[43] + temp1[36]; + temp0[44] = -temp1[44] + temp1[35]; + temp0[45] = -temp1[45] + temp1[34]; + temp0[46] = -temp1[46] + temp1[33]; + temp0[47] = -temp1[47] + temp1[32]; + temp0[48] = -temp1[48] + temp1[63]; + temp0[49] = -temp1[49] + temp1[62]; + temp0[50] = -temp1[50] + temp1[61]; + temp0[51] = -temp1[51] + temp1[60]; + temp0[52] = -temp1[52] + temp1[59]; + temp0[53] = -temp1[53] + temp1[58]; + temp0[54] = -temp1[54] + temp1[57]; + temp0[55] = -temp1[55] + temp1[56]; + temp0[56] = temp1[56] + temp1[55]; + temp0[57] = temp1[57] + temp1[54]; + temp0[58] = temp1[58] + temp1[53]; + temp0[59] = temp1[59] + temp1[52]; + temp0[60] = temp1[60] + temp1[51]; + temp0[61] = temp1[61] + temp1[50]; + temp0[62] = temp1[62] + temp1[49]; + temp0[63] = temp1[63] + temp1[48]; + + // stage 4 + temp1[0] = temp0[0] + temp0[7]; + temp1[1] = temp0[1] + temp0[6]; + temp1[2] = temp0[2] + temp0[5]; + temp1[3] = temp0[3] + temp0[4]; + temp1[4] = -temp0[4] + temp0[3]; + temp1[5] = -temp0[5] + temp0[2]; + temp1[6] = -temp0[6] + temp0[1]; + temp1[7] = -temp0[7] + temp0[0]; + temp1[8] = temp0[8]; + temp1[9] = temp0[9]; + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[10], cospi[32], temp0[13], cosBit); + temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[11], cospi[32], temp0[12], cosBit); + temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[12], cospi[32], temp0[11], cosBit); + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[13], cospi[32], temp0[10], cosBit); + temp1[14] = temp0[14]; + temp1[15] = temp0[15]; + temp1[16] = temp0[16] + temp0[23]; + temp1[17] = temp0[17] + temp0[22]; + temp1[18] = temp0[18] + temp0[21]; + temp1[19] = temp0[19] + temp0[20]; + temp1[20] = -temp0[20] + temp0[19]; + temp1[21] = -temp0[21] + temp0[18]; + temp1[22] = -temp0[22] + temp0[17]; + temp1[23] = -temp0[23] + temp0[16]; + temp1[24] = -temp0[24] + temp0[31]; + temp1[25] = -temp0[25] + temp0[30]; + temp1[26] = -temp0[26] + temp0[29]; + temp1[27] = -temp0[27] + temp0[28]; + temp1[28] = temp0[28] + temp0[27]; + temp1[29] = temp0[29] + temp0[26]; + temp1[30] = temp0[30] + temp0[25]; + temp1[31] = temp0[31] + temp0[24]; + temp1[32] = temp0[32]; + temp1[33] = temp0[33]; + temp1[34] = temp0[34]; + temp1[35] = temp0[35]; + temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[36], cospi[48], temp0[59], cosBit); + temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[37], cospi[48], temp0[58], cosBit); + temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[38], cospi[48], temp0[57], cosBit); + temp1[39] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[39], cospi[48], temp0[56], cosBit); + temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[40], -cospi[16], temp0[55], cosBit); + temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[41], -cospi[16], temp0[54], cosBit); + temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[42], -cospi[16], temp0[53], cosBit); + temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[43], -cospi[16], temp0[52], cosBit); + temp1[44] = temp0[44]; + temp1[45] = temp0[45]; + temp1[46] = temp0[46]; + temp1[47] = temp0[47]; + temp1[48] = temp0[48]; + temp1[49] = temp0[49]; + temp1[50] = temp0[50]; + temp1[51] = temp0[51]; + temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[52], -cospi[16], temp0[43], cosBit); + temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[53], -cospi[16], temp0[42], cosBit); + temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[54], -cospi[16], temp0[41], cosBit); + temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[55], -cospi[16], temp0[40], cosBit); + temp1[56] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[56], cospi[48], temp0[39], cosBit); + temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[57], cospi[48], temp0[38], cosBit); + temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[58], cospi[48], temp0[37], cosBit); + temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[59], cospi[48], temp0[36], cosBit); + temp1[60] = temp0[60]; + temp1[61] = temp0[61]; + temp1[62] = temp0[62]; + temp1[63] = temp0[63]; + + // stage 5 + temp0[0] = temp1[0] + temp1[3]; + temp0[1] = temp1[1] + temp1[2]; + temp0[2] = -temp1[2] + temp1[1]; + temp0[3] = -temp1[3] + temp1[0]; + temp0[4] = temp1[4]; + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[5], cospi[32], temp1[6], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[6], cospi[32], temp1[5], cosBit); + temp0[7] = temp1[7]; + temp0[8] = temp1[8] + temp1[11]; + temp0[9] = temp1[9] + temp1[10]; + temp0[10] = -temp1[10] + temp1[9]; + temp0[11] = -temp1[11] + temp1[8]; + temp0[12] = -temp1[12] + temp1[15]; + temp0[13] = -temp1[13] + temp1[14]; + temp0[14] = temp1[14] + temp1[13]; + temp0[15] = temp1[15] + temp1[12]; + temp0[16] = temp1[16]; + temp0[17] = temp1[17]; + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[18], cospi[48], temp1[29], cosBit); + temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp1[19], cospi[48], temp1[28], cosBit); + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[20], -cospi[16], temp1[27], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp1[21], -cospi[16], temp1[26], cosBit); + temp0[22] = temp1[22]; + temp0[23] = temp1[23]; + temp0[24] = temp1[24]; + temp0[25] = temp1[25]; + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[26], -cospi[16], temp1[21], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[27], -cospi[16], temp1[20], cosBit); + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[28], cospi[48], temp1[19], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp1[29], cospi[48], temp1[18], cosBit); + temp0[30] = temp1[30]; + temp0[31] = temp1[31]; + temp0[32] = temp1[32] + temp1[39]; + temp0[33] = temp1[33] + temp1[38]; + temp0[34] = temp1[34] + temp1[37]; + temp0[35] = temp1[35] + temp1[36]; + temp0[36] = -temp1[36] + temp1[35]; + temp0[37] = -temp1[37] + temp1[34]; + temp0[38] = -temp1[38] + temp1[33]; + temp0[39] = -temp1[39] + temp1[32]; + temp0[40] = -temp1[40] + temp1[47]; + temp0[41] = -temp1[41] + temp1[46]; + temp0[42] = -temp1[42] + temp1[45]; + temp0[43] = -temp1[43] + temp1[44]; + temp0[44] = temp1[44] + temp1[43]; + temp0[45] = temp1[45] + temp1[42]; + temp0[46] = temp1[46] + temp1[41]; + temp0[47] = temp1[47] + temp1[40]; + temp0[48] = temp1[48] + temp1[55]; + temp0[49] = temp1[49] + temp1[54]; + temp0[50] = temp1[50] + temp1[53]; + temp0[51] = temp1[51] + temp1[52]; + temp0[52] = -temp1[52] + temp1[51]; + temp0[53] = -temp1[53] + temp1[50]; + temp0[54] = -temp1[54] + temp1[49]; + temp0[55] = -temp1[55] + temp1[48]; + temp0[56] = -temp1[56] + temp1[63]; + temp0[57] = -temp1[57] + temp1[62]; + temp0[58] = -temp1[58] + temp1[61]; + temp0[59] = -temp1[59] + temp1[60]; + temp0[60] = temp1[60] + temp1[59]; + temp0[61] = temp1[61] + temp1[58]; + temp0[62] = temp1[62] + temp1[57]; + temp0[63] = temp1[63] + temp1[56]; + + // stage 6 + temp1[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit); + temp1[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[1], cospi[32], temp0[0], cosBit); + temp1[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[2], cospi[16], temp0[3], cosBit); + temp1[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[3], -cospi[16], temp0[2], cosBit); + temp1[4] = temp0[4] + temp0[5]; + temp1[5] = -temp0[5] + temp0[4]; + temp1[6] = -temp0[6] + temp0[7]; + temp1[7] = temp0[7] + temp0[6]; + temp1[8] = temp0[8]; + temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[16], temp0[9], cospi[48], temp0[14], cosBit); + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[48], temp0[10], -cospi[16], temp0[13], cosBit); + temp1[11] = temp0[11]; + temp1[12] = temp0[12]; + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp0[13], -cospi[16], temp0[10], cosBit); + temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[16], temp0[14], cospi[48], temp0[9], cosBit); + temp1[15] = temp0[15]; + temp1[16] = temp0[16] + temp0[19]; + temp1[17] = temp0[17] + temp0[18]; + temp1[18] = -temp0[18] + temp0[17]; + temp1[19] = -temp0[19] + temp0[16]; + temp1[20] = -temp0[20] + temp0[23]; + temp1[21] = -temp0[21] + temp0[22]; + temp1[22] = temp0[22] + temp0[21]; + temp1[23] = temp0[23] + temp0[20]; + temp1[24] = temp0[24] + temp0[27]; + temp1[25] = temp0[25] + temp0[26]; + temp1[26] = -temp0[26] + temp0[25]; + temp1[27] = -temp0[27] + temp0[24]; + temp1[28] = -temp0[28] + temp0[31]; + temp1[29] = -temp0[29] + temp0[30]; + temp1[30] = temp0[30] + temp0[29]; + temp1[31] = temp0[31] + temp0[28]; + temp1[32] = temp0[32]; + temp1[33] = temp0[33]; + temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[34], cospi[56], temp0[61], cosBit); + temp1[35] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp0[35], cospi[56], temp0[60], cosBit); + temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[36], -cospi[8], temp0[59], cosBit); + temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp0[37], -cospi[8], temp0[58], cosBit); + temp1[38] = temp0[38]; + temp1[39] = temp0[39]; + temp1[40] = temp0[40]; + temp1[41] = temp0[41]; + temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[42], cospi[24], temp0[53], cosBit); + temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp0[43], cospi[24], temp0[52], cosBit); + temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[44], -cospi[40], temp0[51], cosBit); + temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp0[45], -cospi[40], temp0[50], cosBit); + temp1[46] = temp0[46]; + temp1[47] = temp0[47]; + temp1[48] = temp0[48]; + temp1[49] = temp0[49]; + temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[50], -cospi[40], temp0[45], cosBit); + temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[51], -cospi[40], temp0[44], cosBit); + temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[52], cospi[24], temp0[43], cosBit); + temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp0[53], cospi[24], temp0[42], cosBit); + temp1[54] = temp0[54]; + temp1[55] = temp0[55]; + temp1[56] = temp0[56]; + temp1[57] = temp0[57]; + temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[58], -cospi[8], temp0[37], cosBit); + temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[59], -cospi[8], temp0[36], cosBit); + temp1[60] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[60], cospi[56], temp0[35], cosBit); + temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp0[61], cospi[56], temp0[34], cosBit); + temp1[62] = temp0[62]; + temp1[63] = temp0[63]; + + // stage 7 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[4], cospi[8], temp1[7], cosBit); + temp0[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[5], cospi[40], temp1[6], cosBit); + temp0[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[6], -cospi[40], temp1[5], cosBit); + temp0[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[7], -cospi[8], temp1[4], cosBit); + temp0[8] = temp1[8] + temp1[9]; + temp0[9] = -temp1[9] + temp1[8]; + temp0[10] = -temp1[10] + temp1[11]; + temp0[11] = temp1[11] + temp1[10]; + temp0[12] = temp1[12] + temp1[13]; + temp0[13] = -temp1[13] + temp1[12]; + temp0[14] = -temp1[14] + temp1[15]; + temp0[15] = temp1[15] + temp1[14]; + temp0[16] = temp1[16]; + temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[8], temp1[17], cospi[56], temp1[30], cosBit); + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[56], temp1[18], -cospi[8], temp1[29], cosBit); + temp0[19] = temp1[19]; + temp0[20] = temp1[20]; + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[40], temp1[21], cospi[24], temp1[26], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[24], temp1[22], -cospi[40], temp1[25], cosBit); + temp0[23] = temp1[23]; + temp0[24] = temp1[24]; + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp1[25], -cospi[40], temp1[22], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[40], temp1[26], cospi[24], temp1[21], cosBit); + temp0[27] = temp1[27]; + temp0[28] = temp1[28]; + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp1[29], -cospi[8], temp1[18], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[8], temp1[30], cospi[56], temp1[17], cosBit); + temp0[31] = temp1[31]; + temp0[32] = temp1[32] + temp1[35]; + temp0[33] = temp1[33] + temp1[34]; + temp0[34] = -temp1[34] + temp1[33]; + temp0[35] = -temp1[35] + temp1[32]; + temp0[36] = -temp1[36] + temp1[39]; + temp0[37] = -temp1[37] + temp1[38]; + temp0[38] = temp1[38] + temp1[37]; + temp0[39] = temp1[39] + temp1[36]; + temp0[40] = temp1[40] + temp1[43]; + temp0[41] = temp1[41] + temp1[42]; + temp0[42] = -temp1[42] + temp1[41]; + temp0[43] = -temp1[43] + temp1[40]; + temp0[44] = -temp1[44] + temp1[47]; + temp0[45] = -temp1[45] + temp1[46]; + temp0[46] = temp1[46] + temp1[45]; + temp0[47] = temp1[47] + temp1[44]; + temp0[48] = temp1[48] + temp1[51]; + temp0[49] = temp1[49] + temp1[50]; + temp0[50] = -temp1[50] + temp1[49]; + temp0[51] = -temp1[51] + temp1[48]; + temp0[52] = -temp1[52] + temp1[55]; + temp0[53] = -temp1[53] + temp1[54]; + temp0[54] = temp1[54] + temp1[53]; + temp0[55] = temp1[55] + temp1[52]; + temp0[56] = temp1[56] + temp1[59]; + temp0[57] = temp1[57] + temp1[58]; + temp0[58] = -temp1[58] + temp1[57]; + temp0[59] = -temp1[59] + temp1[56]; + temp0[60] = -temp1[60] + temp1[63]; + temp0[61] = -temp1[61] + temp1[62]; + temp0[62] = temp1[62] + temp1[61]; + temp0[63] = temp1[63] + temp1[60]; + + // stage 8 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = temp0[4]; + temp1[5] = temp0[5]; + temp1[6] = temp0[6]; + temp1[7] = temp0[7]; + temp1[8] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[8], cospi[4], temp0[15], cosBit); + temp1[9] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[9], cospi[36], temp0[14], cosBit); + temp1[10] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[10], cospi[20], temp0[13], cosBit); + temp1[11] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[11], cospi[52], temp0[12], cosBit); + temp1[12] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[12], -cospi[52], temp0[11], cosBit); + temp1[13] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[13], -cospi[20], temp0[10], cosBit); + temp1[14] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[14], -cospi[36], temp0[9], cosBit); + temp1[15] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[15], -cospi[4], temp0[8], cosBit); + temp1[16] = temp0[16] + temp0[17]; + temp1[17] = -temp0[17] + temp0[16]; + temp1[18] = -temp0[18] + temp0[19]; + temp1[19] = temp0[19] + temp0[18]; + temp1[20] = temp0[20] + temp0[21]; + temp1[21] = -temp0[21] + temp0[20]; + temp1[22] = -temp0[22] + temp0[23]; + temp1[23] = temp0[23] + temp0[22]; + temp1[24] = temp0[24] + temp0[25]; + temp1[25] = -temp0[25] + temp0[24]; + temp1[26] = -temp0[26] + temp0[27]; + temp1[27] = temp0[27] + temp0[26]; + temp1[28] = temp0[28] + temp0[29]; + temp1[29] = -temp0[29] + temp0[28]; + temp1[30] = -temp0[30] + temp0[31]; + temp1[31] = temp0[31] + temp0[30]; + temp1[32] = temp0[32]; + temp1[33] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[4], temp0[33], cospi[60], temp0[62], cosBit); + temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[60], temp0[34], -cospi[4], temp0[61], cosBit); + temp1[35] = temp0[35]; + temp1[36] = temp0[36]; + temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[36], temp0[37], cospi[28], temp0[58], cosBit); + temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[28], temp0[38], -cospi[36], temp0[57], cosBit); + temp1[39] = temp0[39]; + temp1[40] = temp0[40]; + temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[20], temp0[41], cospi[44], temp0[54], cosBit); + temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[44], temp0[42], -cospi[20], temp0[53], cosBit); + temp1[43] = temp0[43]; + temp1[44] = temp0[44]; + temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[52], temp0[45], cospi[12], temp0[50], cosBit); + temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[12], temp0[46], -cospi[52], temp0[49], cosBit); + temp1[47] = temp0[47]; + temp1[48] = temp0[48]; + temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[12], temp0[49], -cospi[52], temp0[46], cosBit); + temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[52], temp0[50], cospi[12], temp0[45], cosBit); + temp1[51] = temp0[51]; + temp1[52] = temp0[52]; + temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[44], temp0[53], -cospi[20], temp0[42], cosBit); + temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[20], temp0[54], cospi[44], temp0[41], cosBit); + temp1[55] = temp0[55]; + temp1[56] = temp0[56]; + temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[28], temp0[57], -cospi[36], temp0[38], cosBit); + temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[36], temp0[58], cospi[28], temp0[37], cosBit); + temp1[59] = temp0[59]; + temp1[60] = temp0[60]; + temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[60], temp0[61], -cospi[4], temp0[34], cosBit); + temp1[62] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[4], temp0[62], cospi[60], temp0[33], cosBit); + temp1[63] = temp0[63]; + + // stage 9 + temp0[0] = temp1[0]; + temp0[1] = temp1[1]; + temp0[2] = temp1[2]; + temp0[3] = temp1[3]; + temp0[4] = temp1[4]; + temp0[5] = temp1[5]; + temp0[6] = temp1[6]; + temp0[7] = temp1[7]; + temp0[8] = temp1[8]; + temp0[9] = temp1[9]; + temp0[10] = temp1[10]; + temp0[11] = temp1[11]; + temp0[12] = temp1[12]; + temp0[13] = temp1[13]; + temp0[14] = temp1[14]; + temp0[15] = temp1[15]; + temp0[16] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[16], cospi[2], temp1[31], cosBit); + temp0[17] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[17], cospi[34], temp1[30], cosBit); + temp0[18] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[18], cospi[18], temp1[29], cosBit); + temp0[19] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[19], cospi[50], temp1[28], cosBit); + temp0[20] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[20], cospi[10], temp1[27], cosBit); + temp0[21] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[21], cospi[42], temp1[26], cosBit); + temp0[22] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[22], cospi[26], temp1[25], cosBit); + temp0[23] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[23], cospi[58], temp1[24], cosBit); + temp0[24] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[6], temp1[24], -cospi[58], temp1[23], cosBit); + temp0[25] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[38], temp1[25], -cospi[26], temp1[22], cosBit); + temp0[26] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[22], temp1[26], -cospi[42], temp1[21], cosBit); + temp0[27] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[54], temp1[27], -cospi[10], temp1[20], cosBit); + temp0[28] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[14], temp1[28], -cospi[50], temp1[19], cosBit); + temp0[29] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[46], temp1[29], -cospi[18], temp1[18], cosBit); + temp0[30] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[30], temp1[30], -cospi[34], temp1[17], cosBit); + temp0[31] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[62], temp1[31], -cospi[2], temp1[16], cosBit); + temp0[32] = temp1[32] + temp1[33]; + temp0[33] = -temp1[33] + temp1[32]; + temp0[34] = -temp1[34] + temp1[35]; + temp0[35] = temp1[35] + temp1[34]; + temp0[36] = temp1[36] + temp1[37]; + temp0[37] = -temp1[37] + temp1[36]; + temp0[38] = -temp1[38] + temp1[39]; + temp0[39] = temp1[39] + temp1[38]; + temp0[40] = temp1[40] + temp1[41]; + temp0[41] = -temp1[41] + temp1[40]; + temp0[42] = -temp1[42] + temp1[43]; + temp0[43] = temp1[43] + temp1[42]; + temp0[44] = temp1[44] + temp1[45]; + temp0[45] = -temp1[45] + temp1[44]; + temp0[46] = -temp1[46] + temp1[47]; + temp0[47] = temp1[47] + temp1[46]; + temp0[48] = temp1[48] + temp1[49]; + temp0[49] = -temp1[49] + temp1[48]; + temp0[50] = -temp1[50] + temp1[51]; + temp0[51] = temp1[51] + temp1[50]; + temp0[52] = temp1[52] + temp1[53]; + temp0[53] = -temp1[53] + temp1[52]; + temp0[54] = -temp1[54] + temp1[55]; + temp0[55] = temp1[55] + temp1[54]; + temp0[56] = temp1[56] + temp1[57]; + temp0[57] = -temp1[57] + temp1[56]; + temp0[58] = -temp1[58] + temp1[59]; + temp0[59] = temp1[59] + temp1[58]; + temp0[60] = temp1[60] + temp1[61]; + temp0[61] = -temp1[61] + temp1[60]; + temp0[62] = -temp1[62] + temp1[63]; + temp0[63] = temp1[63] + temp1[62]; + + // stage 10 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = temp0[4]; + temp1[5] = temp0[5]; + temp1[6] = temp0[6]; + temp1[7] = temp0[7]; + temp1[8] = temp0[8]; + temp1[9] = temp0[9]; + temp1[10] = temp0[10]; + temp1[11] = temp0[11]; + temp1[12] = temp0[12]; + temp1[13] = temp0[13]; + temp1[14] = temp0[14]; + temp1[15] = temp0[15]; + temp1[16] = temp0[16]; + temp1[17] = temp0[17]; + temp1[18] = temp0[18]; + temp1[19] = temp0[19]; + temp1[20] = temp0[20]; + temp1[21] = temp0[21]; + temp1[22] = temp0[22]; + temp1[23] = temp0[23]; + temp1[24] = temp0[24]; + temp1[25] = temp0[25]; + temp1[26] = temp0[26]; + temp1[27] = temp0[27]; + temp1[28] = temp0[28]; + temp1[29] = temp0[29]; + temp1[30] = temp0[30]; + temp1[31] = temp0[31]; + temp1[32] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[63], temp0[32], cospi[1], temp0[63], cosBit); + temp1[33] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[31], temp0[33], cospi[33], temp0[62], cosBit); + temp1[34] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[47], temp0[34], cospi[17], temp0[61], cosBit); + temp1[35] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[15], temp0[35], cospi[49], temp0[60], cosBit); + temp1[36] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[55], temp0[36], cospi[9], temp0[59], cosBit); + temp1[37] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[23], temp0[37], cospi[41], temp0[58], cosBit); + temp1[38] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[39], temp0[38], cospi[25], temp0[57], cosBit); + temp1[39] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[7], temp0[39], cospi[57], temp0[56], cosBit); + temp1[40] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[59], temp0[40], cospi[5], temp0[55], cosBit); + temp1[41] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[27], temp0[41], cospi[37], temp0[54], cosBit); + temp1[42] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[43], temp0[42], cospi[21], temp0[53], cosBit); + temp1[43] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[11], temp0[43], cospi[53], temp0[52], cosBit); + temp1[44] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[51], temp0[44], cospi[13], temp0[51], cosBit); + temp1[45] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[19], temp0[45], cospi[45], temp0[50], cosBit); + temp1[46] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[35], temp0[46], cospi[29], temp0[49], cosBit); + temp1[47] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[3], temp0[47], cospi[61], temp0[48], cosBit); + temp1[48] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[3], temp0[48], -cospi[61], temp0[47], cosBit); + temp1[49] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[35], temp0[49], -cospi[29], temp0[46], cosBit); + temp1[50] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[19], temp0[50], -cospi[45], temp0[45], cosBit); + temp1[51] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[51], temp0[51], -cospi[13], temp0[44], cosBit); + temp1[52] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[11], temp0[52], -cospi[53], temp0[43], cosBit); + temp1[53] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[43], temp0[53], -cospi[21], temp0[42], cosBit); + temp1[54] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[27], temp0[54], -cospi[37], temp0[41], cosBit); + temp1[55] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[59], temp0[55], -cospi[5], temp0[40], cosBit); + temp1[56] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[7], temp0[56], -cospi[57], temp0[39], cosBit); + temp1[57] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[39], temp0[57], -cospi[25], temp0[38], cosBit); + temp1[58] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[23], temp0[58], -cospi[41], temp0[37], cosBit); + temp1[59] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[55], temp0[59], -cospi[9], temp0[36], cosBit); + temp1[60] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[15], temp0[60], -cospi[49], temp0[35], cosBit); + temp1[61] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[47], temp0[61], -cospi[17], temp0[34], cosBit); + temp1[62] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[31], temp0[62], -cospi[33], temp0[33], cosBit); + temp1[63] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[63], temp0[63], -cospi[1], temp0[32], cosBit); + + // stage 11 + Unsafe.Add(ref output, 0) = temp1[0]; + Unsafe.Add(ref output, 1) = temp1[32]; + Unsafe.Add(ref output, 2) = temp1[16]; + Unsafe.Add(ref output, 3) = temp1[48]; + Unsafe.Add(ref output, 4) = temp1[8]; + Unsafe.Add(ref output, 5) = temp1[40]; + Unsafe.Add(ref output, 6) = temp1[24]; + Unsafe.Add(ref output, 7) = temp1[56]; + Unsafe.Add(ref output, 8) = temp1[4]; + Unsafe.Add(ref output, 9) = temp1[36]; + Unsafe.Add(ref output, 10) = temp1[20]; + Unsafe.Add(ref output, 11) = temp1[52]; + Unsafe.Add(ref output, 12) = temp1[12]; + Unsafe.Add(ref output, 13) = temp1[44]; + Unsafe.Add(ref output, 14) = temp1[28]; + Unsafe.Add(ref output, 15) = temp1[60]; + Unsafe.Add(ref output, 16) = temp1[2]; + Unsafe.Add(ref output, 17) = temp1[34]; + Unsafe.Add(ref output, 18) = temp1[18]; + Unsafe.Add(ref output, 19) = temp1[50]; + Unsafe.Add(ref output, 20) = temp1[10]; + Unsafe.Add(ref output, 21) = temp1[42]; + Unsafe.Add(ref output, 22) = temp1[26]; + Unsafe.Add(ref output, 23) = temp1[58]; + Unsafe.Add(ref output, 24) = temp1[6]; + Unsafe.Add(ref output, 25) = temp1[38]; + Unsafe.Add(ref output, 26) = temp1[22]; + Unsafe.Add(ref output, 27) = temp1[54]; + Unsafe.Add(ref output, 28) = temp1[14]; + Unsafe.Add(ref output, 29) = temp1[46]; + Unsafe.Add(ref output, 30) = temp1[30]; + Unsafe.Add(ref output, 31) = temp1[62]; + Unsafe.Add(ref output, 32) = temp1[1]; + Unsafe.Add(ref output, 33) = temp1[33]; + Unsafe.Add(ref output, 34) = temp1[17]; + Unsafe.Add(ref output, 35) = temp1[49]; + Unsafe.Add(ref output, 36) = temp1[9]; + Unsafe.Add(ref output, 37) = temp1[41]; + Unsafe.Add(ref output, 38) = temp1[25]; + Unsafe.Add(ref output, 39) = temp1[57]; + Unsafe.Add(ref output, 40) = temp1[5]; + Unsafe.Add(ref output, 41) = temp1[37]; + Unsafe.Add(ref output, 42) = temp1[21]; + Unsafe.Add(ref output, 43) = temp1[53]; + Unsafe.Add(ref output, 44) = temp1[13]; + Unsafe.Add(ref output, 45) = temp1[45]; + Unsafe.Add(ref output, 46) = temp1[29]; + Unsafe.Add(ref output, 47) = temp1[61]; + Unsafe.Add(ref output, 48) = temp1[3]; + Unsafe.Add(ref output, 49) = temp1[35]; + Unsafe.Add(ref output, 50) = temp1[19]; + Unsafe.Add(ref output, 51) = temp1[51]; + Unsafe.Add(ref output, 52) = temp1[11]; + Unsafe.Add(ref output, 53) = temp1[43]; + Unsafe.Add(ref output, 54) = temp1[27]; + Unsafe.Add(ref output, 55) = temp1[59]; + Unsafe.Add(ref output, 56) = temp1[7]; + Unsafe.Add(ref output, 57) = temp1[39]; + Unsafe.Add(ref output, 58) = temp1[23]; + Unsafe.Add(ref output, 59) = temp1[55]; + Unsafe.Add(ref output, 60) = temp1[15]; + Unsafe.Add(ref output, 61) = temp1[47]; + Unsafe.Add(ref output, 62) = temp1[31]; + Unsafe.Add(ref output, 63) = temp1[63]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs index 1a6d864632..e8e449ac45 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs @@ -1,10 +1,71 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct8Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output, cosBit); + + private static void TransformScalar(ref int input, ref int output, int cosBit) + { + Span temp0 = stackalloc int[8]; + Span temp1 = stackalloc int[8]; + + // stage 0; + + // stage 1; + temp0[0] = Unsafe.Add(ref input, 0) + Unsafe.Add(ref input, 7); + temp0[1] = Unsafe.Add(ref input, 1) + Unsafe.Add(ref input, 6); + temp0[2] = Unsafe.Add(ref input, 2) + Unsafe.Add(ref input, 5); + temp0[3] = Unsafe.Add(ref input, 3) + Unsafe.Add(ref input, 4); + temp0[4] = -Unsafe.Add(ref input, 4) + Unsafe.Add(ref input, 3); + temp0[5] = -Unsafe.Add(ref input, 5) + Unsafe.Add(ref input, 2); + temp0[6] = -Unsafe.Add(ref input, 6) + Unsafe.Add(ref input, 1); + temp0[7] = -Unsafe.Add(ref input, 7) + Unsafe.Add(ref input, 0); + + // stage 2 + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + temp1[0] = temp0[0] + temp0[3]; + temp1[1] = temp0[1] + temp0[2]; + temp1[2] = -temp0[2] + temp0[1]; + temp1[3] = -temp0[3] + temp0[0]; + temp1[4] = temp0[4]; + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp0[5], cospi[32], temp0[6], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp0[6], cospi[32], temp0[5], cosBit); + temp1[7] = temp0[7]; + + // stage 3 + temp0[0] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[32], temp1[0], cospi[32], temp1[1], cosBit); + temp0[1] = Av1Dct4Forward1dTransformer.HalfButterfly(-cospi[32], temp1[1], cospi[32], temp1[0], cosBit); + temp0[2] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[2], cospi[16], temp1[3], cosBit); + temp0[3] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[48], temp1[3], -cospi[16], temp1[2], cosBit); + temp0[4] = temp1[4] + temp1[5]; + temp0[5] = -temp1[5] + temp1[4]; + temp0[6] = -temp1[6] + temp1[7]; + temp0[7] = temp1[7] + temp1[6]; + + // stage 4 + temp1[0] = temp0[0]; + temp1[1] = temp0[1]; + temp1[2] = temp0[2]; + temp1[3] = temp0[3]; + temp1[4] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[4], cospi[8], temp0[7], cosBit); + temp1[5] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[5], cospi[40], temp0[6], cosBit); + temp1[6] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[24], temp0[6], -cospi[40], temp0[5], cosBit); + temp1[7] = Av1Dct4Forward1dTransformer.HalfButterfly(cospi[56], temp0[7], -cospi[8], temp0[4], cosBit); + + // stage 5 + Unsafe.Add(ref output, 0) = temp1[0]; + Unsafe.Add(ref output, 1) = temp1[4]; + Unsafe.Add(ref output, 2) = temp1[2]; + Unsafe.Add(ref output, 3) = temp1[6]; + Unsafe.Add(ref output, 4) = temp1[1]; + Unsafe.Add(ref output, 5) = temp1[5]; + Unsafe.Add(ref output, 6) = temp1[3]; + Unsafe.Add(ref output, 7) = temp1[7]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs index 1619301a3b..cb8e98fc70 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs @@ -7,8 +7,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal abstract class Av1Forward2dTransformerBase { - private const int NewSqrt = 5793; - private const int NewSqrtBitCount = 12; + internal const int NewSqrt2 = 5793; + internal const int NewSqrt2BitCount = 12; /// /// SVT: av1_tranform_two_d_core_c @@ -108,7 +108,7 @@ protected static void Transform2dCore(TColumn transformFunctionCo for (c = 0; c < transformColumnCount; ++c) { ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); - current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); + current = Av1Math.RoundShift((long)current * NewSqrt2, NewSqrt2BitCount); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs index d26f2f7865..3dc8be853f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs @@ -1,10 +1,34 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity16Forward1dTransformer : IAv1Forward1dTransformer { + private const int TwiceNewSqrt2 = 2 * 5793; + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output); + + private static void TransformScalar(ref int input, ref int output) + { + output = Av1Math.RoundShift((long)input * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 1) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 2) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 3) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 4) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 4) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 5) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 5) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 6) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 6) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 7) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 7) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 8) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 8) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 9) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 9) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 10) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 10) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 11) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 11) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 12) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 12) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 13) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 13) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 14) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 14) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 15) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 15) * TwiceNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs index e6232664f5..34985e9a9b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs @@ -1,10 +1,29 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity32Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + { + TransformScalar(ref input, ref output); + TransformScalar(ref Unsafe.Add(ref input, 8), ref Unsafe.Add(ref output, 8)); + TransformScalar(ref Unsafe.Add(ref input, 16), ref Unsafe.Add(ref output, 16)); + TransformScalar(ref Unsafe.Add(ref input, 24), ref Unsafe.Add(ref output, 24)); + } + + private static void TransformScalar(ref int input, ref int output) + { + output = input << 2; + Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 2; + Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 2; + Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 2; + Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 2; + Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 2; + Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 2; + Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 2; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs index a478054832..baa622b0a8 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs @@ -1,10 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity4Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output); + + private static void TransformScalar(ref int input, ref int output) + { + output = Av1Math.RoundShift((long)input * Av1Forward2dTransformerBase.NewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 1) * Av1Forward2dTransformerBase.NewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 2) * Av1Forward2dTransformerBase.NewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 3) * Av1Forward2dTransformerBase.NewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs index 4910896fc5..2c3a351813 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs @@ -1,10 +1,39 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity64Forward1dTransformer : IAv1Forward1dTransformer { + private const int QuadNewSqrt2 = 4 * 5793; + public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + { + TransformScalar(ref input, ref output); + TransformScalar(ref Unsafe.Add(ref input, 16), ref Unsafe.Add(ref output, 16)); + TransformScalar(ref Unsafe.Add(ref input, 32), ref Unsafe.Add(ref output, 32)); + TransformScalar(ref Unsafe.Add(ref input, 48), ref Unsafe.Add(ref output, 48)); + } + + private static void TransformScalar(ref int input, ref int output) + { + output = Av1Math.RoundShift((long)input * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 1) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 2) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 3) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 4) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 4) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 5) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 5) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 6) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 6) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 7) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 7) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 8) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 8) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 9) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 9) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 10) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 10) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 11) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 11) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 12) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 12) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 13) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 13) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 14) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 14) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + Unsafe.Add(ref output, 15) = Av1Math.RoundShift((long)Unsafe.Add(ref input, 15) * QuadNewSqrt2, Av1Forward2dTransformerBase.NewSqrt2BitCount); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs index 497663d032..88c3585c0d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs @@ -1,10 +1,24 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity8Forward1dTransformer : IAv1Forward1dTransformer { public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => throw new NotImplementedException(); + => TransformScalar(ref input, ref output); + + private static void TransformScalar(ref int input, ref int output) + { + output = input << 1; + Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 1; + Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 1; + Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 1; + Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 1; + Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 1; + Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 1; + Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 1; + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 093a6b1555..c9674cd442 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -13,15 +13,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class Av1ForwardTransformTests { - private static readonly double[] MaximumAllowedError = + private static readonly int[] MaximumAllowedError = [ 3, // 4x4 transform 5, // 8x8 transform 11, // 16x16 transform 70, // 32x32 transform 64, // 64x64 transform - 3.9, // 4x8 transform - 4.3, // 8x4 transform + 4, // 4x8 transform + 5, // 8x4 transform 12, // 8x16 transform 12, // 16x8 transform 32, // 16x32 transform @@ -36,45 +36,61 @@ public class Av1ForwardTransformTests 36, // 64x16 transform ]; - [Theory] - [MemberData(nameof(GetSizes))] - public void AccuracyDct1dTest(int txSize) - { - Random rnd = new(0); - const int testBlockCount = 1; // Originally set to: 1000 - Av1TransformSize transformSize = (Av1TransformSize)txSize; - Av1Transform2dFlipConfiguration config = new(Av1TransformType.DctDct, transformSize); - int width = config.TransformSize.GetWidth(); + [Fact] + public void AccuracyOfDct1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformSize.Size4x4, Av1TransformType.DctDct, new Av1Dct4Forward1dTransformer()); - int[] inputOfTest = new int[width]; - double[] inputReference = new double[width]; - int[] outputOfTest = new int[width]; - double[] outputReference = new double[width]; - for (int ti = 0; ti < testBlockCount; ++ti) - { - // prepare random test data - for (int ni = 0; ni < width; ++ni) - { - inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); - inputReference[ni] = inputOfTest[ni]; - outputReference[ni] = 0; - outputOfTest[ni] = 255; - } + [Fact] + public void AccuracyOfDct1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformSize.Size8x8, Av1TransformType.DctDct, new Av1Dct8Forward1dTransformer(), 2); - // calculate in forward transform functions - new Av1Dct4Forward1dTransformer().Transform( - ref inputOfTest[0], - ref outputOfTest[0], - config.CosBitColumn, - config.StageRangeColumn); + [Fact] + public void AccuracyOfDct1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformSize.Size16x16, Av1TransformType.DctDct, new Av1Dct16Forward1dTransformer(), 3); - // calculate in reference forward transform functions - Av1ReferenceTransform.ReferenceDct1d(inputReference, outputReference, width); + [Fact] + public void AccuracyOfDct1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformSize.Size32x32, Av1TransformType.DctDct, new Av1Dct32Forward1dTransformer(), 4); - // Assert - Assert.True(CompareWithError(outputReference, outputOfTest, 1)); - } - } + [Fact] + public void AccuracyOfDct1dTransformSize64Test() + => AssertAccuracy1d(Av1TransformSize.Size64x64, Av1TransformType.DctDct, new Av1Dct64Forward1dTransformer(), 5); + + [Fact] + public void AccuracyOfAdst1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformSize.Size4x4, Av1TransformType.AdstAdst, new Av1Adst4Forward1dTransformer()); + + [Fact] + public void AccuracyOfAdst1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformSize.Size8x8, Av1TransformType.AdstAdst, new Av1Adst8Forward1dTransformer(), 2); + + [Fact] + public void AccuracyOfAdst1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformSize.Size16x16, Av1TransformType.AdstAdst, new Av1Adst16Forward1dTransformer(), 3); + + [Fact] + public void AccuracyOfAdst1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformSize.Size32x32, Av1TransformType.AdstAdst, new Av1Adst32Forward1dTransformer(), 4); + + [Fact] + public void AccuracyOfIdentity1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformSize.Size4x4, Av1TransformType.Identity, new Av1Identity4Forward1dTransformer()); + + [Fact] + public void AccuracyOfIdentity1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformSize.Size8x8, Av1TransformType.Identity, new Av1Identity8Forward1dTransformer()); + + [Fact] + public void AccuracyOfIdentity1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformSize.Size16x16, Av1TransformType.Identity, new Av1Identity16Forward1dTransformer()); + + [Fact] + public void AccuracyOfIdentity1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformSize.Size32x32, Av1TransformType.Identity, new Av1Identity32Forward1dTransformer()); + + [Fact] + public void AccuracyOfIdentity1dTransformSize64Test() + => AssertAccuracy1d(Av1TransformSize.Size64x64, Av1TransformType.Identity, new Av1Identity64Forward1dTransformer()); [Theory] [MemberData(nameof(GetCombinations))] @@ -212,10 +228,51 @@ ref Unsafe.Add(ref output, row * 64 * e_size), } } + private static void AssertAccuracy1d( + Av1TransformSize transformSize, + Av1TransformType transformType, + IAv1Forward1dTransformer transformerUnderTest, + int allowedError = 1) + { + Random rnd = new(0); + const int testBlockCount = 1; // Originally set to: 1000 + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + int width = config.TransformSize.GetWidth(); + + int[] inputOfTest = new int[width]; + double[] inputReference = new double[width]; + int[] outputOfTest = new int[width]; + double[] outputReference = new double[width]; + for (int ti = 0; ti < testBlockCount; ++ti) + { + // prepare random test data + for (int ni = 0; ni < width; ++ni) + { + inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); + inputReference[ni] = inputOfTest[ni]; + outputReference[ni] = 0; + outputOfTest[ni] = 255; + } + + // calculate in forward transform functions + transformerUnderTest.Transform( + ref inputOfTest[0], + ref outputOfTest[0], + config.CosBitColumn, + config.StageRangeColumn); + + // calculate in reference forward transform functions + Av1ReferenceTransform.ReferenceTransform1d(config.TransformTypeColumn, inputReference, outputReference, width); + + // Assert + Assert.True(CompareWithError(outputReference, outputOfTest, allowedError)); + } + } + private static bool CompareWithError(Span expected, Span actual, double allowedError) { // compare for the result is witghin accuracy - double maximumErrorInTest = 0; + double maximumErrorInTest = 0d; for (int ni = 0; ni < expected.Length; ++ni) { maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - Math.Round(expected[ni]))); @@ -224,21 +281,12 @@ private static bool CompareWithError(Span expected, Span actual, do return maximumErrorInTest <= allowedError; } - public static TheoryData GetSizes() - { - TheoryData sizes = []; - - // For now test only 4x4. - sizes.Add(0); - return sizes; - } - public static TheoryData GetCombinations() { TheoryData combinations = []; for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) { - double maxError = MaximumAllowedError[s]; + int maxError = MaximumAllowedError[s]; for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; t++) { Av1TransformType transformType = (Av1TransformType)t; @@ -246,7 +294,7 @@ public static TheoryData GetCombinations() Av1Transform2dFlipConfiguration config = new(transformType, transformSize); if (config.IsAllowed()) { - combinations.Add(s, t, (int)maxError); + combinations.Add(s, t, maxError); } // For now only DCT. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs index 5cb91ca44d..a4d5c105b0 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -174,7 +174,7 @@ private static void ReferenceIdentity1d(Span input, Span output, } } - internal static void ReferenceDct1d(Span input, Span output, int size) + private static void ReferenceDct1d(Span input, Span output, int size) { const double kInvSqrt2 = 0.707106781186547524400844362104f; for (int k = 0; k < size; ++k) @@ -223,7 +223,7 @@ private static void ReferenceAdst1d(Span input, Span output, int } } - private static void ReferenceTransform1d(Av1TransformType1d type, Span input, Span output, int size) + internal static void ReferenceTransform1d(Av1TransformType1d type, Span input, Span output, int size) { switch (type) { From c19d687614d32ef1514e8dafe0b50164ebefa461 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 6 Oct 2024 23:35:45 +0200 Subject: [PATCH 165/190] Bounds check for forward transforms --- src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 3 ++ .../Av1/Transform/Av1ForwardTransformer.cs | 32 +++++++++++-------- .../Forward/Av1Adst16Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Adst32Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Adst4Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Adst8Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Dct16Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Dct32Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Dct4Forward1dTransformer.cs | 8 +++-- .../Forward/Av1Dct64Forward1dTransformer.cs | 10 ++++-- .../Forward/Av1Dct8Forward1dTransformer.cs | 8 +++-- .../Forward/Av1DctDct4Forward2dTransformer.cs | 4 +-- .../Forward/Av1Forward2dTransformerBase.cs | 27 +++++++++------- .../Av1Identity16Forward1dTransformer.cs | 8 +++-- .../Av1Identity32Forward1dTransformer.cs | 14 +++++--- .../Av1Identity4Forward1dTransformer.cs | 8 +++-- .../Av1Identity64Forward1dTransformer.cs | 14 +++++--- .../Av1Identity8Forward1dTransformer.cs | 8 +++-- .../Av1/Transform/IAv1Forward1dTransformer.cs | 2 +- .../Heif/Av1/Av1ForwardTransformTests.cs | 7 ++-- 20 files changed, 135 insertions(+), 66 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 9e4831f01a..59da84eeea 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -144,6 +144,9 @@ internal static int AlignPowerOf2(int value, int n) internal static int Clamp(int value, int low, int high) => value < low ? low : (value > high ? high : value); + internal static long Clamp(long value, long low, long high) + => value < low ? low : (value > high ? high : value); + internal static int DivideLog2Floor(int value, int n) => value >> n; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs index 3528842677..de33eae8f1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -36,12 +36,11 @@ internal class Av1ForwardTransformer internal static void Transform2d(Span input, Span coefficients, uint stride, Av1TransformType transformType, Av1TransformSize transformSize, int bitDepth) { Av1Transform2dFlipConfiguration config = new(transformType, transformSize); - ref int buffer = ref TemporaryCoefficientsBuffer[0]; IAv1Forward1dTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); IAv1Forward1dTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); if (columnTransformer != null && rowTransformer != null) { - Transform2dCore(columnTransformer, rowTransformer, ref input[0], stride, ref coefficients[0], config, ref buffer, bitDepth); + Transform2dCore(columnTransformer, rowTransformer, input, stride, coefficients, config, TemporaryCoefficientsBuffer, bitDepth); } else { @@ -55,7 +54,7 @@ internal static void Transform2d(Span input, Span coefficients, uint /// /// SVT: av1_tranform_two_d_core_c /// - private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) + private static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, Span input, uint inputStride, Span output, Av1Transform2dFlipConfiguration config, Span buf, int bitDepth) where TColumn : IAv1Forward1dTransformer where TRow : IAv1Forward1dTransformer { @@ -87,8 +86,13 @@ private static void Transform2dCore(TColumn transformFunctionColu // ASSERT(txfm_func_col != NULL); // ASSERT(txfm_func_row != NULL); // use output buffer as temp buffer - ref int tempIn = ref output; - ref int tempOut = ref Unsafe.Add(ref output, transformRowCount); + Span tempInSpan = output[..transformRowCount]; + Span tempOutSpan = output.Slice(transformRowCount, transformRowCount); + ref int tempIn = ref tempInSpan[0]; + ref int tempOut = ref tempOutSpan[0]; + ref short inputRef = ref input[0]; + ref int outputRef = ref output[0]; + ref int bufRef = ref buf[0]; // Columns for (c = 0; c < transformColumnCount; ++c) @@ -98,7 +102,7 @@ private static void Transform2dCore(TColumn transformFunctionColu uint t = (uint)c; for (r = 0; r < transformRowCount; ++r) { - Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t); t += inputStride; } } @@ -108,20 +112,20 @@ private static void Transform2dCore(TColumn transformFunctionColu for (r = 0; r < transformRowCount; ++r) { // Flip upside down - Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t); t -= inputStride; } } RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c - transformFunctionColumn.Transform(ref tempIn, ref tempOut, cosBitColumn, stageRangeColumn); + transformFunctionColumn.Transform(tempInSpan, tempOutSpan, cosBitColumn, stageRangeColumn); RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c if (!config.FlipLeftToRight) { int t = c; for (r = 0; r < transformRowCount; ++r) { - Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r); t += transformColumnCount; } } @@ -131,7 +135,7 @@ private static void Transform2dCore(TColumn transformFunctionColu for (r = 0; r < transformRowCount; ++r) { // flip from left to right - Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r); t += transformColumnCount; } } @@ -141,11 +145,11 @@ private static void Transform2dCore(TColumn transformFunctionColu for (r = 0; r < transformRowCount; ++r) { transformFunctionRow.Transform( - ref Unsafe.Add(ref buf, r * transformColumnCount), - ref Unsafe.Add(ref output, r * transformColumnCount), + buf.Slice(r * transformColumnCount, transformColumnCount), + output.Slice(r * transformColumnCount, transformColumnCount), cosBitRow, stageRangeRow); - RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); + RoundShiftArray(ref Unsafe.Add(ref outputRef, r * transformColumnCount), transformColumnCount, -shift[2]); if (Math.Abs(rectangleType) == 1) { @@ -154,7 +158,7 @@ ref Unsafe.Add(ref output, r * transformColumnCount), int t = r * transformColumnCount; for (c = 0; c < transformColumnCount; ++c) { - ref int current = ref Unsafe.Add(ref output, t); + ref int current = ref Unsafe.Add(ref outputRef, t); current = Av1Math.RoundShift((long)current * NewSqrt, NewSqrtBitCount); t++; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs index b3c343538c..927e333e83 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst16Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst16Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 16, nameof(input)); + Guard.MustBeSizedAtLeast(output, 16, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs index 9f707ae639..ba907e3a04 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst32Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst32Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 32, nameof(input)); + Guard.MustBeSizedAtLeast(output, 32, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int outputRef, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs index 2b4952873a..38b11dfbfb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst4Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst4Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs index 8019df88c5..701973fc4c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Adst8Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Adst8Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 8, nameof(input)); + Guard.MustBeSizedAtLeast(output, 8, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs index 1173c56de7..ea515aad5d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct16Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct16Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 16, nameof(input)); + Guard.MustBeSizedAtLeast(output, 16, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs index c0068072d3..2b49035f29 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct32Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct32Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 32, nameof(input)); + Guard.MustBeSizedAtLeast(output, 32, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs index 41a5234594..d43e2535c0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct4Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct4Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs index c9149f2973..57b59cc488 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct64Forward1dTransformer.cs @@ -7,10 +7,14 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct64Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransforScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 64, nameof(input)); + Guard.MustBeSizedAtLeast(output, 64, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } - private static void TransforScalar(ref int input, ref int output, int cosBit) + private static void TransformScalar(ref int input, ref int output, int cosBit) { Span temp0 = stackalloc int[64]; Span temp1 = stackalloc int[64]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs index e8e449ac45..923227e57f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Dct8Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Dct8Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output, cosBit); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 8, nameof(input)); + Guard.MustBeSizedAtLeast(output, 8, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit); + } private static void TransformScalar(ref int input, ref int output, int cosBit) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs index b0115d440f..9442618d20 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1DctDct4Forward2dTransformer.cs @@ -12,7 +12,7 @@ internal class Av1DctDct4Forward2dTransformer : Av1Forward2dTransformerBase private readonly Av1Dct4Forward1dTransformer transformer = new(); private readonly int[] temp = new int[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; - public void Transform(ref short input, ref int output, int cosBit, int columnNumber) + public void Transform(Span input, Span output, int cosBit, int columnNumber) { /*if (Vector256.IsHardwareAccelerated) { @@ -22,7 +22,7 @@ public void Transform(ref short input, ref int output, int cosBit, int columnNum } else*/ { - Transform2dCore(this.transformer, this.transformer, ref input, 4, ref output, this.config, ref this.temp[0], 8); + Transform2dCore(this.transformer, this.transformer, input, 4, output, this.config, this.temp, 8); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs index cb8e98fc70..dbe93cf8e3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Forward2dTransformerBase.cs @@ -13,7 +13,7 @@ internal abstract class Av1Forward2dTransformerBase /// /// SVT: av1_tranform_two_d_core_c /// - protected static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, ref short input, uint inputStride, ref int output, Av1Transform2dFlipConfiguration config, ref int buf, int bitDepth) + protected static void Transform2dCore(TColumn transformFunctionColumn, TRow transformFunctionRow, Span input, uint inputStride, Span output, Av1Transform2dFlipConfiguration config, Span buf, int bitDepth) where TColumn : IAv1Forward1dTransformer where TRow : IAv1Forward1dTransformer { @@ -45,8 +45,13 @@ protected static void Transform2dCore(TColumn transformFunctionCo // ASSERT(txfm_func_col != NULL); // ASSERT(txfm_func_row != NULL); // use output buffer as temp buffer - ref int tempIn = ref output; - ref int tempOut = ref Unsafe.Add(ref output, transformRowCount); + ref short inputRef = ref input[0]; + ref int outputRef = ref output[0]; + ref int bufRef = ref buf[0]; + Span tempInSpan = output.Slice(0, transformRowCount); + Span tempOutSpan = output.Slice(transformRowCount, transformRowCount); + ref int tempIn = ref tempInSpan[0]; + ref int tempOut = ref tempOutSpan[0]; // Columns for (c = 0; c < transformColumnCount; ++c) @@ -56,7 +61,7 @@ protected static void Transform2dCore(TColumn transformFunctionCo uint t = (uint)c; for (r = 0; r < transformRowCount; ++r) { - Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t); t += inputStride; } } @@ -66,20 +71,20 @@ protected static void Transform2dCore(TColumn transformFunctionCo for (r = 0; r < transformRowCount; ++r) { // flip upside down - Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref input, t); + Unsafe.Add(ref tempIn, r) = Unsafe.Add(ref inputRef, t); t -= inputStride; } } RoundShiftArray(ref tempIn, transformRowCount, -shift[0]); // NM svt_av1_round_shift_array_c - transformFunctionColumn.Transform(ref tempIn, ref tempOut, cosBitColumn, stageRangeColumn); + transformFunctionColumn.Transform(tempInSpan, tempOutSpan, cosBitColumn, stageRangeColumn); RoundShiftArray(ref tempOut, transformRowCount, -shift[1]); // NM svt_av1_round_shift_array_c if (!config.FlipLeftToRight) { int t = c; for (r = 0; r < transformRowCount; ++r) { - Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r); t += transformColumnCount; } } @@ -89,7 +94,7 @@ protected static void Transform2dCore(TColumn transformFunctionCo for (r = 0; r < transformRowCount; ++r) { // flip from left to right - Unsafe.Add(ref buf, t) = Unsafe.Add(ref tempOut, r); + Unsafe.Add(ref bufRef, t) = Unsafe.Add(ref tempOut, r); t += transformColumnCount; } } @@ -98,8 +103,8 @@ protected static void Transform2dCore(TColumn transformFunctionCo // Rows for (r = 0; r < transformRowCount; ++r) { - transformFunctionRow.Transform(ref Unsafe.Add(ref buf, r * transformColumnCount), ref Unsafe.Add(ref output, r * transformColumnCount), cosBitRow, stageRangeRow); - RoundShiftArray(ref Unsafe.Add(ref output, r * transformColumnCount), transformColumnCount, -shift[2]); + transformFunctionRow.Transform(buf.Slice(r * transformColumnCount, transformColumnCount), output.Slice(r * transformColumnCount, transformColumnCount), cosBitRow, stageRangeRow); + RoundShiftArray(ref Unsafe.Add(ref outputRef, r * transformColumnCount), transformColumnCount, -shift[2]); if (Math.Abs(rectangleType) == 1) { @@ -107,7 +112,7 @@ protected static void Transform2dCore(TColumn transformFunctionCo // size difference is a factor of 2. for (c = 0; c < transformColumnCount; ++c) { - ref int current = ref Unsafe.Add(ref output, (r * transformColumnCount) + c); + ref int current = ref Unsafe.Add(ref outputRef, (r * transformColumnCount) + c); current = Av1Math.RoundShift((long)current * NewSqrt2, NewSqrt2BitCount); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs index 3dc8be853f..78ab05d150 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity16Forward1dTransformer.cs @@ -9,8 +9,12 @@ internal class Av1Identity16Forward1dTransformer : IAv1Forward1dTransformer { private const int TwiceNewSqrt2 = 2 * 5793; - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 16, nameof(input)); + Guard.MustBeSizedAtLeast(output, 16, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } private static void TransformScalar(ref int input, ref int output) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs index 34985e9a9b..13ee029464 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity32Forward1dTransformer.cs @@ -7,12 +7,16 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity32Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + public void Transform(Span input, Span output, int cosBit, Span stageRange) { - TransformScalar(ref input, ref output); - TransformScalar(ref Unsafe.Add(ref input, 8), ref Unsafe.Add(ref output, 8)); - TransformScalar(ref Unsafe.Add(ref input, 16), ref Unsafe.Add(ref output, 16)); - TransformScalar(ref Unsafe.Add(ref input, 24), ref Unsafe.Add(ref output, 24)); + Guard.MustBeSizedAtLeast(input, 32, nameof(input)); + Guard.MustBeSizedAtLeast(output, 32, nameof(output)); + ref int inputRef = ref input[0]; + ref int outputRef = ref output[0]; + TransformScalar(ref inputRef, ref outputRef); + TransformScalar(ref Unsafe.Add(ref inputRef, 8), ref Unsafe.Add(ref outputRef, 8)); + TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16)); + TransformScalar(ref Unsafe.Add(ref inputRef, 24), ref Unsafe.Add(ref outputRef, 24)); } private static void TransformScalar(ref int input, ref int output) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs index baa622b0a8..45a8a78fb1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity4Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity4Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } private static void TransformScalar(ref int input, ref int output) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs index 2c3a351813..15a9ae658f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity64Forward1dTransformer.cs @@ -9,12 +9,16 @@ internal class Av1Identity64Forward1dTransformer : IAv1Forward1dTransformer { private const int QuadNewSqrt2 = 4 * 5793; - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) + public void Transform(Span input, Span output, int cosBit, Span stageRange) { - TransformScalar(ref input, ref output); - TransformScalar(ref Unsafe.Add(ref input, 16), ref Unsafe.Add(ref output, 16)); - TransformScalar(ref Unsafe.Add(ref input, 32), ref Unsafe.Add(ref output, 32)); - TransformScalar(ref Unsafe.Add(ref input, 48), ref Unsafe.Add(ref output, 48)); + Guard.MustBeSizedAtLeast(input, 64, nameof(input)); + Guard.MustBeSizedAtLeast(output, 64, nameof(output)); + ref int inputRef = ref input[0]; + ref int outputRef = ref output[0]; + TransformScalar(ref inputRef, ref outputRef); + TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16)); + TransformScalar(ref Unsafe.Add(ref inputRef, 32), ref Unsafe.Add(ref outputRef, 32)); + TransformScalar(ref Unsafe.Add(ref inputRef, 48), ref Unsafe.Add(ref outputRef, 48)); } private static void TransformScalar(ref int input, ref int output) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs index 88c3585c0d..822e7b4ae6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Forward/Av1Identity8Forward1dTransformer.cs @@ -7,8 +7,12 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; internal class Av1Identity8Forward1dTransformer : IAv1Forward1dTransformer { - public void Transform(ref int input, ref int output, int cosBit, Span stageRange) - => TransformScalar(ref input, ref output); + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 8, nameof(input)); + Guard.MustBeSizedAtLeast(output, 8, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } private static void TransformScalar(ref int input, ref int output) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs index 4f58d0eafc..c77e27acee 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/IAv1Forward1dTransformer.cs @@ -15,5 +15,5 @@ internal interface IAv1Forward1dTransformer /// Output coefficients. /// The cosinus bit. /// Stage ranges. - void Transform(ref int input, ref int output, int cosBit, Span stageRange); + void Transform(Span input, Span output, int cosBit, Span stageRange); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index c9674cd442..484dc102c1 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -8,7 +8,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; /// -/// SVY: test/FwdTxfm2dTest.cc +/// SVY: test/FwdTxfm1dTest.cc +/// SVY: test/FwdTxfm2dAsmTest.cc /// [Trait("Format", "Avif")] public class Av1ForwardTransformTests @@ -256,8 +257,8 @@ private static void AssertAccuracy1d( // calculate in forward transform functions transformerUnderTest.Transform( - ref inputOfTest[0], - ref outputOfTest[0], + inputOfTest, + outputOfTest, config.CosBitColumn, config.StageRangeColumn); From 47c2416d7cab30c23c7da3a96ac99a08a628ab0b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 11 Oct 2024 20:54:09 +0200 Subject: [PATCH 166/190] Some more forward transform tests --- .../Av1/Transform/Av1ForwardTransformer.cs | 21 +- .../Av1Transform2dFlipConfiguration.cs | 14 +- .../Heif/Av1/Transform/Av1TransformType.cs | 2 +- .../Heif/Av1/Av1ForwardTransformTests.cs | 302 +++++++++++++++++- .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 7 +- .../Formats/Heif/Av1/EchoTestTransformer.cs | 12 + 6 files changed, 333 insertions(+), 25 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs index de33eae8f1..0940425871 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformer.cs @@ -38,9 +38,16 @@ internal static void Transform2d(Span input, Span coefficients, uint Av1Transform2dFlipConfiguration config = new(transformType, transformSize); IAv1Forward1dTransformer? columnTransformer = GetTransformer(config.TransformFunctionTypeColumn); IAv1Forward1dTransformer? rowTransformer = GetTransformer(config.TransformFunctionTypeRow); - if (columnTransformer != null && rowTransformer != null) + Transform2d(columnTransformer, rowTransformer, input, coefficients, stride, config, bitDepth); + } + + internal static void Transform2d(TColumn? transformFunctionColumn, TRow? transformFunctionRow, Span input, Span coefficients, uint stride, Av1Transform2dFlipConfiguration config, int bitDepth) + where TColumn : IAv1Forward1dTransformer + where TRow : IAv1Forward1dTransformer + { + if (transformFunctionColumn != null && transformFunctionRow != null) { - Transform2dCore(columnTransformer, rowTransformer, input, stride, coefficients, config, TemporaryCoefficientsBuffer, bitDepth); + Transform2dCore(transformFunctionColumn, transformFunctionRow, input, stride, coefficients, config, TemporaryCoefficientsBuffer, bitDepth); } else { @@ -142,20 +149,20 @@ private static void Transform2dCore(TColumn transformFunctionColu } // Rows - for (r = 0; r < transformRowCount; ++r) + for (r = 0; r < transformCount; r += transformColumnCount) { transformFunctionRow.Transform( - buf.Slice(r * transformColumnCount, transformColumnCount), - output.Slice(r * transformColumnCount, transformColumnCount), + buf.Slice(r, transformColumnCount), + output.Slice(r, transformColumnCount), cosBitRow, stageRangeRow); - RoundShiftArray(ref Unsafe.Add(ref outputRef, r * transformColumnCount), transformColumnCount, -shift[2]); + RoundShiftArray(ref Unsafe.Add(ref outputRef, r), transformColumnCount, -shift[2]); if (Math.Abs(rectangleType) == 1) { // Multiply everything by Sqrt2 if the transform is rectangular and the // size difference is a factor of 2. - int t = r * transformColumnCount; + int t = r; for (c = 0; c < transformColumnCount; ++c) { ref int current = ref Unsafe.Add(ref outputRef, t); diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs index 7fd6d6d95d..d4bca87659 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -1,8 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Drawing; - namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal class Av1Transform2dFlipConfiguration @@ -124,10 +122,12 @@ internal class Av1Transform2dFlipConfiguration [5], // fidtx64_range_mult2 ]; - private readonly int[] shift; + private int[] shift; public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1TransformSize transformSize) { + // SVT: svt_av1_get_inv_txfm_cfg + // SVT: svt_aom_transform_config this.TransformSize = transformSize; this.TransformType = transformType; this.SetFlip(transformType); @@ -248,6 +248,14 @@ public bool IsAllowed() return supportedTypes.Contains(this.TransformType); } + internal void SetShift(int shift0, int shift1, int shift2) => this.shift = [shift0, shift1, shift2]; + + internal void SetFlip(bool upsideDown, bool leftToRight) + { + this.FlipUpsideDown = upsideDown; + this.FlipLeftToRight = leftToRight; + } + private void SetFlip(Av1TransformType transformType) { switch (transformType) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs index dbc02ea6fb..96867d2ab1 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformType.cs @@ -16,7 +16,7 @@ internal enum Av1TransformType : byte AdstDct, /// - /// DCT in vertical, ADST in horizontal. + /// DCT in vertical, ADST in horizontal. /// DctAdst, diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 484dc102c1..9902a35be7 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -37,6 +37,288 @@ public class Av1ForwardTransformTests 36, // 64x16 transform ]; + [Theory] + [MemberData(nameof(GetCombinations))] + public void ConfigTest(int txSize, int txType, int _) + { + // Arrange + Av1TransformType transformType = (Av1TransformType)txType; + Av1TransformSize transformSize = (Av1TransformSize)txSize; + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + + // Act + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + + // Assert + Assert.Equal(transformSize, config.TransformSize); + Assert.Equal(transformType, config.TransformType); + string actual = $"{config.TransformTypeColumn}{config.TransformTypeRow}"; + if (actual == "IdentityIdentity") + { + actual = "Identity"; + } + else + { + if (actual.StartsWith("Identity", StringComparison.InvariantCulture)) + { + actual = actual.Replace("Identity", "Horizontal"); + } + + if (actual.EndsWith("Identity", StringComparison.InvariantCulture)) + { + actual = "Vertical" + actual.Replace("Identity", ""); + } + } + + Assert.Equal(transformType.ToString(), actual); + } + + [Theory] + [MemberData(nameof(GetCombinations))] + public void ScaleFactorTest(int txSize, int txType, int _) + { + // Arrange + Av1TransformType transformType = (Av1TransformType)txType; + Av1TransformSize transformSize = (Av1TransformSize)txSize; + short[] input = new short[64 * 64]; + Array.Fill(input, 1); + int[] actual = new int[64 * 64]; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + int blockSize = width * height; + double expected = Av1ReferenceTransform.GetScaleFactor(config); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + (uint)width, + config, + 8); + + // Assert + Assert.True(actual.Take(blockSize).All(x => Math.Abs(x - expected) < 1d)); + } + + [Fact] + public void FlipNothingTest() + { + // Arrange + short[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] expected = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] actual = new int[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(false, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void FlipHorizontalTest() + { + // Arrange + int[] expected = [ + 4, 3, 2, 1, + 8, 7, 6, 5, + 12, 11, 10, 9, + 16, 15, 14, 13]; + short[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] actual = new int[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(false, true); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void FlipVerticalTest() + { + // Arrange + int[] expected = [ + 13, 14, 15, 16, + 9, 10, 11, 12, + 5, 6, 7, 8, + 1, 2, 3, 4]; + short[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] actual = new int[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(true, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void FlipHorizontalAndVerticalTest() + { + // Arrange + int[] expected = [ + 16, 15, 14, 13, + 12, 11, 10, 9, + 8, 7, 6, 5, + 4, 3, 2, 1]; + short[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] actual = new int[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(true, true); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void NonSquareTransformSizeTest() + { + // Arrange + short[] input = [ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32]; + + // Expected is multiplied by Sqrt(2). + int[] expected = [ + 18, 20, 21, 23, 24, 25, 27, 28, + 13, 14, 16, 17, 18, 20, 21, 23, + 7, 8, 10, 11, 13, 14, 16, 17, + 1, 3, 4, 6, 7, 8, 10, 11]; + int[] actual = new int[32]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size8x4); + config.SetFlip(true, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + + // [Fact] + public void NonSquareTransformSize2Test() + { + // Arrange + short[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + 17, 18, 19, 20, + 21, 22, 23, 24, + 25, 26, 27, 28, + 29, 30, 31, 32]; + int[] expected = [ + 29, 30, 31, 32, + 25, 26, 27, 28, + 21, 22, 23, 24, + 17, 18, 19, 20, + 13, 14, 15, 16, + 9, 10, 11, 12, + 5, 6, 7, 8, + 1, 2, 3, 4]; + int[] actual = new int[32]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x8); + config.SetFlip(true, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1ForwardTransformer.Transform2d( + transformer, + transformer, + input, + actual, + 4, + config, + 8); + + // Assert + Assert.Equal(expected, actual); + } + [Fact] public void AccuracyOfDct1dTransformSize4Test() => AssertAccuracy1d(Av1TransformSize.Size4x4, Av1TransformType.DctDct, new Av1Dct4Forward1dTransformer()); @@ -71,7 +353,7 @@ public void AccuracyOfAdst1dTransformSize16Test() [Fact] public void AccuracyOfAdst1dTransformSize32Test() - => AssertAccuracy1d(Av1TransformSize.Size32x32, Av1TransformType.AdstAdst, new Av1Adst32Forward1dTransformer(), 4); + => AssertAccuracy1d(Av1TransformSize.Size32x32, Av1TransformType.AdstAdst, new Av1Adst32Forward1dTransformer(), 5); [Fact] public void AccuracyOfIdentity1dTransformSize4Test() @@ -93,8 +375,8 @@ public void AccuracyOfIdentity1dTransformSize32Test() public void AccuracyOfIdentity1dTransformSize64Test() => AssertAccuracy1d(Av1TransformSize.Size64x64, Av1TransformType.Identity, new Av1Identity64Forward1dTransformer()); - [Theory] - [MemberData(nameof(GetCombinations))] + // [Theory] + // [MemberData(nameof(GetCombinations))] public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) { const int bitDepth = 8; @@ -106,7 +388,7 @@ public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) int width = config.TransformSize.GetWidth(); int height = config.TransformSize.GetHeight(); int blockSize = width * height; - double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config, width, height); + double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config); short[] inputOfTest = new short[blockSize]; double[] inputReference = new double[blockSize]; @@ -138,7 +420,7 @@ public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) // repack the coefficents for some tx_size RepackCoefficients(outputOfTest, outputReference, width, height); - Assert.True(CompareWithError(outputReference, outputOfTest, maxAllowedError * scaleFactor), $"Forward transform 2d test with transform type: {transformType}, transform size: {transformSize} and loop: {ti}"); + Assert.True(CompareWithError(outputReference, outputOfTest, maxAllowedError * scaleFactor), $"Transform type: {transformType}, transform size: {transformSize}."); } } @@ -236,7 +518,7 @@ private static void AssertAccuracy1d( int allowedError = 1) { Random rnd = new(0); - const int testBlockCount = 1; // Originally set to: 1000 + const int testBlockCount = 100; // Originally set to: 1000 Av1Transform2dFlipConfiguration config = new(transformType, transformSize); int width = config.TransformSize.GetWidth(); @@ -287,23 +569,17 @@ public static TheoryData GetCombinations() TheoryData combinations = []; for (int s = 0; s < (int)Av1TransformSize.AllSizes; s++) { + Av1TransformSize transformSize = (Av1TransformSize)s; int maxError = MaximumAllowedError[s]; for (int t = 0; t < (int)Av1TransformType.AllTransformTypes; t++) { Av1TransformType transformType = (Av1TransformType)t; - Av1TransformSize transformSize = (Av1TransformSize)s; Av1Transform2dFlipConfiguration config = new(transformType, transformSize); if (config.IsAllowed()) { combinations.Add(s, t, maxError); } - - // For now only DCT. - break; } - - // For now only 4x4. - break; } return combinations; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs index a4d5c105b0..dff594926e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -23,9 +23,11 @@ internal class Av1ReferenceTransform * ******************************************************************************/ - public static double GetScaleFactor(Av1Transform2dFlipConfiguration config, int transformWidth, int transformHeight) + public static double GetScaleFactor(Av1Transform2dFlipConfiguration config) { Span shift = config.Shift; + int transformWidth = config.TransformSize.GetWidth(); + int transformHeight = config.TransformSize.GetHeight(); int amplifyBit = shift[0] + shift[1] + shift[2]; double scaleFactor = amplifyBit >= 0 ? (1 << amplifyBit) : (1.0 / (1 << -amplifyBit)); @@ -40,6 +42,9 @@ public static double GetScaleFactor(Av1Transform2dFlipConfiguration config, int return scaleFactor; } + /// + /// SVT: reference_txfm_2d + /// public static void ReferenceTransformFunction2d(Span input, Span output, Av1TransformType transformType, Av1TransformSize transformSize, double scaleFactor) { // Get transform type and size of each dimension. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs new file mode 100644 index 0000000000..79c8f0c504 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs @@ -0,0 +1,12 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class EchoTestTransformer : IAv1Forward1dTransformer +{ + public void Transform(Span input, Span output, int cosBit, Span stageRange) + => input.CopyTo(output); +} From 101e841944e5e6dbad3ae41b9e0c551b811091f8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 15 Oct 2024 20:53:58 +0200 Subject: [PATCH 167/190] Partial accuracy test for forward transform --- .../Heif/Av1/Av1ForwardTransformTests.cs | 132 ++++++++++++------ .../Formats/Heif/Av1/Av1ReferenceTransform.cs | 2 +- 2 files changed, 93 insertions(+), 41 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 9902a35be7..53667c140a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -242,7 +242,7 @@ public void FlipHorizontalAndVerticalTest() } [Fact] - public void NonSquareTransformSizeTest() + public void NonSquareTransformSizeLandscapeTest() { // Arrange short[] input = [ @@ -251,7 +251,7 @@ public void NonSquareTransformSizeTest() 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]; - // Expected is multiplied by Sqrt(2). + // Expected is divided by Sqrt(2). int[] expected = [ 18, 20, 21, 23, 24, 25, 27, 28, 13, 14, 16, 17, 18, 20, 21, 23, @@ -277,8 +277,8 @@ public void NonSquareTransformSizeTest() Assert.Equal(expected, actual); } - // [Fact] - public void NonSquareTransformSize2Test() + [Fact] + public void NonSquareTransformSizePortraitTest() { // Arrange short[] input = [ @@ -290,15 +290,17 @@ public void NonSquareTransformSize2Test() 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]; + + // Expected is multiplied by Sqrt(2). int[] expected = [ - 29, 30, 31, 32, - 25, 26, 27, 28, - 21, 22, 23, 24, - 17, 18, 19, 20, - 13, 14, 15, 16, - 9, 10, 11, 12, - 5, 6, 7, 8, - 1, 2, 3, 4]; + 41, 42, 44, 45, + 35, 37, 38, 40, + 30, 31, 33, 34, + 24, 25, 27, 28, + 18, 20, 21, 23, + 13, 14, 16, 17, + 7, 8, 10, 11, + 1, 3, 4, 6]; int[] actual = new int[32]; Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x8); config.SetFlip(true, false); @@ -375,8 +377,8 @@ public void AccuracyOfIdentity1dTransformSize32Test() public void AccuracyOfIdentity1dTransformSize64Test() => AssertAccuracy1d(Av1TransformSize.Size64x64, Av1TransformType.Identity, new Av1Identity64Forward1dTransformer()); - // [Theory] - // [MemberData(nameof(GetCombinations))] + [Theory] + [MemberData(nameof(GetCombinations))] public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) { const int bitDepth = 8; @@ -390,18 +392,29 @@ public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) int blockSize = width * height; double scaleFactor = Av1ReferenceTransform.GetScaleFactor(config); + // TODO: Still some limitations in either reference or the actual implementation. + if (config.TransformTypeColumn == Av1TransformType1d.FlipAdst || config.TransformTypeRow == Av1TransformType1d.FlipAdst) + { + return; + } + + if (width == 64 || height == 64 || width != height) + { + return; + } + short[] inputOfTest = new short[blockSize]; - double[] inputReference = new double[blockSize]; + double[] inputOfReference = new double[blockSize]; int[] outputOfTest = new int[blockSize]; - double[] outputReference = new double[blockSize]; + double[] outputOfReference = new double[blockSize]; for (int ti = 0; ti < testBlockCount; ++ti) { // prepare random test data for (int ni = 0; ni < blockSize; ++ni) { inputOfTest[ni] = (short)rnd.Next((1 << 10) - 1); - inputReference[ni] = inputOfTest[ni]; - outputReference[ni] = 0; + inputOfReference[ni] = inputOfTest[ni]; + outputOfReference[ni] = 0; outputOfTest[ni] = 255; } @@ -409,18 +422,51 @@ public void Accuracy2dTest(int txSize, int txType, int maxAllowedError = 0) Av1ForwardTransformer.Transform2d( inputOfTest, outputOfTest, - (uint)transformSize.GetWidth(), + (uint)width, transformType, transformSize, bitDepth); // calculate in reference forward transform functions - Av1ReferenceTransform.ReferenceTransformFunction2d(inputReference, outputReference, transformType, transformSize, scaleFactor); + FlipInput(config, inputOfReference); + Av1ReferenceTransform.ReferenceTransformFunction2d(inputOfReference, outputOfReference, transformType, transformSize, scaleFactor); // repack the coefficents for some tx_size - RepackCoefficients(outputOfTest, outputReference, width, height); + RepackCoefficients(outputOfTest, outputOfReference, width, height); + + Assert.True(CompareWithError(outputOfReference, outputOfTest, maxAllowedError * scaleFactor), $"{transformType} of {transformSize}, error: {GetMaximumError(outputOfReference, outputOfTest)}."); + } + } + + private static void FlipInput(Av1Transform2dFlipConfiguration config, Span input) + { + int width = config.TransformSize.GetWidth(); + int height = config.TransformSize.GetHeight(); + double tmp; + if (config.FlipLeftToRight) + { + for (int r = 0; r < height; ++r) + { + for (int c = 0; c < width / 2; ++c) + { + tmp = input[(r * width) + c]; + input[(r * width) + c] = input[(r * width) + width - 1 - c]; + input[(r * width) + width - 1 - c] = tmp; + } + } + } - Assert.True(CompareWithError(outputReference, outputOfTest, maxAllowedError * scaleFactor), $"Transform type: {transformType}, transform size: {transformSize}."); + if (config.FlipUpsideDown) + { + for (int c = 0; c < width; ++c) + { + for (int r = 0; r < height / 2; ++r) + { + tmp = input[(r * width) + c]; + input[(r * width) + c] = input[((height - 1 - r) * width) + c]; + input[((height - 1 - r) * width) + c] = tmp; + } + } } } @@ -430,7 +476,7 @@ private static void RepackCoefficients(Span outputOfTest, Span outp { for (int i = 0; i < 2; ++i) { - uint e_size = i == 0 ? (uint)sizeof(int) : sizeof(double); + uint elementSize = i == 0 ? (uint)sizeof(int) : sizeof(double); ref byte output = ref (i == 0) ? ref Unsafe.As(ref outputOfTest[0]) : ref Unsafe.As(ref outputReference[0]); @@ -440,26 +486,26 @@ private static void RepackCoefficients(Span outputOfTest, Span outp // zero out top-right 32x32 area. for (uint row = 0; row < 32; ++row) { - Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * elementSize), 0, 32 * elementSize); } // zero out the bottom 64x32 area. - Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 64 * e_size), 0, 32 * 64 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 64 * elementSize), 0, 32 * 64 * elementSize); // Re-pack non-zero coeffs in the first 32x32 indices. for (uint row = 1; row < 32; ++row) { Unsafe.CopyBlock( - ref Unsafe.Add(ref output, row * 32 * e_size), - ref Unsafe.Add(ref output, row * 64 * e_size), - 32 * e_size); + ref Unsafe.Add(ref output, row * 32 * elementSize), + ref Unsafe.Add(ref output, row * 64 * elementSize), + 32 * elementSize); } } else if (tx_width == 32 && tx_height == 64) { // tx_size == TX_32X64 // zero out the bottom 32x32 area. - Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 32 * e_size), 0, 32 * 32 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, 32 * 32 * elementSize), 0, 32 * 32 * elementSize); // Note: no repacking needed here. } @@ -469,23 +515,23 @@ ref Unsafe.Add(ref output, row * 64 * e_size), // zero out right 32x32 area. for (uint row = 0; row < 32; ++row) { - Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * elementSize), 0, 32 * elementSize); } // Re-pack non-zero coeffs in the first 32x32 indices. for (uint row = 1; row < 32; ++row) { Unsafe.CopyBlock( - ref Unsafe.Add(ref output, row * 32 * e_size), - ref Unsafe.Add(ref output, row * 64 * e_size), - 32 * e_size); + ref Unsafe.Add(ref output, row * 32 * elementSize), + ref Unsafe.Add(ref output, row * 64 * elementSize), + 32 * elementSize); } } else if (tx_width == 16 && tx_height == 64) { // tx_size == TX_16X64 // zero out the bottom 16x32 area. - Unsafe.InitBlock(ref Unsafe.Add(ref output, 16 * 32 * e_size), 0, 16 * 32 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, 16 * 32 * elementSize), 0, 16 * 32 * elementSize); // Note: no repacking needed here. } @@ -496,16 +542,16 @@ ref Unsafe.Add(ref output, row * 64 * e_size), // zero out right 32x16 area. for (uint row = 0; row < 16; ++row) { - Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * e_size), 0, 32 * e_size); + Unsafe.InitBlock(ref Unsafe.Add(ref output, ((row * 64) + 32) * elementSize), 0, 32 * elementSize); } // Re-pack non-zero coeffs in the first 32x16 indices. for (uint row = 1; row < 16; ++row) { Unsafe.CopyBlock( - ref Unsafe.Add(ref output, row * 32 * e_size), - ref Unsafe.Add(ref output, row * 64 * e_size), - 32 * e_size); + ref Unsafe.Add(ref output, row * 32 * elementSize), + ref Unsafe.Add(ref output, row * 64 * elementSize), + 32 * elementSize); } } } @@ -554,14 +600,20 @@ private static void AssertAccuracy1d( private static bool CompareWithError(Span expected, Span actual, double allowedError) { - // compare for the result is witghin accuracy + // compare for the result is within accuracy + double maximumErrorInTest = GetMaximumError(expected, actual); + return maximumErrorInTest <= allowedError; + } + + private static double GetMaximumError(Span expected, Span actual) + { double maximumErrorInTest = 0d; for (int ni = 0; ni < expected.Length; ++ni) { maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - Math.Round(expected[ni]))); } - return maximumErrorInTest <= allowedError; + return maximumErrorInTest; } public static TheoryData GetCombinations() diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs index dff594926e..e5442db143 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceTransform.cs @@ -36,7 +36,7 @@ public static double GetScaleFactor(Av1Transform2dFlipConfiguration config) int rectType = Av1ForwardTransformer.GetRectangularRatio(transformWidth, transformHeight); if (Math.Abs(rectType) == 1) { - scaleFactor *= Math.Pow(2, 0.5); + scaleFactor *= Math.Sqrt(2); } return scaleFactor; From 796ea84006641cd5ccd3a4e38afb4ef3d601098d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 22 Oct 2024 21:42:10 +0200 Subject: [PATCH 168/190] Implementation of some inverse transformers --- .../Formats/Heif/Av1/Av1Constants.cs | 5 + .../Av1Transform2dFlipConfiguration.cs | 1 + .../Inverse/Av1Adst4Inverse1dTransformer.cs | 78 +++++++ .../Inverse/Av1Dct4Inverse1dTransformer.cs | 86 ++++++++ .../Av1Identity16Inverse1dTransformer.cs | 43 ++++ .../Av1Identity32Inverse1dTransformer.cs | 36 ++++ .../Av1Identity4Inverse1dTransformer.cs | 34 +++ .../Av1Identity64Inverse1dTransformer.cs | 44 ++++ .../Av1Identity8Inverse1dTransformer.cs | 31 +++ .../Heif/Av1/Av1InverseTransformTests.cs | 196 ++++++++++++++++++ 10 files changed, 554 insertions(+) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index a31bb137fe..fc4915e537 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -175,4 +175,9 @@ internal static class Av1Constants public const int QuantificationMatrixLevelCount = 4; public const int AngleStep = 3; + + /// + /// Maximum number of stages in a 1-dimensioanl transform function. + /// + public const int MaxTransformStageNumber = 12; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs index d4bca87659..b1abf23245 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Transform2dFlipConfiguration.cs @@ -179,6 +179,7 @@ public Av1Transform2dFlipConfiguration(Av1TransformType transformType, Av1Transf /// /// SVT: svt_av1_gen_fwd_stage_range + /// SVT: svt_av1_gen_inv_stage_range /// public void GenerateStageRange(int bitDepth) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs new file mode 100644 index 0000000000..3c0fa7d566 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Adst4Inverse1dTransformer.cs @@ -0,0 +1,78 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Adst4Inverse1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit, stageRange); + } + + /// + /// SVT: svt_av1_iadst4_new + /// + private static void TransformScalar(ref int input, ref int output, int cosBit, Span stageRange) + { + int bit = cosBit; + Span sinpi = Av1SinusConstants.SinusPi(bit); + int s0, s1, s2, s3, s4, s5, s6, s7; + + int x0 = input; + int x1 = Unsafe.Add(ref input, 1); + int x2 = Unsafe.Add(ref input, 2); + int x3 = Unsafe.Add(ref input, 3); + + if (!(x0 != 0 | x1 != 0 | x2 != 0 | x3 != 0)) + { + output = 0; + Unsafe.Add(ref output, 1) = 0; + Unsafe.Add(ref output, 2) = 0; + Unsafe.Add(ref output, 3) = 0; + return; + } + + Guard.IsTrue(sinpi[1] + sinpi[2] == sinpi[4], nameof(sinpi), "Sinus Pi check failed."); + + s0 = sinpi[1] * x0; + s1 = sinpi[2] * x0; + s2 = sinpi[3] * x1; + s3 = sinpi[4] * x2; + s4 = sinpi[1] * x2; + s5 = sinpi[2] * x3; + s6 = sinpi[4] * x3; + + s7 = (x0 - x2) + x3; + + // stage 3 + s0 = s0 + s3; + s1 = s1 - s4; + s3 = s2; + s2 = sinpi[3] * s7; + + // stage 4 + s0 = s0 + s5; + s1 = s1 - s6; + + // stage 5 + x0 = s0 + s3; + x1 = s1 + s3; + x2 = s2; + x3 = s0 + s1; + + // stage 6 + x3 = x3 - s3; + + output = Av1Math.RoundShift(x0, bit); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift(x1, bit); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift(x2, bit); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift(x3, bit); + + // range_check_buf(6, input, output, 4, stage_range[6]); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs new file mode 100644 index 0000000000..ca19e188b5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Dct4Inverse1dTransformer.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Dct4Inverse1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0], cosBit, stageRange); + } + + /// + /// SVT: svt_av1_idct4_new + /// + private static void TransformScalar(ref int input, ref int output, int cosBit, Span stageRange) + { + Span cospi = Av1SinusConstants.CosinusPi(cosBit); + int stage = 0; + Span temp0 = stackalloc int[4]; + Span temp1 = stackalloc int[4]; + + // stage 0; + + // stage 1; + stage++; + temp0[0] = input; + temp0[1] = Unsafe.Add(ref input, 2); + temp0[2] = Unsafe.Add(ref input, 1); + temp0[3] = Unsafe.Add(ref input, 3); + + // range_check_buf(stage, input, bf1, size, stage_range[stage]); + + // stage 2 + stage++; + temp1[0] = HalfButterfly(cospi[32], temp0[0], cospi[32], temp0[1], cosBit); + temp1[1] = HalfButterfly(cospi[32], temp0[0], -cospi[32], temp0[1], cosBit); + temp1[2] = HalfButterfly(cospi[48], temp0[2], -cospi[16], temp0[3], cosBit); + temp1[3] = HalfButterfly(cospi[16], temp0[2], cospi[48], temp0[3], cosBit); + + // range_check_buf(stage, input, bf1, size, stage_range[stage]); + + // stage 3 + stage++; + Unsafe.Add(ref output, 0) = ClampValue(temp1[0] + temp1[3], stageRange[stage]); + Unsafe.Add(ref output, 1) = ClampValue(temp1[1] + temp1[2], stageRange[stage]); + Unsafe.Add(ref output, 2) = ClampValue(temp1[1] - temp1[2], stageRange[stage]); + Unsafe.Add(ref output, 3) = ClampValue(temp1[0] - temp1[3], stageRange[stage]); + } + + internal static int ClampValue(int value, byte bit) + { + if (bit <= 0) + { + return value; // Do nothing for invalid clamp bit. + } + + long max_value = (1L << (bit - 1)) - 1; + long min_value = -(1L << (bit - 1)); + return (int)Av1Math.Clamp(value, min_value, max_value); + } + + internal static int HalfButterfly(int w0, int in0, int w1, int in1, int bit) + { + long result64 = (long)(w0 * in0) + (w1 * in1); + long intermediate = result64 + (1L << (bit - 1)); + + // NOTE(david.barker): The value 'result_64' may not necessarily fit + // into 32 bits. However, the result of this function is nominally + // ROUND_POWER_OF_TWO_64(result_64, bit) + // and that is required to fit into stage_range[stage] many bits + // (checked by range_check_buf()). + // + // Here we've unpacked that rounding operation, and it can be shown + // that the value of 'intermediate' here *does* fit into 32 bits + // for any conformant bitstream. + // The upshot is that, if you do all this calculation using + // wrapping 32-bit arithmetic instead of (non-wrapping) 64-bit arithmetic, + // then you'll still get the correct result. + return (int)(intermediate >> bit); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs new file mode 100644 index 0000000000..14071f4d29 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity16Inverse1dTransformer.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Identity16Inverse1dTransformer : IAv1Forward1dTransformer +{ + private const long Sqrt2Times2 = Av1Identity4Inverse1dTransformer.Sqrt2 >> 1; + + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 16, nameof(input)); + Guard.MustBeSizedAtLeast(output, 16, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } + + /// + /// SVT: svt_av1_iidentity16_c + /// + private static void TransformScalar(ref int input, ref int output) + { + // Normal input should fit into 32-bit. Cast to 64-bit here to avoid + // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c. + output = Av1Math.RoundShift(Sqrt2Times2 * input, Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 1), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 2), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 3), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 4) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 4), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 5) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 5), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 6) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 6), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 7) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 7), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 8) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 8), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 9) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 9), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 10) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 10), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 11) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 11), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 12) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 12), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 13) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 13), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 14) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 14), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 15) = Av1Math.RoundShift(Sqrt2Times2 * Unsafe.Add(ref input, 15), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs new file mode 100644 index 0000000000..22ed590ba5 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity32Inverse1dTransformer.cs @@ -0,0 +1,36 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Identity32Inverse1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 32, nameof(input)); + Guard.MustBeSizedAtLeast(output, 32, nameof(output)); + ref int inputRef = ref input[0]; + ref int outputRef = ref output[0]; + TransformScalar(ref inputRef, ref outputRef); + TransformScalar(ref Unsafe.Add(ref inputRef, 8), ref Unsafe.Add(ref outputRef, 8)); + TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16)); + TransformScalar(ref Unsafe.Add(ref inputRef, 24), ref Unsafe.Add(ref outputRef, 24)); + } + + /// + /// SVT: svt_av1_iidentity32_c + /// + private static void TransformScalar(ref int input, ref int output) + { + output = input << 2; + Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 2; + Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 2; + Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 2; + Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 2; + Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 2; + Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 2; + Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 2; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs new file mode 100644 index 0000000000..6547f16ae0 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity4Inverse1dTransformer.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Identity4Inverse1dTransformer : IAv1Forward1dTransformer +{ + internal const int Sqrt2Bits = 12; + + // 2^12 * sqrt(2) + internal const long Sqrt2 = 5793; + + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 4, nameof(input)); + Guard.MustBeSizedAtLeast(output, 4, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } + + /// + /// SVT: svt_av1_iidentity4_c + /// + private static void TransformScalar(ref int input, ref int output) + { + // Normal input should fit into 32-bit. Cast to 64-bit here to avoid + // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c. + output = Av1Math.RoundShift(Sqrt2 * input, Sqrt2Bits); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 1), Sqrt2Bits); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 2), Sqrt2Bits); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2 * Unsafe.Add(ref input, 3), Sqrt2Bits); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs new file mode 100644 index 0000000000..7c75fa2a68 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity64Inverse1dTransformer.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Identity64Inverse1dTransformer : IAv1Forward1dTransformer +{ + private const long Sqrt2Times4 = Av1Identity4Inverse1dTransformer.Sqrt2 >> 2; + + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 64, nameof(input)); + Guard.MustBeSizedAtLeast(output, 64, nameof(output)); + ref int inputRef = ref input[0]; + ref int outputRef = ref output[0]; + TransformScalar(ref inputRef, ref outputRef); + TransformScalar(ref Unsafe.Add(ref inputRef, 8), ref Unsafe.Add(ref outputRef, 8)); + TransformScalar(ref Unsafe.Add(ref inputRef, 16), ref Unsafe.Add(ref outputRef, 16)); + TransformScalar(ref Unsafe.Add(ref inputRef, 24), ref Unsafe.Add(ref outputRef, 24)); + TransformScalar(ref Unsafe.Add(ref inputRef, 32), ref Unsafe.Add(ref outputRef, 32)); + TransformScalar(ref Unsafe.Add(ref inputRef, 40), ref Unsafe.Add(ref outputRef, 40)); + TransformScalar(ref Unsafe.Add(ref inputRef, 48), ref Unsafe.Add(ref outputRef, 48)); + TransformScalar(ref Unsafe.Add(ref inputRef, 56), ref Unsafe.Add(ref outputRef, 56)); + } + + /// + /// SVT: svt_av1_iidentity64_c + /// + private static void TransformScalar(ref int input, ref int output) + { + // Normal input should fit into 32-bit. Cast to 64-bit here to avoid + // overflow with corrupted/fuzzed input. The same for av1_iidentity/16/64_c. + output = Av1Math.RoundShift(Sqrt2Times4 * input, Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 1) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 1), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 2) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 2), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 3) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 3), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 4) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 4), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 5) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 5), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 6) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 6), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + Unsafe.Add(ref output, 7) = Av1Math.RoundShift(Sqrt2Times4 * Unsafe.Add(ref input, 7), Av1Identity4Inverse1dTransformer.Sqrt2Bits); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs new file mode 100644 index 0000000000..5528f03dd7 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Inverse/Av1Identity8Inverse1dTransformer.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +internal class Av1Identity8Inverse1dTransformer : IAv1Forward1dTransformer +{ + public void Transform(Span input, Span output, int cosBit, Span stageRange) + { + Guard.MustBeSizedAtLeast(input, 8, nameof(input)); + Guard.MustBeSizedAtLeast(output, 8, nameof(output)); + TransformScalar(ref input[0], ref output[0]); + } + + /// + /// SVT: svt_av1_iidentity8_c + /// + private static void TransformScalar(ref int input, ref int output) + { + output = input << 1; + Unsafe.Add(ref output, 1) = Unsafe.Add(ref input, 1) << 1; + Unsafe.Add(ref output, 2) = Unsafe.Add(ref input, 2) << 1; + Unsafe.Add(ref output, 3) = Unsafe.Add(ref input, 3) << 1; + Unsafe.Add(ref output, 4) = Unsafe.Add(ref input, 4) << 1; + Unsafe.Add(ref output, 5) = Unsafe.Add(ref input, 5) << 1; + Unsafe.Add(ref output, 6) = Unsafe.Add(ref input, 6) << 1; + Unsafe.Add(ref output, 7) = Unsafe.Add(ref input, 7) << 1; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs new file mode 100644 index 0000000000..f25082b4d7 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs @@ -0,0 +1,196 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +/// +/// SVT: test/InvTxfm1dTest.cc +/// SVT: test/InvTxfm2dAsmTest.cc +/// +[Trait("Format", "Avif")] +public class Av1InverseTransformTests +{ + [Fact] + public void AccuracyOfDct1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size4x4, 1); + + // [Fact] + public void AccuracyOfDct1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size8x8, 1, 2); + + // [Fact] + public void AccuracyOfDct1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size16x16, 1, 3); + + // [Fact] + public void AccuracyOfDct1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size32x32, 1, 4); + + // [Fact] + public void AccuracyOfDct1dTransformSize64Test() + => AssertAccuracy1d(Av1TransformType.DctDct, Av1TransformSize.Size64x64, 1, 5); + + [Fact] + public void AccuracyOfAdst1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size4x4, 1); + + // [Fact] + public void AccuracyOfAdst1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size8x8, 1, 2); + + // [Fact] + public void AccuracyOfAdst1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size16x16, 1, 3); + + // [Fact] + public void AccuracyOfAdst1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformType.AdstAdst, Av1TransformSize.Size32x32, 1, 3); + + [Fact] + public void AccuracyOfIdentity1dTransformSize4Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 1); + + [Fact] + public void AccuracyOfIdentity1dTransformSize8Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size8x8, 2); + + [Fact] + public void AccuracyOfIdentity1dTransformSize16Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size16x16, 1); + + [Fact] + public void AccuracyOfIdentity1dTransformSize32Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size32x32, 4); + + [Fact] + public void AccuracyOfIdentity1dTransformSize64Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size64x64, 4); + + [Fact] + public void AccuracyOfEchoTransformSize4Test() + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 0, new EchoTestTransformer(), new EchoTestTransformer()); + + private static void AssertAccuracy1d( + Av1TransformType transformType, + Av1TransformSize transformSize, + int scaleLog2, + int allowedError = 1) + { + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + IAv1Forward1dTransformer forward = GetForwardTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer inverse = GetInverseTransformer(config.TransformFunctionTypeColumn); + AssertAccuracy1d(transformType, transformSize, scaleLog2, forward, inverse, allowedError); + } + + private static void AssertAccuracy1d( + Av1TransformType transformType, + Av1TransformSize transformSize, + int scaleLog2, + IAv1Forward1dTransformer forwardTransformer, + IAv1Forward1dTransformer inverseTransformer, + int allowedError = 1) + { + const int bitDepth = 10; + Random rnd = new(0); + const int testBlockCount = 100; // Originally set to: 5000 + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + config.GenerateStageRange(bitDepth); + int width = config.TransformSize.GetWidth(); + + int[] inputOfTest = new int[width]; + int[] outputOfTest = new int[width]; + int[] outputReference = new int[width]; + for (int ti = 0; ti < testBlockCount; ++ti) + { + // prepare random test data + for (int ni = 0; ni < width; ++ni) + { + inputOfTest[ni] = (short)rnd.Next((1 << bitDepth) - 1); + outputReference[ni] = 0; + outputOfTest[ni] = 255; + } + + // calculate in forward transform functions + forwardTransformer.Transform( + inputOfTest, + outputReference, + config.CosBitColumn, + config.StageRangeColumn); + + // calculate in inverse transform functions + inverseTransformer.Transform( + outputReference, + outputOfTest, + config.CosBitColumn, + config.StageRangeColumn); + + // Assert + Assert.True(CompareWithError(inputOfTest, outputOfTest.Select(x => x >> scaleLog2).ToArray(), allowedError), $"Error: {GetMaximumError(inputOfTest, outputOfTest)}"); + } + } + + private static IAv1Forward1dTransformer GetForwardTransformer(Av1TransformFunctionType func) => + func switch + { + Av1TransformFunctionType.Dct4 => new Av1Dct4Forward1dTransformer(), + Av1TransformFunctionType.Dct8 => new Av1Dct8Forward1dTransformer(), + Av1TransformFunctionType.Dct16 => new Av1Dct16Forward1dTransformer(), + Av1TransformFunctionType.Dct32 => new Av1Dct32Forward1dTransformer(), + Av1TransformFunctionType.Dct64 => new Av1Dct64Forward1dTransformer(), + Av1TransformFunctionType.Adst4 => new Av1Adst4Forward1dTransformer(), + Av1TransformFunctionType.Adst8 => new Av1Adst8Forward1dTransformer(), + Av1TransformFunctionType.Adst16 => new Av1Adst16Forward1dTransformer(), + Av1TransformFunctionType.Adst32 => new Av1Adst32Forward1dTransformer(), + Av1TransformFunctionType.Identity4 => new Av1Identity4Forward1dTransformer(), + Av1TransformFunctionType.Identity8 => new Av1Identity8Forward1dTransformer(), + Av1TransformFunctionType.Identity16 => new Av1Identity16Forward1dTransformer(), + Av1TransformFunctionType.Identity32 => new Av1Identity32Forward1dTransformer(), + Av1TransformFunctionType.Identity64 => new Av1Identity64Forward1dTransformer(), + Av1TransformFunctionType.Invalid => null, + _ => null, + }; + + private static IAv1Forward1dTransformer GetInverseTransformer(Av1TransformFunctionType func) => + func switch + { + Av1TransformFunctionType.Dct4 => new Av1Dct4Inverse1dTransformer(), + Av1TransformFunctionType.Dct8 => null, // new Av1Dct8Inverse1dTransformer(), + Av1TransformFunctionType.Dct16 => null, // new Av1Dct16Inverse1dTransformer(), + Av1TransformFunctionType.Dct32 => null, // new Av1Dct32Inverse1dTransformer(), + Av1TransformFunctionType.Dct64 => null, // new Av1Dct64Inverse1dTransformer(), + Av1TransformFunctionType.Adst4 => new Av1Adst4Inverse1dTransformer(), + Av1TransformFunctionType.Adst8 => null, // new Av1Adst8Inverse1dTransformer(), + Av1TransformFunctionType.Adst16 => null, // new Av1Adst16Inverse1dTransformer(), + Av1TransformFunctionType.Adst32 => null, // new Av1Adst32Inverse1dTransformer(), + Av1TransformFunctionType.Identity4 => new Av1Identity4Inverse1dTransformer(), + Av1TransformFunctionType.Identity8 => new Av1Identity8Inverse1dTransformer(), + Av1TransformFunctionType.Identity16 => new Av1Identity16Inverse1dTransformer(), + Av1TransformFunctionType.Identity32 => new Av1Identity32Inverse1dTransformer(), + Av1TransformFunctionType.Identity64 => new Av1Identity64Inverse1dTransformer(), + Av1TransformFunctionType.Invalid => null, + _ => null, + }; + + private static bool CompareWithError(Span expected, Span actual, int allowedError) + { + // compare for the result is within accuracy + int maximumErrorInTest = GetMaximumError(expected, actual); + return maximumErrorInTest <= allowedError; + } + + private static int GetMaximumError(Span expected, Span actual) + { + int maximumErrorInTest = 0; + int count = Math.Min(expected.Length, 32); + for (int ni = 0; ni < count; ++ni) + { + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - expected[ni])); + } + + return maximumErrorInTest; + } +} From 34e8fc230c67b75de7feb3590ffd50bd4e23fce2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 23 Oct 2024 22:16:44 +0200 Subject: [PATCH 169/190] 2-dimensional inverse transform implementation --- .../Transform/Av1DctDctInverseTransformer.cs | 157 -------- ...ory.cs => Av1ForwardTransformerFactory.cs} | 2 +- .../Av1/Transform/Av1Inverse2dTransformer.cs | 363 ++++++++++++++++++ .../Av1/Transform/Av1InverseTransformMath.cs | 91 ++++- .../Av1/Transform/Av1InverseTransformer.cs | 4 +- .../Transform/Av1InverseTransformerFactory.cs | 38 ++ .../Transform/Av1TransformSizeExtensions.cs | 39 ++ .../Transform/InverseTransformerFactory.cs | 19 - 8 files changed, 533 insertions(+), 180 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs rename src/ImageSharp/Formats/Heif/Av1/Transform/{ForwardTransformerFactory.cs => Av1ForwardTransformerFactory.cs} (98%) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs deleted file mode 100644 index e7363ed42b..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1DctDctInverseTransformer.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -internal class Av1DctDctInverseTransformer -{ - private const int UnitQuantizationShift = 2; - - internal static void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) - { - Guard.IsTrue(transformFunctionParameters.TransformType == Av1TransformType.DctDct, nameof(transformFunctionParameters.TransformType), "This class implements DCT-DCT transformations only."); - - switch (transformFunctionParameters.TransformSize) - { - case Av1TransformSize.Size4x4: - InverseWhalshHadamard4x4(ref coefficients, ref readBuffer[0], readStride, ref writeBuffer[0], writeStride, transformFunctionParameters.EndOfBuffer, transformFunctionParameters.BitDepth); - break; - default: - throw new NotImplementedException("Only 4x4 transformation size supported for now"); - } - } - - /// - /// SVT: highbd_iwht4x4_add - /// - private static void InverseWhalshHadamard4x4(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int endOfBuffer, int bitDepth) - { - if (endOfBuffer > 1) - { - InverseWhalshHadamard4x4Add16(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); - } - else - { - InverseWhalshHadamard4x4Add1(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); - } - } - - /// - /// SVT: svt_av1_highbd_iwht4x4_16_add_c - /// - private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) - { - /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, - 0.5 shifts per pixel. */ - int i; - Span output = stackalloc ushort[16]; - ushort a1, b1, c1, d1, e1; - ref int ip = ref input; - ref ushort op = ref output[0]; - ref ushort opTmp = ref output[0]; - ref ushort destForRead = ref Unsafe.As(ref destinationForRead); - ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); - - for (i = 0; i < 4; i++) - { - a1 = (ushort)(ip >> UnitQuantizationShift); - c1 = (ushort)(Unsafe.Add(ref ip, 1) >> UnitQuantizationShift); - d1 = (ushort)(Unsafe.Add(ref ip, 2) >> UnitQuantizationShift); - b1 = (ushort)(Unsafe.Add(ref ip, 3) >> UnitQuantizationShift); - a1 += c1; - d1 -= b1; - e1 = (ushort)((a1 - d1) >> 1); - b1 = (ushort)(e1 - b1); - c1 = (ushort)(e1 - c1); - a1 -= b1; - d1 += c1; - op = a1; - Unsafe.Add(ref op, 1) = b1; - Unsafe.Add(ref op, 2) = c1; - Unsafe.Add(ref op, 3) = d1; - ip = ref Unsafe.Add(ref ip, 4); - op = ref Unsafe.Add(ref op, 4); - } - - ip = opTmp; - for (i = 0; i < 4; i++) - { - a1 = (ushort)ip; - c1 = (ushort)Unsafe.Add(ref ip, 4); - d1 = (ushort)Unsafe.Add(ref ip, 8); - b1 = (ushort)Unsafe.Add(ref ip, 12); - a1 += c1; - d1 -= b1; - e1 = (ushort)((a1 - d1) >> 1); - b1 = (ushort)(e1 - b1); - c1 = (ushort)(e1 - c1); - a1 -= b1; - d1 += c1; - /* Disabled in normal build - range_check_value(a1, (int8_t)(bd + 1)); - range_check_value(b1, (int8_t)(bd + 1)); - range_check_value(c1, (int8_t)(bd + 1)); - range_check_value(d1, (int8_t)(bd + 1)); - */ - - destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), d1, bitDepth); - - ip = ref Unsafe.Add(ref ip, 1); - destForRead = ref Unsafe.Add(ref destForRead, 1); - destForWrite = ref Unsafe.Add(ref destForWrite, 1); - } - } - - /// - /// SVT: svt_av1_highbd_iwht4x4_1_add_c - /// - private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) - { - int i; - ushort a1, e1; - Span tmp = stackalloc int[4]; - ref int ip = ref input; - ref int ipTmp = ref tmp[0]; - ref int op = ref tmp[0]; - ref ushort destForRead = ref Unsafe.As(ref destinationForRead); - ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); - - a1 = (ushort)(ip >> UnitQuantizationShift); - e1 = (ushort)(a1 >> 1); - a1 -= e1; - op = a1; - Unsafe.Add(ref op, 1) = e1; - Unsafe.Add(ref op, 2) = e1; - Unsafe.Add(ref op, 3) = e1; - - ip = ipTmp; - for (i = 0; i < 4; i++) - { - e1 = (ushort)(ip >> 1); - a1 = (ushort)(ip - e1); - destForWrite = ClipPixelAdd(destForRead, a1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite * 2) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth); - Unsafe.Add(ref destForWrite, strideForWrite * 3) = ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), a1, bitDepth); - ip = ref Unsafe.Add(ref ip, 1); - destForRead = ref Unsafe.Add(ref destForRead, 1); - destForWrite = ref Unsafe.Add(ref destForWrite, 1); - } - } - - private static ushort ClipPixelAdd(ushort value, int trans, int bitDepth) - => ClipPixel(value + trans, bitDepth); - - private static ushort ClipPixel(int value, int bitDepth) - => bitDepth switch - { - 10 => (ushort)Av1Math.Clamp(value, 0, 1023), - 12 => (ushort)Av1Math.Clamp(value, 0, 4095), - _ => (ushort)Av1Math.Clamp(value, 0, 255), - }; -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs similarity index 98% rename from src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs rename to src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs index 4463f2d225..c8664655d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/ForwardTransformerFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ForwardTransformerFactory.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -internal static class ForwardTransformerFactory +internal static class Av1ForwardTransformerFactory { internal static void EstimateTransform( Span residualBuffer, diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs new file mode 100644 index 0000000000..97371b376b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs @@ -0,0 +1,363 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal class Av1Inverse2dTransformer +{ + private const int UnitQuantizationShift = 2; + + /// + /// SVT: inv_txfm2d_add_c + /// + internal static void InverseTransform2dAdd( + Span input, + Span outputForRead, + int strideForRead, + Span outputForWrite, + int strideForWrite, + Av1Transform2dFlipConfiguration config, + Span transformFunctionBuffer, + int bitDepth) + { + // Note when assigning txfm_size_col, we use the txfm_size from the + // row configuration and vice versa. This is intentionally done to + // accurately perform rectangular transforms. When the transform is + // rectangular, the number of columns will be the same as the + // txfm_size stored in the row cfg struct. It will make no difference + // for square transforms. + int transformWidth = config.TransformSize.GetWidth(); + int transformHeight = config.TransformSize.GetHeight(); + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = config.TransformSize.GetRectangleLogRatio(); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow); + Guard.NotNull(functionColumn); + Guard.NotNull(functionRow); + + // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col) + // it is used for intermediate data buffering + int bufferOffset = Math.Max(transformHeight, transformWidth); + Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer)); + Span tempIn = transformFunctionBuffer; + Span tempOut = tempIn.Slice(bufferOffset); + Span buf = tempOut.Slice(bufferOffset); + Span bufPtr = buf; + int c, r; + + // Rows + for (r = 0; r < transformHeight; ++r) + { + if (Math.Abs(rectangleType) == 1) + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount); + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + else + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = input[c]; + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + + Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); + input.Slice(transformWidth); + bufPtr.Slice(transformWidth); + } + + // Columns + for (c = 0; c < transformWidth; ++c) + { + if (!config.FlipLeftToRight) + { + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + c]; + } + } + else + { + // flip left right + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)]; + } + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16)); + functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn); + Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]); + if (!config.FlipUpsideDown) + { + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = + Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r], bitDepth); + } + } + else + { + // flip upside down + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd( + outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1], bitDepth); + } + } + } + } + + /// + /// SVT: inv_txfm2d_add_c + /// + internal static void InverseTransform2dAdd( + Span input, + Span outputForRead, + int strideForRead, + Span outputForWrite, + int strideForWrite, + Av1Transform2dFlipConfiguration config, + Span transformFunctionBuffer) + { + const int bitDepth = 8; + + // Note when assigning txfm_size_col, we use the txfm_size from the + // row configuration and vice versa. This is intentionally done to + // accurately perform rectangular transforms. When the transform is + // rectangular, the number of columns will be the same as the + // txfm_size stored in the row cfg struct. It will make no difference + // for square transforms. + int transformWidth = config.TransformSize.GetWidth(); + int transformHeight = config.TransformSize.GetHeight(); + + // Take the shift from the larger dimension in the rectangular case. + Span shift = config.Shift; + int rectangleType = config.TransformSize.GetRectangleLogRatio(); + config.GenerateStageRange(bitDepth); + + int cosBitColumn = config.CosBitColumn; + int cosBitRow = config.CosBitRow; + IAv1Forward1dTransformer? functionColumn = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeColumn); + IAv1Forward1dTransformer? functionRow = Av1InverseTransformerFactory.GetTransformer(config.TransformFunctionTypeRow); + Guard.NotNull(functionColumn); + Guard.NotNull(functionRow); + + // txfm_buf's length is txfm_size_row * txfm_size_col + 2 * MAX(txfm_size_row, txfm_size_col) + // it is used for intermediate data buffering + int bufferOffset = Math.Max(transformHeight, transformWidth); + Guard.MustBeSizedAtLeast(transformFunctionBuffer, (transformHeight * transformWidth) + (2 * bufferOffset), nameof(transformFunctionBuffer)); + Span tempIn = transformFunctionBuffer; + Span tempOut = tempIn.Slice(bufferOffset); + Span buf = tempOut.Slice(bufferOffset); + Span bufPtr = buf; + int c, r; + + // Rows + for (r = 0; r < transformHeight; ++r) + { + if (Math.Abs(rectangleType) == 1) + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = Av1Math.RoundShift((long)input[c] * Av1InverseTransformMath.NewInverseSqrt2, Av1InverseTransformMath.NewSqrt2BitCount); + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + else + { + for (c = 0; c < transformWidth; ++c) + { + tempIn[c] = input[c]; + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformWidth, (byte)(bitDepth + 8)); + functionRow.Transform(tempIn, bufPtr, cosBitRow, config.StageRangeRow); + } + + Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); + input.Slice(transformWidth); + bufPtr.Slice(transformWidth); + } + + // Columns + for (c = 0; c < transformWidth; ++c) + { + if (!config.FlipLeftToRight) + { + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + c]; + } + } + else + { + // flip left right + for (r = 0; r < transformHeight; ++r) + { + tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)]; + } + } + + Av1InverseTransformMath.ClampBuffer(tempIn, transformHeight, (byte)Math.Max(bitDepth + 6, 16)); + functionColumn.Transform(tempIn, tempOut, cosBitColumn, config.StageRangeColumn); + Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]); + if (!config.FlipUpsideDown) + { + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = + Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r]); + } + } + else + { + // flip upside down + for (r = 0; r < transformHeight; ++r) + { + outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd( + outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1]); + } + } + } + } + + /// + /// SVT: highbd_iwht4x4_add + /// + private static void InverseWhalshHadamard4x4(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int endOfBuffer, int bitDepth) + { + if (endOfBuffer > 1) + { + InverseWhalshHadamard4x4Add16(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + else + { + InverseWhalshHadamard4x4Add1(ref input, ref destinationForRead, strideForRead, ref destinationForWrite, strideForWrite, bitDepth); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_16_add_c + /// + private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + 0.5 shifts per pixel. */ + int i; + Span output = stackalloc ushort[16]; + ushort a1, b1, c1, d1, e1; + ref int ip = ref input; + ref ushort op = ref output[0]; + ref ushort opTmp = ref output[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + for (i = 0; i < 4; i++) + { + a1 = (ushort)(ip >> UnitQuantizationShift); + c1 = (ushort)(Unsafe.Add(ref ip, 1) >> UnitQuantizationShift); + d1 = (ushort)(Unsafe.Add(ref ip, 2) >> UnitQuantizationShift); + b1 = (ushort)(Unsafe.Add(ref ip, 3) >> UnitQuantizationShift); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + op = a1; + Unsafe.Add(ref op, 1) = b1; + Unsafe.Add(ref op, 2) = c1; + Unsafe.Add(ref op, 3) = d1; + ip = ref Unsafe.Add(ref ip, 4); + op = ref Unsafe.Add(ref op, 4); + } + + ip = opTmp; + for (i = 0; i < 4; i++) + { + a1 = (ushort)ip; + c1 = (ushort)Unsafe.Add(ref ip, 4); + d1 = (ushort)Unsafe.Add(ref ip, 8); + b1 = (ushort)Unsafe.Add(ref ip, 12); + a1 += c1; + d1 -= b1; + e1 = (ushort)((a1 - d1) >> 1); + b1 = (ushort)(e1 - b1); + c1 = (ushort)(e1 - c1); + a1 -= b1; + d1 += c1; + /* Disabled in normal build + range_check_value(a1, (int8_t)(bd + 1)); + range_check_value(b1, (int8_t)(bd + 1)); + range_check_value(c1, (int8_t)(bd + 1)); + range_check_value(d1, (int8_t)(bd + 1)); + */ + + destForWrite = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), c1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), d1, bitDepth); + + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } + + /// + /// SVT: svt_av1_highbd_iwht4x4_1_add_c + /// + private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) + { + int i; + ushort a1, e1; + Span tmp = stackalloc int[4]; + ref int ip = ref input; + ref int ipTmp = ref tmp[0]; + ref int op = ref tmp[0]; + ref ushort destForRead = ref Unsafe.As(ref destinationForRead); + ref ushort destForWrite = ref Unsafe.As(ref destinationForWrite); + + a1 = (ushort)(ip >> UnitQuantizationShift); + e1 = (ushort)(a1 >> 1); + a1 -= e1; + op = a1; + Unsafe.Add(ref op, 1) = e1; + Unsafe.Add(ref op, 2) = e1; + Unsafe.Add(ref op, 3) = e1; + + ip = ipTmp; + for (i = 0; i < 4; i++) + { + e1 = (ushort)(ip >> 1); + a1 = (ushort)(ip - e1); + destForWrite = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 2) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 2), a1, bitDepth); + Unsafe.Add(ref destForWrite, strideForWrite * 3) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead * 3), a1, bitDepth); + ip = ref Unsafe.Add(ref ip, 1); + destForRead = ref Unsafe.Add(ref destForRead, 1); + destForWrite = ref Unsafe.Add(ref destForWrite, 1); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs index 52ee9c26c6..c8c7a04d3e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs @@ -1,10 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Quantization; +using System; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1InverseTransformMath { + public const int NewInverseSqrt2 = 2896; + public const int NewSqrt2BitCount = 12; + public static readonly int[,] AcQLookup = new int[3, 256] { { @@ -145,4 +150,88 @@ public static void InvertQuantization(out int quantization, out int shift, int d quantization = m - (1 << 16); shift = 1 << (16 - l); } + + public static byte ClipPixelAdd(byte dest, long trans) + { + trans = CheckRange(trans, 8); + return (byte)ClipPixelHighBitDepth(dest + trans, 8); + } + + public static ushort ClipPixelAdd(ushort dest, long trans, int bitDepth) + { + trans = CheckRange(trans, bitDepth); + return ClipPixelHighBitDepth(dest + trans, bitDepth); + } + + private static ushort ClipPixelHighBitDepth(long val, int bd) + { + switch (bd) + { + case 8: + default: + return (ushort)Av1Math.Clamp(val, 0, 255); + case 10: + return (ushort)Av1Math.Clamp(val, 0, 1023); + case 12: + return (ushort)Av1Math.Clamp(val, 0, 4095); + } + } + + public static void RoundShiftArray(Span arr, int size, int bit) + { + int i; + if (bit == 0) + { + return; + } + else + { + if (bit > 0) + { + for (i = 0; i < size; i++) + { + arr[i] = Av1Math.RoundShift(arr[i], bit); + } + } + else + { + for (i = 0; i < size; i++) + { + arr[i] = arr[i] * (1 << (-bit)); + } + } + } + } + + internal static void ClampBuffer(Span buffer, int size, byte bit) + { + for (int i = 0; i < size; i++) + { + buffer[i] = ClampValue(buffer[i], bit); + } + } + + private static int ClampValue(int value, byte bit) + { + if (bit <= 0) + { + return value; // Do nothing for invalid clamp bit. + } + + long max_value = (1L << (bit - 1)) - 1; + long min_value = -(1L << (bit - 1)); + return (int)Av1Math.Clamp(value, min_value, max_value); + } + + private static long CheckRange(long input, int bd) + { + // AV1 TX case + // - 8 bit: signed 16 bit integer + // - 10 bit: signed 18 bit integer + // - 12 bit: signed 20 bit integer + // - max quantization error = 1828 << (bd - 8) + int int_max = (1 << (7 + bd)) - 1 + (914 << (bd - 7)); + int int_min = -int_max - 1; + return Av1Math.Clamp(input, int_min, int_max); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs index edb04aeae6..ed05dc9e6d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -28,8 +28,8 @@ public static void Reconstruct8Bit(Span coefficientsBuffer, Span reco transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); } - InverseTransformerFactory.InverseTransformAdd( - ref coefficientsBuffer[0], reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); + Av1InverseTransformerFactory.InverseTransformAdd( + coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs new file mode 100644 index 0000000000..1f73edae1b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +internal static class Av1InverseTransformerFactory +{ + public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.MustBeLessThanOrEqualTo(transformFunctionParameters.BitDepth, 8, nameof(transformFunctionParameters)); + Guard.IsFalse(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 8-bit pipeline while 16-bit is requested."); + int width = transformFunctionParameters.TransformSize.GetWidth(); + int height = transformFunctionParameters.TransformSize.GetHeight(); + Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; + Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); + Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer); + } + + public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + { + Guard.IsTrue(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 16-bit pipeline while 8-bit is requested."); + int width = transformFunctionParameters.TransformSize.GetWidth(); + int height = transformFunctionParameters.TransformSize.GetHeight(); + Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; + Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); + Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer, transformFunctionParameters.BitDepth); + } + + internal static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType type) => type switch + { + Av1TransformFunctionType.Dct4 => new Av1Dct4Inverse1dTransformer(), + Av1TransformFunctionType.Adst4 => new Av1Adst4Inverse1dTransformer(), + Av1TransformFunctionType.Identity4 => new Av1Identity4Inverse1dTransformer(), + _ => null + }; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs index 3766c5a6d8..3341d2fc80 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1TransformSizeExtensions.cs @@ -172,4 +172,43 @@ public static int GetScale(this Av1TransformSize size) public static int GetBlockWidthLog2(this Av1TransformSize size) => BlockWidthLog2[(int)GetAdjusted(size)]; public static int GetBlockHeightLog2(this Av1TransformSize size) => BlockHeightLog2[(int)GetAdjusted(size)]; + + public static int GetRectangleLogRatio(this Av1TransformSize size) + { + int col = GetWidth(size); + int row = GetHeight(size); + if (col == row) + { + return 0; + } + + if (col > row) + { + if (col == row * 2) + { + return 1; + } + + if (col == row * 4) + { + return 2; + } + + throw new InvalidImageContentException("Unsupported transform size"); + } + else + { + if (row == col * 2) + { + return -1; + } + + if (row == col * 4) + { + return -2; + } + + throw new InvalidImageContentException("Unsupported transform size"); + } + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs deleted file mode 100644 index feb7dbe76c..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/InverseTransformerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; - -internal static class InverseTransformerFactory -{ - internal static unsafe void InverseTransformAdd(ref int coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) - { - switch (transformFunctionParameters.TransformType) - { - case Av1TransformType.DctDct: - Av1DctDctInverseTransformer.InverseTransformAdd(ref coefficients, readBuffer, readStride, writeBuffer, writeStride, transformFunctionParameters); - break; - default: - throw new InvalidImageContentException("Unknown transform type: " + transformFunctionParameters.TransformType); - } - } -} From 2fa5ca11a10a78fd98b07f3c2376ace5e38b4f36 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 2 Nov 2024 15:29:25 +0100 Subject: [PATCH 170/190] Tests for inverse transform --- .../Av1/Transform/Av1Inverse2dTransformer.cs | 48 ++- .../Av1/Transform/Av1InverseTransformMath.cs | 25 +- .../Transform/Av1InverseTransformerFactory.cs | 6 +- .../Heif/Av1/Av1InverseTransformTests.cs | 305 +++++++++++++++++- 4 files changed, 356 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs index 97371b376b..eda267bb97 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System.Runtime.CompilerServices; +using System.ComponentModel; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -12,11 +12,11 @@ internal class Av1Inverse2dTransformer /// /// SVT: inv_txfm2d_add_c /// - internal static void InverseTransform2dAdd( + internal static void Transform2dAdd( Span input, - Span outputForRead, + Span outputForRead, int strideForRead, - Span outputForWrite, + Span outputForWrite, int strideForWrite, Av1Transform2dFlipConfiguration config, Span transformFunctionBuffer, @@ -78,8 +78,8 @@ internal static void InverseTransform2dAdd( } Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); - input.Slice(transformWidth); - bufPtr.Slice(transformWidth); + input = input[transformWidth..]; + bufPtr = bufPtr.Slice(transformWidth); } // Columns @@ -87,17 +87,21 @@ internal static void InverseTransform2dAdd( { if (!config.FlipLeftToRight) { + int t = c; for (r = 0; r < transformHeight; ++r) { - tempIn[r] = buf[(r * transformWidth) + c]; + tempIn[r] = buf[t]; + t += transformWidth; } } else { // flip left right + int t = transformWidth - c - 1; for (r = 0; r < transformHeight; ++r) { - tempIn[r] = buf[(r * transformWidth) + (transformWidth - c - 1)]; + tempIn[r] = buf[t]; + t += transformWidth; } } @@ -106,19 +110,29 @@ internal static void InverseTransform2dAdd( Av1InverseTransformMath.RoundShiftArray(tempOut, transformHeight, -shift[1]); if (!config.FlipUpsideDown) { + int indexForWrite = c; + int indexForRead = c; for (r = 0; r < transformHeight; ++r) { - outputForWrite[(r * strideForWrite) + c] = - Av1InverseTransformMath.ClipPixelAdd(outputForRead[(r * strideForRead) + c], tempOut[r], bitDepth); + outputForWrite[indexForWrite] = + Av1InverseTransformMath.ClipPixelAdd(outputForRead[indexForRead], tempOut[r], bitDepth); + indexForWrite += strideForWrite; + indexForRead += strideForRead; } } else { // flip upside down + int indexForWrite = c; + int indexForRead = c; + int indexTemp = transformHeight - 1; for (r = 0; r < transformHeight; ++r) { - outputForWrite[(r * strideForWrite) + c] = Av1InverseTransformMath.ClipPixelAdd( - outputForRead[(r * strideForRead) + c], tempOut[transformHeight - r - 1], bitDepth); + outputForWrite[indexForWrite] = Av1InverseTransformMath.ClipPixelAdd( + outputForRead[indexForRead], tempOut[indexTemp], bitDepth); + indexForWrite += strideForWrite; + indexForRead += strideForRead; + indexTemp--; } } } @@ -127,7 +141,7 @@ internal static void InverseTransform2dAdd( /// /// SVT: inv_txfm2d_add_c /// - internal static void InverseTransform2dAdd( + internal static void Transform2dAdd( Span input, Span outputForRead, int strideForRead, @@ -240,6 +254,7 @@ internal static void InverseTransform2dAdd( } } + /* /// /// SVT: highbd_iwht4x4_add /// @@ -260,8 +275,8 @@ private static void InverseWhalshHadamard4x4(ref int input, ref byte destination /// private static void InverseWhalshHadamard4x4Add16(ref int input, ref byte destinationForRead, int strideForRead, ref byte destinationForWrite, int strideForWrite, int bitDepth) { - /* 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, - 0.5 shifts per pixel. */ + // 4-point reversible, orthonormal inverse Walsh-Hadamard in 3.5 adds, + // 0.5 shifts per pixel. int i; Span output = stackalloc ushort[16]; ushort a1, b1, c1, d1, e1; @@ -311,7 +326,7 @@ 0.5 shifts per pixel. */ range_check_value(b1, (int8_t)(bd + 1)); range_check_value(c1, (int8_t)(bd + 1)); range_check_value(d1, (int8_t)(bd + 1)); - */ + // destForWrite = Av1InverseTransformMath.ClipPixelAdd(destForRead, a1, bitDepth); Unsafe.Add(ref destForWrite, strideForWrite) = Av1InverseTransformMath.ClipPixelAdd(Unsafe.Add(ref destForRead, strideForRead), b1, bitDepth); @@ -360,4 +375,5 @@ private static void InverseWhalshHadamard4x4Add1(ref int input, ref byte destina destForWrite = ref Unsafe.Add(ref destForWrite, 1); } } + */ } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs index c8c7a04d3e..4a4cae4564 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformMath.cs @@ -157,23 +157,23 @@ public static byte ClipPixelAdd(byte dest, long trans) return (byte)ClipPixelHighBitDepth(dest + trans, 8); } - public static ushort ClipPixelAdd(ushort dest, long trans, int bitDepth) + public static short ClipPixelAdd(short dest, long trans, int bitDepth) { trans = CheckRange(trans, bitDepth); return ClipPixelHighBitDepth(dest + trans, bitDepth); } - private static ushort ClipPixelHighBitDepth(long val, int bd) + private static short ClipPixelHighBitDepth(long val, int bd) { switch (bd) { case 8: default: - return (ushort)Av1Math.Clamp(val, 0, 255); + return (short)Av1Math.Clamp(val, 0, 255); case 10: - return (ushort)Av1Math.Clamp(val, 0, 1023); + return (short)Av1Math.Clamp(val, 0, 1023); case 12: - return (ushort)Av1Math.Clamp(val, 0, 4095); + return (short)Av1Math.Clamp(val, 0, 4095); } } @@ -234,4 +234,19 @@ private static long CheckRange(long input, int bd) int int_min = -int_max - 1; return Av1Math.Clamp(input, int_min, int_max); } + + internal static int GetMaxEndOfBuffer(Av1TransformSize transformSize) + { + if (transformSize is Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64) + { + return 1024; + } + + if (transformSize is Av1TransformSize.Size16x64 or Av1TransformSize.Size64x16) + { + return 512; + } + + return transformSize.GetSize2d(); + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs index 1f73edae1b..474364df8c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs @@ -15,17 +15,17 @@ public static unsafe void InverseTransformAdd(Span coefficients, Span int height = transformFunctionParameters.TransformSize.GetHeight(); Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); - Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer); + Av1Inverse2dTransformer.Transform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer); } - public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) + public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) { Guard.IsTrue(transformFunctionParameters.Is16BitPipeline, nameof(transformFunctionParameters), "Calling 16-bit pipeline while 8-bit is requested."); int width = transformFunctionParameters.TransformSize.GetWidth(); int height = transformFunctionParameters.TransformSize.GetHeight(); Span buffer = new int[(width * height) + (2 * Math.Max(width, height))]; Av1Transform2dFlipConfiguration config = new(transformFunctionParameters.TransformType, transformFunctionParameters.TransformSize); - Av1Inverse2dTransformer.InverseTransform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer, transformFunctionParameters.BitDepth); + Av1Inverse2dTransformer.Transform2dAdd(coefficients, readBuffer, readStride, writeBuffer, writeStride, config, buffer, transformFunctionParameters.BitDepth); } internal static IAv1Forward1dTransformer? GetTransformer(Av1TransformFunctionType type) => type switch diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs index f25082b4d7..c84531a76a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Inverse; @@ -74,6 +75,151 @@ public void AccuracyOfIdentity1dTransformSize64Test() public void AccuracyOfEchoTransformSize4Test() => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 0, new EchoTestTransformer(), new EchoTestTransformer()); + [Fact] + public void FlipNothingTest() + { + // Arrange + int[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + short[] expected = [ + 2, 4, 6, 8, + 10, 12, 14, 16, + 18, 20, 22, 24, + 26, 28, 30, 32]; + int[] temp = new int[16 + 8]; + short[] actual = new short[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.GenerateStageRange(8); + config.SetFlip(false, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1Inverse2dTransformer.Transform2dAdd( + input, + actual, + 4, + actual, + 4, + config, + temp, + 8); + + // Assert + Assert.True(CompareWithError(expected, actual, 1)); + } + + [Fact] + public void FlipHorizontalTest() + { + // Arrange + short[] expected = [ + 8, 6, 4, 2, + 16, 14, 12, 10, + 24, 22, 20, 18, + 32, 30, 28, 26]; + int[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] temp = new int[16 + 8]; + short[] actual = new short[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(false, true); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1Inverse2dTransformer.Transform2dAdd( + input, + actual, + 4, + actual, + 4, + config, + temp, + 8); + + // Assert + Assert.True(CompareWithError(expected, actual, 1)); + } + + [Fact] + public void FlipVerticalTest() + { + // Arrange + short[] expected = [ + 26, 28, 30, 32, + 18, 20, 22, 24, + 10, 12, 14, 16, + 2, 4, 6, 8]; + int[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] temp = new int[16 + 8]; + short[] actual = new short[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(true, false); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1Inverse2dTransformer.Transform2dAdd( + input, + actual, + 4, + actual, + 4, + config, + temp, + 8); + + // Assert + Assert.True(CompareWithError(expected, actual, 1)); + } + + [Fact] + public void FlipHorizontalAndVerticalTest() + { + // Arrange + short[] expected = [ + 32, 30, 28, 26, + 24, 22, 20, 18, + 16, 14, 12, 10, + 8, 6, 4, 2]; + int[] input = [ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16]; + int[] temp = new int[16 + 8]; + short[] actual = new short[16]; + Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); + config.SetFlip(true, true); + config.SetShift(0, 0, 0); + IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + + // Act + Av1Inverse2dTransformer.Transform2dAdd( + input, + actual, + 4, + actual, + 4, + config, + temp, + 8); + + // Assert + Assert.True(CompareWithError(expected, actual, 1)); + } + private static void AssertAccuracy1d( Av1TransformType transformType, Av1TransformSize transformSize, @@ -129,10 +275,160 @@ private static void AssertAccuracy1d( config.StageRangeColumn); // Assert - Assert.True(CompareWithError(inputOfTest, outputOfTest.Select(x => x >> scaleLog2).ToArray(), allowedError), $"Error: {GetMaximumError(inputOfTest, outputOfTest)}"); + Assert.True(CompareWithError(inputOfTest, outputOfTest.Select(x => x >> scaleLog2).ToArray(), allowedError), $"Error: {GetMaximumError(inputOfTest, outputOfTest)}"); + } + } + + // [Theory] + // [MemberData(nameof(Generate2dCombinations))] + public void Test2dTransformAdd(int txSize, int txType, bool isLossless) + { + const int bitDepth = 8; + Av1TransformType transformType = (Av1TransformType)txType; + Av1TransformSize transformSize = (Av1TransformSize)txSize; + Av1TransformFunctionParameters transformFunctionParams = new() + { + BitDepth = bitDepth, + IsLossless = isLossless, + TransformSize = transformSize, + EndOfBuffer = Av1InverseTransformMath.GetMaxEndOfBuffer(transformSize) + }; + + if (bitDepth > 8 && !isLossless) + { + // Not support 10 bit with not lossless + return; + } + + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + uint stride = (uint)width; + short[] input = new short[width * height]; + int[] referenceOutput = new int[width * height]; + short[] outputOfTest = new short[width * height]; + int[] transformActual = new int[width * height]; + int[] tempBuffer = new int[(width * height) + 128]; + + transformFunctionParams.TransformType = transformType; + Av1Transform2dFlipConfiguration config = new(transformType, transformSize); + config.GenerateStageRange(bitDepth); + + const int loops = 1; // Initially: 10; + for (int k = 0; k < loops; k++) + { + PopulateWithRandomValues(input, bitDepth); + + Av1ForwardTransformer.Transform2d( + input, + referenceOutput, + stride, + transformType, + transformSize, + bitDepth); + Av1Inverse2dTransformer.Transform2dAdd( + referenceOutput.Select(x => x >> 3).ToArray(), + outputOfTest, + width, + outputOfTest, + width, + config, + tempBuffer, + bitDepth); + Av1ForwardTransformer.Transform2d( + outputOfTest.Select(x => (short)(x >> 1)).ToArray(), + transformActual, + stride, + transformType, + transformSize, + bitDepth); + + Assert.True(CompareWithError(referenceOutput, transformActual, 1), $"Error: {GetMaximumError(referenceOutput, transformActual)}"); + } + } + + private static void DivideArray(Span list, int factor) + { + for (int i = 0; i < list.Length; i++) + { + list[i] = list[i] / factor; + } + } + + private static void DivideArray(Span list, int factor) + { + for (int i = 0; i < list.Length; i++) + { + list[i] = (short)(list[i] / factor); } } + public static TheoryData Generate2dCombinations() + { + int[][] transformFunctionSupportMatrix = [ + + // [Size][type]" // O - No; 1 - lossless; 2 - !lossless; 3 - any + /*0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15*/ + [3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2], // 0 TX_4X4, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 1 TX_8X8, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 2 TX_16X16, + [3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0], // 3 TX_32X32, + [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // 4 TX_64X64, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 5 TX_4X8, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 6 TX_8X4, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 7 TX_8X16, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 8 TX_16X8, + [3, 1, 3, 1, 1, 3, 1, 1, 1, 3, 3, 3, 1, 3, 1, 3], // 9 TX_16X32, + [3, 3, 1, 1, 3, 1, 1, 1, 1, 3, 3, 3, 3, 1, 3, 1], // 10 TX_32X16, + [3, 0, 1, 0, 0, 1, 0, 0, 0, 3, 3, 3, 0, 1, 0, 1], // 11 TX_32X64, + [3, 1, 0, 0, 1, 0, 0, 0, 0, 3, 3, 3, 1, 0, 1, 0], // 12 TX_64X32, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 13 TX_4X16, + [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3], // 14 TX_16X4, + [3, 1, 3, 1, 1, 3, 1, 1, 1, 3, 3, 3, 1, 3, 1, 3], // 15 TX_8X32, + [3, 3, 1, 1, 3, 1, 1, 1, 1, 3, 3, 3, 3, 1, 3, 1], // 16 TX_32X8, + [3, 0, 3, 0, 0, 3, 0, 0, 0, 3, 3, 3, 0, 3, 0, 3], // 17 TX_16X64, + [3, 3, 0, 0, 3, 0, 0, 0, 0, 3, 3, 3, 3, 0, 3, 0], // 18 TX_64X16, + /*0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15*/ + ]; + + TheoryData data = []; + for (int size = 0; size < (int)Av1TransformSize.AllSizes; size++) + { + for (int type = 0; type < (int)Av1TransformType.AllTransformTypes; type++) + { + for (int i = 0; i < 2; i++) + { + bool isLossless = i == 1; + if ((isLossless && ((transformFunctionSupportMatrix[size][type] & 1) == 0)) || + (!isLossless && ((transformFunctionSupportMatrix[size][type] & 2) == 0))) + { + continue; + } + + if (IsTransformTypeImplemented((Av1TransformType)type, (Av1TransformSize)size)) + { + data.Add(size, type, isLossless); + } + } + } + } + + return data; + } + + private static void PopulateWithRandomValues(Span input, int bitDepth) + { + Random rnd = new(42); + int maxValue = (1 << (bitDepth - 1)) - 1; + int minValue = -maxValue; + for (int i = 0; i < input.Length; i++) + { + input[i] = (short)rnd.Next(minValue, maxValue); + } + } + + private static bool IsTransformTypeImplemented(Av1TransformType transformType, Av1TransformSize transformSize) + => transformSize == Av1TransformSize.Size4x4; + private static IAv1Forward1dTransformer GetForwardTransformer(Av1TransformFunctionType func) => func switch { @@ -175,20 +471,21 @@ private static IAv1Forward1dTransformer GetInverseTransformer(Av1TransformFuncti _ => null, }; - private static bool CompareWithError(Span expected, Span actual, int allowedError) + private static bool CompareWithError(Span expected, Span actual, int allowedError) + where T : unmanaged { // compare for the result is within accuracy int maximumErrorInTest = GetMaximumError(expected, actual); return maximumErrorInTest <= allowedError; } - private static int GetMaximumError(Span expected, Span actual) + private static int GetMaximumError(Span expected, Span actual) { int maximumErrorInTest = 0; int count = Math.Min(expected.Length, 32); for (int ni = 0; ni < count; ++ni) { - maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(actual[ni] - expected[ni])); + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(Convert.ToInt32(actual[ni]) - Convert.ToInt32(expected[ni]))); } return maximumErrorInTest; From 0e2679b358a21bec1a198a3be9be35b18f4d4b22 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 2 Nov 2024 18:13:01 +0100 Subject: [PATCH 171/190] Add references to SVT library method names --- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 6 +++ .../Quantification/Av1InverseQuantizer.cs | 10 ++++- .../Heif/Av1/Transform/Av1BlockDecoder.cs | 2 - .../Av1/Transform/Av1InverseTransformer.cs | 38 +++++++++---------- .../Transform/Av1InverseTransformerFactory.cs | 3 ++ 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 365cd46a54..77c69df3f7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -95,6 +95,9 @@ private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int sup } } + /// + /// SVT: svt_aom_decode_super_block + /// private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { this.blockDecoder.UpdateSuperblock(superblockInfo); @@ -102,6 +105,9 @@ private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblo this.DecodePartition(modeInfoPosition, superblockInfo, tileInfo); } + /// + /// SVT: decode_partition + /// private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { Av1BlockModeInfo modeInfo = superblockInfo.GetModeInfo(modeInfoPosition); diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs index e06e9ba0f6..4cb7c1cc77 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -78,6 +78,9 @@ public void UpdateDequant(Av1DeQuantizationContext deQuants, Av1SuperblockInfo s } } + /// + /// SVT: svt_aom_inverse_quantize + /// public int InverseQuantize(Av1BlockModeInfo mode, Span level, Span qCoefficients, Av1TransformType transformType, Av1TransformSize transformSize, Av1Plane plane) { Guard.NotNull(this.deQuantsDeltaQ); @@ -135,12 +138,15 @@ ref this.inverseQuantizationMatrix[qmLevel][(int)plane][(int)qmTransformSize] return coefficientCount; } + /// + /// SVT: get_dqv + /// private static int GetDeQuantizedValue(short dequant, int coefficientIndex, ref int iqMatrix) { + const int bias = 1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1); int deQuantifiedValue = dequant; - // TODO: Check order of operators - deQuantifiedValue = ((Unsafe.Add(ref iqMatrix, coefficientIndex) * deQuantifiedValue) + (1 << (Av1ScanOrderConstants.QuantizationMatrixLevelBitCount - 1))) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount; + deQuantifiedValue = ((Unsafe.Add(ref iqMatrix, coefficientIndex) * deQuantifiedValue) + bias) >> Av1ScanOrderConstants.QuantizationMatrixLevelBitCount; return deQuantifiedValue; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index 9e76b20f4f..fc10f30994 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -183,8 +183,6 @@ public void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1Bl quantizationCoefficients, transformBlockReconstructionBuffer, reconstructionStride, - transformBlockReconstructionBuffer, - reconstructionStride, transformSize, transformType, plane, diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs index ed05dc9e6d..6b36f191fc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformer.cs @@ -8,7 +8,7 @@ internal class Av1InverseTransformer /// /// SVT: svt_aom_inv_transform_recon8bit /// - public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBufferRead, int reconstructionReadStride, Span reconstructionBufferWrite, int reconstructionWriteStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) + public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBuffer, int reconstructionStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) { Av1TransformFunctionParameters transformFunctionParameters = new() { @@ -20,33 +20,31 @@ public static void Reconstruct8Bit(Span coefficientsBuffer, Span reco Is16BitPipeline = false }; - if (reconstructionBufferRead != reconstructionBufferWrite) - { - /* When output pointers to read and write are differents, - * then kernel copy also all buffer from read to write, - * and cannot be limited by End Of Buffer calculations. */ - transformFunctionParameters.EndOfBuffer = GetMaxEndOfBuffer(transformSize); - } - Av1InverseTransformerFactory.InverseTransformAdd( - coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); + coefficientsBuffer, reconstructionBuffer, reconstructionStride, reconstructionBuffer, reconstructionStride, transformFunctionParameters); } /// - /// SVT: av1_get_max_eob + /// SVT: svt_aom_inv_transform_recon8bit /// - private static int GetMaxEndOfBuffer(Av1TransformSize transformSize) + public static void Reconstruct8Bit(Span coefficientsBuffer, Span reconstructionBufferRead, int reconstructionReadStride, Span reconstructionBufferWrite, int reconstructionWriteStride, Av1TransformSize transformSize, Av1TransformType transformType, int plane, int numberOfCoefficients, bool isLossless) { - if (transformSize is Av1TransformSize.Size64x64 or Av1TransformSize.Size64x32 or Av1TransformSize.Size32x64) + Av1TransformFunctionParameters transformFunctionParameters = new() { - return 1024; - } + TransformType = transformType, + TransformSize = transformSize, + EndOfBuffer = numberOfCoefficients, + IsLossless = isLossless, + BitDepth = 8, + Is16BitPipeline = false + }; - if (transformSize is Av1TransformSize.Size16x64 or Av1TransformSize.Size64x16) - { - return 512; - } + /* When output pointers to read and write are differents, + * then kernel copy also all buffer from read to write, + * and cannot be limited by End Of Buffer calculations. */ + transformFunctionParameters.EndOfBuffer = Av1InverseTransformMath.GetMaxEndOfBuffer(transformSize); - return transformSize.GetSize2d(); + Av1InverseTransformerFactory.InverseTransformAdd( + coefficientsBuffer, reconstructionBufferRead, reconstructionReadStride, reconstructionBufferWrite, reconstructionWriteStride, transformFunctionParameters); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs index 474364df8c..526ff5250e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1InverseTransformerFactory.cs @@ -7,6 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal static class Av1InverseTransformerFactory { + /// + /// SVT: svt_av1_inv_txfm_add + /// public static unsafe void InverseTransformAdd(Span coefficients, Span readBuffer, int readStride, Span writeBuffer, int writeStride, Av1TransformFunctionParameters transformFunctionParameters) { Guard.MustBeLessThanOrEqualTo(transformFunctionParameters.BitDepth, 8, nameof(transformFunctionParameters)); From bf3e50ffa0bb829c3486a44a0a858e9968f67b9a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 5 Nov 2024 20:48:40 +0100 Subject: [PATCH 172/190] Add some SVT references --- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 19 +++-- .../Heif/Av1/Pipeline/Av1FrameEncoder.cs | 74 +++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 77c69df3f7..06946271af 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -39,7 +39,11 @@ public void DecodeFrame() bool doLoopFilterFlag = false; bool doLoopRestoration = false; bool doUpscale = false; - this.DecodeLoopFilterForFrame(doLoopFilterFlag); + if (doLoopFilterFlag) + { + this.DecodeLoopFilterForFrame(); + } + if (doLoopRestoration) { // LoopRestorationSaveBoundaryLines(false); @@ -56,6 +60,9 @@ public void DecodeFrame() // PadPicture(); } + /// + /// SVT: decode_tile + /// private void DecodeFrameTiles(int tileColumn) { int tileRowCount = this.frameHeader.TilesInfo.TileRowCount; @@ -74,6 +81,9 @@ private void DecodeFrameTiles(int tileColumn) } } + /// + /// SVT: decode_tile_row + /// private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int superblockRow) { int superblockModeInfoSizeLog2 = this.sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2; @@ -122,13 +132,8 @@ private void DecodePartition(Point modeInfoPosition, Av1SuperblockInfo superbloc } } - private void DecodeLoopFilterForFrame(bool doLoopFilterFlag) + private void DecodeLoopFilterForFrame() { - if (!doLoopFilterFlag) - { - return; - } - int superblockSizeLog2 = this.sequenceHeader.SuperblockSizeLog2; int pictureWidthInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameWidth, this.sequenceHeader.SuperblockSizeLog2); int pictureHeightInSuperblocks = Av1Math.DivideLog2Ceiling(this.frameHeader.FrameSize.FrameHeight, this.sequenceHeader.SuperblockSizeLog2); diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs new file mode 100644 index 0000000000..90529ab1fd --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; + +internal class Av1FrameEncoder +{ + private readonly Av1FrameBuffer frameBuffer; + + public Av1FrameEncoder(Av1FrameBuffer frameBuffer) + { + this.frameBuffer = frameBuffer; + } + + /// + /// SVT: svt_av1_enc_init + /// + public static void Encode() + { + /************************************ + * Thread Handles + ************************************/ + + // Resource Coordination + // Single thread calling svt_aom_resource_coordination_kernel with context enc_handle_ptr->resource_coordination_context_ptr + // Multiple threads calling svt_aom_picture_analysis_kernel, with context enc_handle_ptr->picture_analysis_context_ptr_array + + // Picture Decision + // Single thread calling svt_aom_picture_decision_kernel with context enc_handle_ptr->picture_decision_context_ptr + + // Motion Estimation + // Multiple threads calling svt_aom_motion_estimation_kernel with context enc_handle_ptr->motion_estimation_context_ptr_array + + // Initial Rate Control + // Single thread calling svt_aom_initial_rate_control_kernel with context enc_handle_ptr->initial_rate_control_context_ptr + + // Source Based Oprations + // source_based_operations_context_ptr_array + + // TPL dispenser + // Multiple threads calling svt_aom_tpl_disp_kernel with context enc_handle_ptr->tpl_disp_context_ptr_array + + // Picture Manager + // Single thread calling svt_aom_picture_manager_kernel with context enc_handle_ptr->picture_manager_context_ptr + + // Rate Control + // Single thread calling svt_aom_rate_control_kernel with context enc_handle_ptr->rate_control_context_ptr + + // Mode Decision Configuration Process + // Multiple threads calling svt_aom_mode_decision_configuration_kernel with context enc_handle_ptr->mode_decision_configuration_context_ptr_array + + // EncDec Process + // Multiple threads calling svt_aom_mode_decision_kernel enc_handle_ptr->enc_dec_context_ptr_array + + // Dlf Process + // Multiple threads calling svt_aom_dlf_kernel with context enc_handle_ptr->dlf_context_ptr_array + + // Cdef Process + // Multiple threads calling svt_aom_cdef_kernel enc_handle_ptr->cdef_context_ptr_array + + // Rest Process + // Multiple threads calling svt_aom_rest_kernel enc_handle_ptr->rest_context_ptr_array + + // Entropy Coding Process + // Multiple threads calling svt_aom_entropy_coding_kernel enc_handle_ptr->entropy_coding_context_ptr_array + + // Packetization + // Single thread calling svt_aom_packetization_kernel with context enc_handle_ptr->packetization_context_ptr + + // svt_print_memory_usage(); + } +} From 62a6a2342752ae1e8b644a1f86780b3731641dc8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 5 Nov 2024 22:10:42 +0100 Subject: [PATCH 173/190] Bug fixes to partition parsing --- .../Formats/Heif/Av1/Tiling/Av1FrameInfo.cs | 2 +- .../Tiling/Av1ParseAboveNeighbor4x4Context.cs | 4 ++-- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 19 +++++++++++-------- .../Formats/Heif/Av1/Av1TilingTests.cs | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs index 41011d93fd..0808d4844a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs @@ -48,7 +48,7 @@ public Av1FrameInfo(ObuSequenceHeader sequenceHeader) // Allocate the arrays. this.superblockInfos = new Av1SuperblockInfo[superblockCount]; this.modeInfos = new Av1BlockModeInfo[superblockCount * this.modeInfoCountPerSuperblock]; - this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoCountPerSuperblock * this.superblockColumnCount, this.modeInfoCountPerSuperblock * this.superblockRowCount), superblockSizeLog2); + this.modeInfoMap = new Av1FrameModeInfoMap(new Size(this.modeInfoSizePerSuperblock * this.superblockColumnCount, this.modeInfoSizePerSuperblock * this.superblockRowCount), superblockSizeLog2); this.transformInfosY = new Av1TransformInfo[superblockCount * this.modeInfoCountPerSuperblock]; this.transformInfosUv = new Av1TransformInfo[2 * superblockCount * this.modeInfoCountPerSuperblock]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs index 9cae5c5254..0f6c4149cd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseAboveNeighbor4x4Context.cs @@ -64,9 +64,9 @@ public void Clear(ObuSequenceHeader sequenceHeader, int modeInfoColumnStart, int Array.Fill(this.aboveCompGroupIndex, 0, 0, width); } - public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileLoc, Av1BlockSize subSize, Av1BlockSize blockSize) + public void UpdatePartition(Point modeInfoLocation, Av1TileInfo tileInfo, Av1BlockSize subSize, Av1BlockSize blockSize) { - int startIndex = modeInfoLocation.X - tileLoc.ModeInfoColumnStart; + int startIndex = modeInfoLocation.X - tileInfo.ModeInfoColumnStart; int bw = blockSize.Get4x4WideCount(); int value = Av1PartitionContext.GetAboveContext(subSize); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 48f45eceeb..0ce61bc0cc 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -187,8 +187,8 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, switch (partitionType) { case Av1PartitionType.Split: - Point loc1 = new(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); - Point loc2 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); + Point loc1 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y); + Point loc2 = new(modeInfoLocation.X, modeInfoLocation.Y + halfBlock4x4Size); Point loc3 = new(modeInfoLocation.X + halfBlock4x4Size, modeInfoLocation.Y + halfBlock4x4Size); this.ParsePartition(ref reader, modeInfoLocation, subSize, superblockInfo, tileInfo); this.ParsePartition(ref reader, loc1, subSize, superblockInfo, tileInfo); @@ -248,7 +248,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, for (int i = 0; i < 4; i++) { int currentBlockRow = rowIndex + (i * quarterBlock4x4Size); - if (i > 0 && currentBlockRow > this.FrameHeader.ModeInfoRowCount) + if (i > 0 && currentBlockRow >= this.FrameHeader.ModeInfoRowCount) { break; } @@ -262,7 +262,7 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, for (int i = 0; i < 4; i++) { int currentBlockColumn = columnIndex + (i * quarterBlock4x4Size); - if (i > 0 && currentBlockColumn > this.FrameHeader.ModeInfoColumnCount) + if (i > 0 && currentBlockColumn >= this.FrameHeader.ModeInfoColumnCount) { break; } @@ -338,7 +338,7 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.ReadBlockTransformSize(ref reader, modeInfoLocation, partitionInfo, superblockInfo, tileInfo); if (partitionInfo.ModeInfo.Skip) { - this.ResetSkipContext(partitionInfo); + this.ResetSkipContext(partitionInfo, tileInfo); } this.Residual(ref reader, partitionInfo, superblockInfo, tileInfo, blockSize); @@ -347,7 +347,10 @@ private void ParseBlock(ref Av1SymbolDecoder reader, Point modeInfoLocation, Av1 this.FrameInfo.UpdateModeInfo(blockModeInfo, superblockInfo); } - private void ResetSkipContext(Av1PartitionInfo partitionInfo) + /// + /// SVT: reset_skip_context + /// + private void ResetSkipContext(Av1PartitionInfo partitionInfo, Av1TileInfo tileInfo) { int planesCount = this.SequenceHeader.ColorConfig.PlaneCount; for (int i = 0; i < planesCount; i++) @@ -358,8 +361,8 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo) DebugGuard.IsTrue(planeBlockSize != Av1BlockSize.Invalid, nameof(planeBlockSize)); int txsWide = planeBlockSize.GetWidth() >> 2; int txsHigh = planeBlockSize.GetHeight() >> 2; - int aboveOffset = (partitionInfo.ColumnIndex - this.FrameHeader.TilesInfo.TileColumnStartModeInfo[partitionInfo.ColumnIndex]) >> subX; - int leftOffset = (partitionInfo.RowIndex - this.FrameHeader.TilesInfo.TileRowStartModeInfo[partitionInfo.RowIndex]) >> subY; + int aboveOffset = (partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart) >> subX; + int leftOffset = (partitionInfo.RowIndex - partitionInfo.SuperblockInfo.Position.Y) >> subY; this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide); this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index e1ad345605..3c5212b7ac 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; public class Av1TilingTests { [Theory] - /*[InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18)]*/ - [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21)] + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18)] + // [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21)] public void ReadFirstTile(string filename, int dataOffset, int dataSize, int tileOffset) { // Assign From 61400794c09d120a35ce79e3b62f524d3fde3650 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 5 Nov 2024 22:11:12 +0100 Subject: [PATCH 174/190] Memory optimization for Frame Info Map --- .../Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs index 6867b3966d..d13b1592a4 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameModeInfoMap.cs @@ -13,7 +13,7 @@ internal partial class Av1FrameInfo /// public class Av1FrameModeInfoMap { - private readonly int[] offsets; + private readonly ushort[] offsets; private Size alignedModeInfoCount; public Av1FrameModeInfoMap(Size modeInfoCount, int superblockSizeLog2) @@ -22,7 +22,7 @@ public Av1FrameModeInfoMap(Size modeInfoCount, int superblockSizeLog2) modeInfoCount.Width * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2)), modeInfoCount.Height * (1 << (superblockSizeLog2 - Av1Constants.ModeInfoSizeLog2))); this.NextIndex = 0; - this.offsets = new int[this.alignedModeInfoCount.Width * this.alignedModeInfoCount.Height]; + this.offsets = new ushort[this.alignedModeInfoCount.Width * this.alignedModeInfoCount.Height]; } /// @@ -54,7 +54,7 @@ public void Update(Point modeInfoLocation, Av1BlockSize blockSize) /* Update 4x4 nbr offset map */ for (int i = modeInfoLocation.Y; i < modeInfoLocation.Y + bh4; i++) { - Array.Fill(this.offsets, this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4); + Array.Fill(this.offsets, (ushort)this.NextIndex, (i * this.alignedModeInfoCount.Width) + modeInfoLocation.X, bw4); } this.NextIndex++; From a33bc1895abce4647e841d1a1aff525c098e129f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 7 Nov 2024 22:21:41 +0100 Subject: [PATCH 175/190] Decode superblock immediately after parsing it --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 8 ++++++- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 4 ++-- .../Heif/Av1/Pipeline/IAv1FrameDecoder.cs | 20 ++++++++++++++++ .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 23 +++++++++++++++---- .../Formats/Heif/Av1/Av1TilingTests.cs | 6 ++++- 5 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index e6ed9ab0fd..5ad394dc06 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -48,7 +48,13 @@ public void ReadTile(Span tileData, int tileNum) { this.SequenceHeader = this.obuReader.SequenceHeader; this.FrameHeader = this.obuReader.FrameHeader; - this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader!, this.FrameHeader!); + Guard.NotNull(this.tileReader, nameof(this.tileReader)); + Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); + Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); + this.FrameInfo = new(this.SequenceHeader); + this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false); + this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer); + this.tileReader = new Av1TileReader(this.configuration, this.SequenceHeader, this.FrameHeader, this.frameDecoder); } this.tileReader.ReadTile(tileData, tileNum); diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 06946271af..8c1b68d414 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; -internal class Av1FrameDecoder +internal class Av1FrameDecoder : IAv1FrameDecoder { private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; @@ -108,7 +108,7 @@ private void DecodeTileRow(int tileRow, int tileColumn, int modeInfoRow, int sup /// /// SVT: svt_aom_decode_super_block /// - private void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) + public void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) { this.blockDecoder.UpdateSuperblock(superblockInfo); this.inverseQuantizer.UpdateDequant(this.deQuants, superblockInfo); diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs new file mode 100644 index 0000000000..1204e8bc1b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/IAv1FrameDecoder.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; + +/// +/// Interface for decoder of a single frame. +/// +internal interface IAv1FrameDecoder +{ + /// + /// Decode a single superblock. + /// + /// The top left position of the superblock, in mode info units. + /// The superblock to decode + /// The tile in whcih the superblock is positioned. + void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo); +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 0ce61bc0cc..ae1050a6c9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; @@ -35,12 +36,14 @@ internal class Av1TileReader : IAv1TileReader private readonly int[] firstTransformOffset = new int[2]; private readonly int[] coefficientIndex = []; private readonly Configuration configuration; + private readonly IAv1FrameDecoder frameDecoder; - public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader) + public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, IAv1FrameDecoder frameDecoder) { this.FrameHeader = frameHeader; this.configuration = configuration; this.SequenceHeader = sequenceHeader; + this.frameDecoder = frameDecoder; // init_main_frame_ctxt this.FrameInfo = new(this.SequenceHeader); @@ -56,7 +59,7 @@ public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHead int superblockColumnCount = Av1Math.AlignPowerOf2(sequenceHeader.MaxFrameWidth, sequenceHeader.SuperblockSizeLog2) >> sequenceHeader.SuperblockSizeLog2; int modeInfoWideColumnCount = superblockColumnCount * sequenceHeader.SuperblockModeInfoSize; - modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperblockSizeLog2 - 2); + modeInfoWideColumnCount = Av1Math.AlignPowerOf2(modeInfoWideColumnCount, sequenceHeader.SuperblockSizeLog2 - Av1Constants.ModeInfoSizeLog2); this.aboveNeighborContext = new Av1ParseAboveNeighbor4x4Context(planesCount, modeInfoWideColumnCount); this.leftNeighborContext = new Av1ParseLeftNeighbor4x4Context(planesCount, sequenceHeader.SuperblockModeInfoSize); this.transformUnitCount = new int[Av1Constants.MaxPlanes][]; @@ -72,6 +75,9 @@ public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHead public Av1FrameInfo FrameInfo { get; } + /// + /// SVT: parse_tile + /// public void ReadTile(Span tileData, int tileNum) { Av1SymbolDecoder reader = new(tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); @@ -103,14 +109,15 @@ public void ReadTile(Span tileData, int tileNum) Av1TileInfo tileInfo = new(tileRowIndex, tileColumnIndex, this.FrameHeader); Av1BlockSize superBlockSize = this.SequenceHeader.SuperblockSize; - int superBlock4x4Size = this.SequenceHeader.SuperblockSizeLog2; + int superBlock4x4Size = this.SequenceHeader.SuperblockSize.Get4x4WideCount(); + int superBlockSizeLog2 = this.SequenceHeader.SuperblockSizeLog2; for (int row = modeInfoRowStart; row < modeInfoRowEnd; row += superBlock4x4Size) { - int superBlockRow = (row << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size; + int superBlockRow = (row << Av1Constants.ModeInfoSizeLog2) >> superBlockSizeLog2; this.leftNeighborContext.Clear(this.SequenceHeader); for (int column = modeInfoColumnStart; column < modeInfoColumnEnd; column += superBlock4x4Size) { - int superBlockColumn = (column << Av1Constants.ModeInfoSizeLog2) >> superBlock4x4Size; + int superBlockColumn = (column << Av1Constants.ModeInfoSizeLog2) >> superBlockSizeLog2; Point superblockPosition = new(superBlockColumn, superBlockRow); Av1SuperblockInfo superblockInfo = this.FrameInfo.GetSuperblock(superblockPosition); @@ -120,6 +127,9 @@ public void ReadTile(Span tileData, int tileNum) this.firstTransformOffset[1] = 0; this.ReadLoopRestoration(modeInfoPosition, superBlockSize); this.ParsePartition(ref reader, modeInfoPosition, superBlockSize, superblockInfo, tileInfo); + + // decoding of the superblock + this.frameDecoder.DecodeSuperblock(modeInfoPosition, superblockInfo, tileInfo); } } } @@ -1863,6 +1873,9 @@ private static bool IsChroma(int rowIndex, int columnIndex, Av1BlockModeInfo blo return xPos && yPos; }*/ + /// + /// SVT: partition_plane_context + /// private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1TileInfo tileInfo, Av1SuperblockInfo superblockInfo) { // Maximum partition point is 8x8. Offset the log value occordingly. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 3c5212b7ac..bab5512167 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -24,7 +25,10 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til IAv1TileReader stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); obuReader.ReadAll(ref bitStreamReader, dataSize, stub); - Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader); + Av1FrameBuffer frameBuffer = new(Configuration.Default, obuReader.SequenceHeader, Av1ColorFormat.Yuv444, false); + Av1FrameInfo frameInfo = new(obuReader.SequenceHeader); + Av1FrameDecoder frameDecoder = new(obuReader.SequenceHeader, obuReader.FrameHeader, frameInfo, frameBuffer); + Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader, frameDecoder); // Act tileReader.ReadTile(tileSpan, 0); From 1f2e05f74cd821b87ab2e1138ec68b1b60f15f41 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 8 Nov 2024 16:13:27 +0100 Subject: [PATCH 176/190] Superblock info exposes its mode info position --- .../Formats/Heif/Av1/Tiling/Av1FrameInfo.cs | 8 ++++++++ .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 4 ++-- .../Heif/Av1/Tiling/Av1SuperblockInfo.cs | 5 +++++ .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 6 +++--- .../Formats/Heif/Av1/Av1FrameDecoderStub.cs | 17 +++++++++++++++++ .../Formats/Heif/Av1/Av1TilingTests.cs | 10 +++++----- 6 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameDecoderStub.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs index 0808d4844a..718497703b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FrameInfo.cs @@ -82,8 +82,16 @@ public Av1FrameInfo(ObuSequenceHeader sequenceHeader) this.deltaLoopFilter = new int[superblockCount << this.deltaLoopFactorLog2]; } + /// + /// Gets the number of mode info blocks in a single superblock. + /// public int ModeInfoCount => this.modeInfos.Length; + /// + /// Gets the Width or height of a single superblock, counted in mode info blocks. + /// + public int SuperblockModeInfoSize => this.modeInfoSizePerSuperblock; + public Av1SuperblockInfo GetSuperblock(Point index) { Span span = this.superblockInfos; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index e61c7b563e..fabeb629ed 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -66,7 +66,7 @@ public void Clear(ObuSequenceHeader sequenceHeader) public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize) { - int startIndex = (modeInfoLocation.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask; + int startIndex = (modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask; int bh = blockSize.Get4x4HighCount(); int value = Av1PartitionContext.GetLeftContext(subSize); DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - bh, nameof(startIndex)); @@ -75,7 +75,7 @@ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblock public void UpdateTransformation(Point modeInfoLocation, Av1SuperblockInfo superblockInfo, Av1TransformSize transformSize, Av1BlockSize blockSize, bool skip) { - int startIndex = modeInfoLocation.Y - superblockInfo.Position.Y; + int startIndex = modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y; int transformHeight = transformSize.GetHeight(); int n4h = blockSize.Get4x4HighCount(); if (skip) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs index 05ac285fdb..e35658d9e2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SuperblockInfo.cs @@ -18,6 +18,11 @@ public Av1SuperblockInfo(Av1FrameInfo frameInfo, Point position) /// public Point Position { get; } + /// + /// Gets the position of this superblock inside the tile, counted in mode info blocks (of 4x4 pixels). + /// + public Point ModeInfoPosition => this.Position * this.frameInfo.SuperblockModeInfoSize; + public ref int SuperblockDeltaQ => ref this.frameInfo.GetDeltaQuantizationIndex(this.Position); public Av1BlockModeInfo SuperblockModeInfo => this.GetModeInfo(new Point(0, 0)); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index ae1050a6c9..6a4dcbde81 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -372,7 +372,7 @@ private void ResetSkipContext(Av1PartitionInfo partitionInfo, Av1TileInfo tileIn int txsWide = planeBlockSize.GetWidth() >> 2; int txsHigh = planeBlockSize.GetHeight() >> 2; int aboveOffset = (partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart) >> subX; - int leftOffset = (partitionInfo.RowIndex - partitionInfo.SuperblockInfo.Position.Y) >> subY; + int leftOffset = (partitionInfo.RowIndex - partitionInfo.SuperblockInfo.ModeInfoPosition.Y) >> subY; this.aboveNeighborContext.ClearContext(i, aboveOffset, txsWide); this.leftNeighborContext.ClearContext(i, leftOffset, txsHigh); } @@ -1267,7 +1267,7 @@ private Av1TransformSize ReadSelectedTransformSize(ref Av1SymbolDecoder reader, Av1TransformSize maxTransformSize = partitionInfo.ModeInfo.BlockSize.GetMaximumTransformSize(); int aboveWidth = this.aboveNeighborContext.AboveTransformWidth[partitionInfo.ColumnIndex - tileInfo.ModeInfoColumnStart]; int above = (aboveWidth >= maxTransformSize.GetWidth()) ? 1 : 0; - int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.Position.Y]; + int leftHeight = this.leftNeighborContext.LeftTransformHeight[partitionInfo.RowIndex - superblockInfo.ModeInfoPosition.Y]; int left = (leftHeight >= maxTransformSize.GetHeight()) ? 1 : 0; bool hasAbove = partitionInfo.AvailableAbove; bool hasLeft = partitionInfo.AvailableLeft; @@ -1880,7 +1880,7 @@ private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1 { // Maximum partition point is 8x8. Offset the log value occordingly. int aboveCtx = this.aboveNeighborContext.AbovePartitionWidth[location.X - tileInfo.ModeInfoColumnStart]; - int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.Position.Y) & Av1PartitionContext.Mask]; + int leftCtx = this.leftNeighborContext.LeftPartitionHeight[(location.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask]; int blockSizeLog = blockSize.Get4x4WidthLog2() - Av1BlockSize.Block8x8.Get4x4WidthLog2(); int above = (aboveCtx >> blockSizeLog) & 0x1; int left = (leftCtx >> blockSizeLog) & 0x1; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameDecoderStub.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameDecoderStub.cs new file mode 100644 index 0000000000..c7378c14af --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameDecoderStub.cs @@ -0,0 +1,17 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +internal class Av1FrameDecoderStub : IAv1FrameDecoder +{ + private readonly List superblocks = []; + + public void DecodeSuperblock(Point modeInfoPosition, Av1SuperblockInfo superblockInfo, Av1TileInfo tileInfo) + => this.superblocks.Add(superblockInfo); + + public int SuperblockCount => this.superblocks.Count; +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index bab5512167..815c667bbe 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -3,7 +3,6 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -12,9 +11,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; public class Av1TilingTests { [Theory] - [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18)] - // [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21)] - public void ReadFirstTile(string filename, int dataOffset, int dataSize, int tileOffset) + [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 16)] + [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21, 1)] + public void ReadFirstTile(string filename, int dataOffset, int dataSize, int tileOffset, int superblockCount) { // Assign string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); @@ -27,7 +26,7 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til obuReader.ReadAll(ref bitStreamReader, dataSize, stub); Av1FrameBuffer frameBuffer = new(Configuration.Default, obuReader.SequenceHeader, Av1ColorFormat.Yuv444, false); Av1FrameInfo frameInfo = new(obuReader.SequenceHeader); - Av1FrameDecoder frameDecoder = new(obuReader.SequenceHeader, obuReader.FrameHeader, frameInfo, frameBuffer); + Av1FrameDecoderStub frameDecoder = new(); Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader, frameDecoder); // Act @@ -35,5 +34,6 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til // Assert Assert.Equal(dataSize * 8, bitStreamReader.BitPosition); + Assert.Equal(superblockCount, frameDecoder.SuperblockCount); } } From b4f3fe3a1929216d47239d0aa71a4b00359c604a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 9 Nov 2024 09:30:45 +0100 Subject: [PATCH 177/190] Creator of IAv1TileReader --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 2 +- .../Heif/Av1/OpenBitstreamUnit/ObuReader.cs | 7 ++++-- .../Tiling/Av1ParseLeftNeighbor4x4Context.cs | 2 +- .../Formats/Heif/Av1/Av1TilingTests.cs | 24 ++++++++++++++++++- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 14 +++++------ 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 5ad394dc06..fbaca19a65 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -31,7 +31,7 @@ public Av1Decoder(Configuration configuration) public void Decode(Span buffer) { Av1BitStreamReader reader = new(buffer); - this.obuReader.ReadAll(ref reader, buffer.Length, this, false); + this.obuReader.ReadAll(ref reader, buffer.Length, () => this, false); Guard.NotNull(this.tileReader, nameof(this.tileReader)); Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); diff --git a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs index b4054bfcf4..4cf648f0da 100644 --- a/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/OpenBitstreamUnit/ObuReader.cs @@ -39,6 +39,8 @@ internal class ObuReader private static readonly int[] SegmentationFeatureMax = [255, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, MaxLoopFilter, 7, 0, 0]; + private IAv1TileReader? decoder; + public ObuSequenceHeader? SequenceHeader { get; set; } public ObuFrameHeader? FrameHeader { get; set; } @@ -46,7 +48,7 @@ internal class ObuReader /// /// Decode all OBU's in a frame. /// - public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileReader decoder, bool isAnnexB = false) + public void ReadAll(ref Av1BitStreamReader reader, int dataSize, Func creator, bool isAnnexB = false) { bool seenFrameHeader = false; bool frameDecodingFinished = false; @@ -121,7 +123,8 @@ public void ReadAll(ref Av1BitStreamReader reader, int dataSize, IAv1TileReader throw new InvalidImageContentException("Corrupt frame"); } - this.ReadTileGroup(ref reader, decoder, header, out frameDecodingFinished); + this.decoder ??= creator(); + this.ReadTileGroup(ref reader, this.decoder, header, out frameDecodingFinished); if (frameDecodingFinished) { seenFrameHeader = false; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs index fabeb629ed..70f846d856 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ParseLeftNeighbor4x4Context.cs @@ -69,7 +69,7 @@ public void UpdatePartition(Point modeInfoLocation, Av1SuperblockInfo superblock int startIndex = (modeInfoLocation.Y - superblockInfo.ModeInfoPosition.Y) & Av1PartitionContext.Mask; int bh = blockSize.Get4x4HighCount(); int value = Av1PartitionContext.GetLeftContext(subSize); - DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftTransformHeight.Length - bh, nameof(startIndex)); + DebugGuard.MustBeLessThanOrEqualTo(startIndex, this.LeftPartitionHeight.Length - bh, nameof(startIndex)); Array.Fill(this.LeftPartitionHeight, value, startIndex, bh); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 815c667bbe..46c39e50d6 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -23,7 +23,7 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til Av1BitStreamReader bitStreamReader = new(headerSpan); IAv1TileReader stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); - obuReader.ReadAll(ref bitStreamReader, dataSize, stub); + obuReader.ReadAll(ref bitStreamReader, dataSize, () => stub); Av1FrameBuffer frameBuffer = new(Configuration.Default, obuReader.SequenceHeader, Av1ColorFormat.Yuv444, false); Av1FrameInfo frameInfo = new(obuReader.SequenceHeader); Av1FrameDecoderStub frameDecoder = new(); @@ -36,4 +36,26 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til Assert.Equal(dataSize * 8, bitStreamReader.BitPosition); Assert.Equal(superblockCount, frameDecoder.SuperblockCount); } + + [Theory] + // [InlineData(TestImages.Heif.XnConvert, 0x010E, 0x03CC, 18, 16)] + [InlineData(TestImages.Heif.Orange4x4, 0x010E, 0x001d, 21, 1)] + public void DecodeFirstTile(string filename, int dataOffset, int dataSize, int tileOffset, int superblockCount) + { + // Assign + string filePath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, filename); + byte[] content = File.ReadAllBytes(filePath); + Span headerSpan = content.AsSpan(dataOffset, dataSize); + Span tileSpan = content.AsSpan(tileOffset, dataSize - tileOffset); + Av1BitStreamReader bitStreamReader = new(headerSpan); + ObuReader obuReader = new(); + Av1FrameDecoderStub frameDecoder = new(); + + // Act + obuReader.ReadAll(ref bitStreamReader, dataSize, () => new Av1TileReader(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader, frameDecoder)); + + // Assert + Assert.Equal(dataSize * 8, bitStreamReader.BitPosition); + Assert.Equal(superblockCount, frameDecoder.SuperblockCount); + } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 44a9fdc97e..29d3f36d77 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -39,7 +39,7 @@ public void ReadFrameHeader(string filename, int fileOffset, int blockSize) ObuReader obuReader = new(); // Act - obuReader.ReadAll(ref reader, blockSize, decoder); + obuReader.ReadAll(ref reader, blockSize, () => decoder); // Assert Assert.NotNull(obuReader.SequenceHeader); @@ -63,7 +63,7 @@ public void BinaryIdenticalRoundTripFrameHeader(string filename, int fileOffset, ObuReader obuReader = new(); // Act 1 - obuReader.ReadAll(ref reader, blockSize, tileStub); + obuReader.ReadAll(ref reader, blockSize, () => tileStub); // Assign 2 MemoryStream encoded = new(); @@ -91,7 +91,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b ObuReader obuReader1 = new(); // Act 1 - obuReader1.ReadAll(ref reader, blockSize, tileStub); + obuReader1.ReadAll(ref reader, blockSize, () => tileStub); // Assign 2 MemoryStream encoded = new(); @@ -107,7 +107,7 @@ public void ThreeTimeRoundTripFrameHeader(string filename, int fileOffset, int b ObuReader obuReader2 = new(); // Act 2 - obuReader2.ReadAll(ref reader2, encodedBuffer.Length, tileDecoder2); + obuReader2.ReadAll(ref reader2, encodedBuffer.Length, () => tileDecoder2); // Assert Assert.Equal(ObuPrettyPrint.PrettyPrintProperties(obuReader1.SequenceHeader.ColorConfig), ObuPrettyPrint.PrettyPrintProperties(obuReader2.SequenceHeader.ColorConfig)); @@ -125,7 +125,7 @@ public void ReadTemporalDelimiter() IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act - obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, DefaultTemporalDelimiterBitStream.Length, () => tileDecoder); // Assert Assert.Null(obuReader.SequenceHeader); @@ -142,7 +142,7 @@ public void ReadHeaderWithoutSizeField() IAv1TileReader tileDecoder = new Av1TileDecoderStub(); // Act - obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder); // Assert Assert.Null(obuReader.SequenceHeader); @@ -160,7 +160,7 @@ public void ReadSequenceHeader() ObuSequenceHeader expected = GetDefaultSequenceHeader(); // Act - obuReader.ReadAll(ref reader, bitStream.Length, tileDecoder); + obuReader.ReadAll(ref reader, bitStream.Length, () => tileDecoder); // Assert Assert.NotNull(obuReader.SequenceHeader); From 3abc9a0278aaa55bac18512f8c8efb358765c60a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 15 Nov 2024 15:02:52 +0100 Subject: [PATCH 178/190] Encode partition symbol --- .../Formats/Heif/Av1/Av1Constants.cs | 2 + src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 5 ++ .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 76 ++++++++++-------- .../Heif/Av1/Tiling/Av1SymbolEncoder.cs | 44 +++++++++-- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 9 +-- .../Formats/Heif/Av1/SymbolTests.cs | 77 ++++++++++++++++++- 6 files changed, 169 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index fc4915e537..6539012e5b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -180,4 +180,6 @@ internal static class Av1Constants /// Maximum number of stages in a 1-dimensioanl transform function. /// public const int MaxTransformStageNumber = 12; + + public const int PartitionProbabilitySet = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 59da84eeea..62d6d63c08 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -166,4 +166,9 @@ internal static int RoundShift(long value, int bit) DebugGuard.MustBeGreaterThanOrEqualTo(bit, 1, nameof(bit)); return (int)((value + (1L << (bit - 1))) >> bit); } + + /// + /// implies . + /// + internal static bool Implies(bool a, bool b) => !a || b; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 62a3894d45..d33b5f6599 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -64,42 +64,24 @@ public Av1PartitionType ReadPartitionType(int context) return (Av1PartitionType)r.ReadSymbol(this.tilePartitionTypes[context]); } - public bool ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) + /// + /// SVT: partition_gather_vert_alike + /// + public Av1PartitionType ReadSplitOrHorizontal(Av1BlockSize blockSize, int context) { - Av1Distribution input = this.tilePartitionTypes[context]; - uint p = Av1Distribution.ProbabilityTop; - p -= GetElementProbability(input, Av1PartitionType.Horizontal); - p -= GetElementProbability(input, Av1PartitionType.Split); - p -= GetElementProbability(input, Av1PartitionType.HorizontalA); - p -= GetElementProbability(input, Av1PartitionType.HorizontalB); - p -= GetElementProbability(input, Av1PartitionType.VerticalA); - if (blockSize != Av1BlockSize.Block128x128) - { - p -= GetElementProbability(input, Av1PartitionType.Horizontal4); - } - - Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + Av1Distribution distribution = GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(distribution) > 0; + return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Horizontal; } - public bool ReadSplitOrVertical(Av1BlockSize blockSize, int context) + /// + /// SVT: partition_gather_horz_alike + /// + public Av1PartitionType ReadSplitOrVertical(Av1BlockSize blockSize, int context) { - Av1Distribution input = this.tilePartitionTypes[context]; - uint p = Av1Distribution.ProbabilityTop; - p -= GetElementProbability(input, Av1PartitionType.Vertical); - p -= GetElementProbability(input, Av1PartitionType.Split); - p -= GetElementProbability(input, Av1PartitionType.HorizontalA); - p -= GetElementProbability(input, Av1PartitionType.VerticalA); - p -= GetElementProbability(input, Av1PartitionType.VerticalB); - if (blockSize != Av1BlockSize.Block128x128) - { - p -= GetElementProbability(input, Av1PartitionType.Vertical4); - } - - Av1Distribution distribution = new(Av1Distribution.ProbabilityTop - p); + Av1Distribution distribution = GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(distribution) > 0; + return r.ReadSymbol(distribution) > 0 ? Av1PartitionType.Split : Av1PartitionType.Vertical; } public Av1PredictionMode ReadYMode(Av1BlockModeInfo? aboveModeInfo, Av1BlockModeInfo? leftModeInfo) @@ -259,6 +241,40 @@ public int ReadChromaFromLumaAlphaV(int jointSignPlus1) return r.ReadSymbol(this.chromeForLumaAlpha[context]); } + internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) + { + Av1Distribution input = inputs[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Horizontal); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.HorizontalB); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Horizontal4); + } + + return new(Av1Distribution.ProbabilityTop - p); + } + + internal static Av1Distribution GetSplitOrVerticalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) + { + Av1Distribution input = inputs[context]; + uint p = Av1Distribution.ProbabilityTop; + p -= GetElementProbability(input, Av1PartitionType.Vertical); + p -= GetElementProbability(input, Av1PartitionType.Split); + p -= GetElementProbability(input, Av1PartitionType.HorizontalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalA); + p -= GetElementProbability(input, Av1PartitionType.VerticalB); + if (blockSize != Av1BlockSize.Block128x128) + { + p -= GetElementProbability(input, Av1PartitionType.Vertical4); + } + + return new(Av1Distribution.ProbabilityTop - p); + } + private static uint GetElementProbability(Av1Distribution probability, Av1PartitionType element) => probability[(int)element - 1] - probability[(int)element]; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs index 3da3237c26..78bc2eec49 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs @@ -10,22 +10,52 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - private Av1SymbolWriter? writer; + private bool isDisposed; + private Av1SymbolWriter writer; public Av1SymbolEncoder(Configuration configuration, int initialSize) => this.writer = new(configuration, initialSize); public void WriteUseIntraBlockCopy(bool value) - => this.writer!.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value ? 1 : 0, this.tileIntraBlockCopy); + } + + public void WritePartitionType(Av1PartitionType partitionType, int context) + { + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol((int)partitionType, this.tilePartitionTypes[context]); + } + + public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) + { + Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); + int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value, distribution); + } - public void WritePartitionType(Av1PartitionType value, int context) - => this.writer!.WriteSymbol((int)value, this.tilePartitionTypes[context]); + public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) + { + Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); + int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(value, distribution); + } - public IMemoryOwner Exit() => this.writer!.Exit(); + public IMemoryOwner Exit() + { + ref Av1SymbolWriter w = ref this.writer; + return w.Exit(); + } public void Dispose() { - this.writer?.Dispose(); - this.writer = null; + if (!this.isDisposed) + { + this.writer.Dispose(); + this.isDisposed = true; + } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 6a4dcbde81..d4355fc016 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -14,7 +14,6 @@ internal class Av1TileReader : IAv1TileReader { private static readonly int[] SgrprojXqdMid = [-32, 31]; private static readonly int[] WienerTapsMid = [3, -7, 15]; - private const int PartitionProbabilitySet = 4; private static readonly int[] Signs = [0, -1, 1]; private static readonly int[] DcSignContexts = [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, @@ -182,13 +181,11 @@ private void ParsePartition(ref Av1SymbolDecoder reader, Point modeInfoLocation, } else if (hasColumns) { - bool splitOrVertical = reader.ReadSplitOrVertical(blockSize, ctx); - partitionType = splitOrVertical ? Av1PartitionType.Split : Av1PartitionType.Horizontal; + partitionType = reader.ReadSplitOrHorizontal(blockSize, ctx); } else if (hasRows) { - bool splitOrHorizontal = reader.ReadSplitOrHorizontal(blockSize, ctx); - partitionType = splitOrHorizontal ? Av1PartitionType.Split : Av1PartitionType.Vertical; + partitionType = reader.ReadSplitOrVertical(blockSize, ctx); } } @@ -1886,7 +1883,7 @@ private int GetPartitionPlaneContext(Point location, Av1BlockSize blockSize, Av1 int left = (leftCtx >> blockSizeLog) & 0x1; DebugGuard.IsTrue(blockSize.Get4x4WidthLog2() == blockSize.Get4x4HeightLog2(), "Blocks should be square"); DebugGuard.MustBeGreaterThanOrEqualTo(blockSizeLog, 0, nameof(blockSizeLog)); - return ((left << 1) + above) + (blockSizeLog * PartitionProbabilitySet); + return ((left << 1) + above) + (blockSizeLog * Av1Constants.PartitionProbabilitySet); } private void UpdatePartitionContext(Point modeInfoLocation, Av1TileInfo tileLoc, Av1SuperblockInfo superblockInfo, Av1BlockSize subSize, Av1BlockSize blockSize, Av1PartitionType partition) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs index 756319adaf..81f2d84776 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs @@ -2,9 +2,11 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Reflection; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Memory; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -217,6 +219,80 @@ public void RoundTripPartitionType() Assert.Equal(values, actuals); } + [Theory] + [InlineData((int)Av1BlockSize.Block4x4, 7)] + [InlineData((int)Av1BlockSize.Block4x4, 5)] + [InlineData((int)Av1BlockSize.Block8x4, 7)] + [InlineData((int)Av1BlockSize.Block4x8, 7)] + [InlineData((int)Av1BlockSize.Block32x64, 7)] + [InlineData((int)Av1BlockSize.Block64x32, 7)] + [InlineData((int)Av1BlockSize.Block64x64, 7)] + public void RoundTripSplitOrHorizontalPartitionType(int blockSize, int context) + { + // Assign + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1PartitionType[] values = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Horizontal]; + Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; + + // Act + foreach (Av1PartitionType value in values) + { + encoder.WriteSplitOrHorizontal(value, (Av1BlockSize)blockSize, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSplitOrHorizontal((Av1BlockSize)blockSize, context); + } + + // Assert + Assert.Equal(values, actuals); + } + + [Theory] + [InlineData((int)Av1BlockSize.Block4x4, 7)] + [InlineData((int)Av1BlockSize.Block4x4, 5)] + [InlineData((int)Av1BlockSize.Block8x4, 7)] + [InlineData((int)Av1BlockSize.Block4x8, 7)] + [InlineData((int)Av1BlockSize.Block32x64, 7)] + [InlineData((int)Av1BlockSize.Block64x32, 7)] + [InlineData((int)Av1BlockSize.Block64x64, 7)] + public void RoundTripSplitOrVerticalPartitionType(int blockSize, int context) + { + // Assign + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1PartitionType[] values = [ + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, + Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Vertical]; + Av1PartitionType[] actuals = new Av1PartitionType[values.Length]; + + // Act + foreach (Av1PartitionType value in values) + { + encoder.WriteSplitOrVertical(value, (Av1BlockSize)blockSize, context); + } + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + for (int i = 0; i < values.Length; i++) + { + actuals[i] = decoder.ReadSplitOrVertical((Av1BlockSize)blockSize, context); + } + + // Assert + Assert.Equal(values, actuals); + } + [Fact] public void RoundTripUseIntraBlockCopy() { @@ -235,7 +311,6 @@ public void RoundTripUseIntraBlockCopy() using IMemoryOwner encoded = encoder.Exit(); Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); - Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopy(); From 0dacc04985aae28c4fb409359e85f4b746528827 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 15 Nov 2024 20:01:08 +0100 Subject: [PATCH 179/190] Move coefficients reading into Av1SymbolEncoder class --- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 328 +++++++++++++++++- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 298 +--------------- 2 files changed, 318 insertions(+), 308 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index d33b5f6599..a80b62e3d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -197,48 +197,346 @@ public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneT return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; } - public int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) + public int ReadChromFromLumaSign() + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.chromeForLumaSign); + } + + public int ReadChromaFromLumaAlphaU(int jointSignPlus1) + { + ref Av1SymbolReader r = ref this.reader; + int context = jointSignPlus1 - 3; + return r.ReadSymbol(this.chromeForLumaAlpha[context]); + } + + public int ReadChromaFromLumaAlphaV(int jointSignPlus1) + { + ref Av1SymbolReader r = ref this.reader; + int context = AlphaVContexts[jointSignPlus1]; + return r.ReadSymbol(this.chromeForLumaAlpha[context]); + } + + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + int i = endOfBlock - 1; + int pos = scan[i]; + int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i); + int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) + { + int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); + level += coefficinetBaseRange; + if (coefficinetBaseRange < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + } + + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + for (int c = endSi; c >= startSi; --c) + { + int pos = scan[c]; + int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); + int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) + { + int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); + level += k; + if (k < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + } + } + + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + for (int c = endSi; c >= startSi; --c) + { + int pos = scan[c]; + int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); + int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); + if (level > Av1Constants.BaseLevelsCount) + { + int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) + { + int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); + level += k; + if (k < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + + levels[GetPaddedIndex(pos, bwl)] = level; + } + } + + public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, Span scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) + { + int maxScanLine = 0; + int culLevel = 0; + int dcValue = 0; + coefficientBuffer[0] = endOfBlock; + for (int c = 0; c < endOfBlock; c++) + { + int sign = 0; + int level = levels[GetPaddedIndex(scan[c], bwl)]; + if (level != 0) + { + maxScanLine = Math.Max(maxScanLine, scan[c]); + if (c == 0) + { + sign = this.ReadDcSign(planeType, dcSignContext); + } + else + { + sign = this.ReadLiteral(1); + } + + if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1) + { + level += this.ReadGolomb(); + } + + if (c == 0) + { + dcValue = sign != 0 ? -level : level; + } + + level &= 0xfffff; + culLevel += level; + } + + coefficientBuffer[c + 1] = sign != 0 ? -level : level; + } + + culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); + SetDcSign(ref culLevel, dcValue); + + return culLevel; + } + + private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.coefficientsBaseRange[(int)transformSizeContext][(int)planeType][baseRangeContext]); } - public int ReadDcSign(Av1PlaneType planeType, int dcSignContext) + private int ReadDcSign(Av1PlaneType planeType, int dcSignContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.dcSign[(int)planeType][dcSignContext]); } - public int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) + private int ReadBaseEndOfBlock(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int coefficientContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.baseEndOfBlock[(int)transformSizeContext][(int)planeType][coefficientContext]); } - public int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + private int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } - public int ReadChromFromLumaSign() + private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) { - ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.chromeForLumaSign); + int row = pos >> bwl; + int col = pos - (row << bwl); + if (pos == 0) + { + return 0; + } + + if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || + (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || + (transformClass == Av1TransformClass.ClassVertical && row == 0)) + { + return 7; + } + + return 14; } - public int ReadChromaFromLumaAlphaU(int jointSignPlus1) + private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex) { - ref Av1SymbolReader r = ref this.reader; - int context = jointSignPlus1 - 3; - return r.ReadSymbol(this.chromeForLumaAlpha[context]); + if (scanIndex == 0) + { + return 0; + } + + if (scanIndex <= (height << bwl) >> 3) + { + return 1; + } + + if (scanIndex <= (height << bwl) >> 2) + { + return 2; + } + + return 3; } - public int ReadChromaFromLumaAlphaV(int jointSignPlus1) + private static int GetBaseRangeContext2d(Span levels, int c, int bwl) { - ref Av1SymbolReader r = ref this.reader; - int context = AlphaVContexts[jointSignPlus1]; - return r.ReadSymbol(this.chromeForLumaAlpha[context]); + DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); + int row = c >> bwl; + int col = c - (row << bwl); + int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; + int pos = (row * stride) + col; + int mag = + Math.Min(levels[pos + 1], Av1Constants.MaxBaseRange) + + Math.Min(levels[pos + stride], Av1Constants.MaxBaseRange) + + Math.Min(levels[pos + 1 + stride], Av1Constants.MaxBaseRange); + mag = Math.Min((mag + 1) >> 1, 6); + if ((row | col) < 2) + { + return mag + 7; + } + + return mag + 14; + } + + private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) + { + DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); + int mag; + levels = levels[GetPaddedIndex(pos, bwl)..]; + mag = Math.Min(levels[1], 3); // { 0, 1 } + mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } + mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } + mag += Math.Min(levels[2], 3); // { 0, 2 } + mag += Math.Min(levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } + + int ctx = Math.Min((mag + 1) >> 1, 4); + return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); + } + + private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) + { + int row = c >> bwl; + int col = c - (row << bwl); + int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; + int pos = (row * stride) + col; + int mag = levels[pos + 1]; + mag += levels[pos + stride]; + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += levels[pos + stride + 1]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if ((row < 2) && (col < 2)) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassHorizontal: + mag += levels[pos + 2]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (col == 0) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassVertical: + mag += levels[pos + (stride << 1)]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (row == 0) + { + return mag + 7; + } + + break; + default: + break; + } + + return mag + 14; + } + + private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + { + int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); + return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + } + + private static int GetPaddedIndex(int scanIndex, int bwl) + => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2); + + private int ReadGolomb() + { + int x = 1; + int length = 0; + int i = 0; + + while (i == 0) + { + i = this.ReadLiteral(1); + ++length; + if (length > 20) + { + // SVT_LOG("Invalid length in read_golomb"); + break; + } + } + + for (i = 0; i < length - 1; ++i) + { + x <<= 1; + x += this.ReadLiteral(1); + } + + return x - 1; + } + + private static void SetDcSign(ref int culLevel, int dcValue) + { + if (dcValue < 0) + { + culLevel |= 1 << Av1Constants.CoefficientContextBitCount; + } + else if (dcValue > 0) + { + culLevel += 2 << Av1Constants.CoefficientContextBitCount; + } } internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index d4355fc016..934355e708 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -566,7 +566,6 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; - int dcValue = 0; int[] levelsBuffer = new int[Av1Constants.TransformPad2d]; Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; @@ -574,7 +573,6 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); int bwl = transformSize.GetBlockWidthLog2(); int endOfBlock; - int maxScanLine = 0; if (allZero) { if (plane == 0) @@ -623,314 +621,28 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); } - int i = endOfBlock - 1; - int pos = scan[i]; - int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i); - int level = reader.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); - if (level > Av1Constants.BaseLevelsCount) - { - int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); - for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) - { - int coefficinetBaseRange = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); - level += coefficinetBaseRange; - if (coefficinetBaseRange < Av1Constants.BaseRangeSizeMinus1) - { - break; - } - } - } - - levels[GetPaddedIndex(pos, bwl)] = level; - + reader.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); if (endOfBlock > 1) { if (transformClass == Av1TransformClass.Class2D) { - ReadCoefficientsReverse2d(ref reader, transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, 0, scan, bwl, levels, transformSizeContext, planeType); + reader.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + reader.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); } else { - ReadCoefficientsReverse(ref reader, transformSize, transformInfo.Type, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + reader.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); } } DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); - coefficientBuffer[0] = endOfBlock; - for (int c = 0; c < endOfBlock; c++) - { - int sign = 0; - level = levels[GetPaddedIndex(scan[c], bwl)]; - if (level != 0) - { - maxScanLine = Math.Max(maxScanLine, scan[c]); - if (c == 0) - { - sign = reader.ReadDcSign(planeType, transformBlockContext.DcSignContext); - } - else - { - sign = reader.ReadLiteral(1); - } - - if (level >= Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount + 1) - { - level += ReadGolomb(ref reader); - } - - if (c == 0) - { - dcValue = sign != 0 ? -level : level; - } - - level &= 0xfffff; - culLevel += level; - } - - coefficientBuffer[c + 1] = sign != 0 ? -level : level; - } - - culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); - SetDcSign(ref culLevel, dcValue); - + culLevel = reader.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); transformInfo.CodeBlockFlag = true; return endOfBlock; } - private static void ReadCoefficientsReverse2d(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) - { - for (int c = endSi; c >= startSi; --c) - { - int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); - int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); - if (level > Av1Constants.BaseLevelsCount) - { - int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); - for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) - { - int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); - level += k; - if (k < Av1Constants.BaseRangeSizeMinus1) - { - break; - } - } - } - - levels[GetPaddedIndex(pos, bwl)] = level; - } - } - - private static int GetBaseRangeContext2d(Span levels, int c, int bwl) - { - DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = (row * stride) + col; - int mag = - Math.Min(levels[pos + 1], Av1Constants.MaxBaseRange) + - Math.Min(levels[pos + stride], Av1Constants.MaxBaseRange) + - Math.Min(levels[pos + 1 + stride], Av1Constants.MaxBaseRange); - mag = Math.Min((mag + 1) >> 1, 6); - if ((row | col) < 2) - { - return mag + 7; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) - { - DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); - int mag; - levels = levels[GetPaddedIndex(pos, bwl)..]; - mag = Math.Min(levels[1], 3); // { 0, 1 } - mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } - mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } - mag += Math.Min(levels[2], 3); // { 0, 2 } - mag += Math.Min(levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } - - int ctx = Math.Min((mag + 1) >> 1, 4); - return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); - } - - private static void ReadCoefficientsReverse(ref Av1SymbolDecoder reader, Av1TransformSize transformSize, Av1TransformType transformType, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) - { - Av1TransformClass transformClass = transformType.ToClass(); - for (int c = endSi; c >= startSi; --c) - { - int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); - int level = reader.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); - if (level > Av1Constants.BaseLevelsCount) - { - int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); - for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) - { - int k = reader.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); - level += k; - if (k < Av1Constants.BaseRangeSizeMinus1) - { - break; - } - } - } - - levels[GetPaddedIndex(pos, bwl)] = level; - } - } - - private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) - { - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = (row * stride) + col; - int mag = levels[pos + 1]; - mag += levels[pos + stride]; - switch (transformClass) - { - case Av1TransformClass.Class2D: - mag += levels[pos + stride + 1]; - mag = Math.Min((mag + 1) >> 1, 6); - if (c == 0) - { - return mag; - } - - if ((row < 2) && (col < 2)) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassHorizontal: - mag += levels[pos + 2]; - mag = Math.Min((mag + 1) >> 1, 6); - if (c == 0) - { - return mag; - } - - if (col == 0) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassVertical: - mag += levels[pos + (stride << 1)]; - mag = Math.Min((mag + 1) >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row == 0) - { - return mag + 7; - } - - break; - default: - break; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) - { - int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); - return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); - } - - private static int ReadGolomb(ref Av1SymbolDecoder reader) - { - int x = 1; - int length = 0; - int i = 0; - - while (i == 0) - { - i = reader.ReadLiteral(1); - ++length; - if (length > 20) - { - // SVT_LOG("Invalid length in read_golomb"); - break; - } - } - - for (i = 0; i < length - 1; ++i) - { - x <<= 1; - x += reader.ReadLiteral(1); - } - - return x - 1; - } - - private static void SetDcSign(ref int culLevel, int dcValue) - { - if (dcValue < 0) - { - culLevel |= 1 << Av1Constants.CoefficientContextBitCount; - } - else if (dcValue > 0) - { - culLevel += 2 << Av1Constants.CoefficientContextBitCount; - } - } - - private static int GetPaddedIndex(int scanIndex, int bwl) - => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2); - - private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) - { - int row = pos >> bwl; - int col = pos - (row << bwl); - if (pos == 0) - { - return 0; - } - - if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || - (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || - (transformClass == Av1TransformClass.ClassVertical && row == 0)) - { - return 7; - } - - return 14; - } - - private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex) - { - if (scanIndex == 0) - { - return 0; - } - - if (scanIndex <= (height << bwl) >> 3) - { - return 1; - } - - if (scanIndex <= (height << bwl) >> 2) - { - return 2; - } - - return 3; - } - private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int culLevel) { bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; From 3ea42d050ad3b3ee45db98e3cc494adca0e69e5f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 15 Nov 2024 20:11:45 +0100 Subject: [PATCH 180/190] Make scan order read only --- .../Quantification/Av1InverseQuantizer.cs | 2 +- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 8 +++---- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Heif/Av1/Transform/Av1ScanOrder.cs | 22 +++++++++++-------- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs index 4cb7c1cc77..0f08b216b0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantification/Av1InverseQuantizer.cs @@ -85,7 +85,7 @@ public int InverseQuantize(Av1BlockModeInfo mode, Span level, Span qCo { Guard.NotNull(this.deQuantsDeltaQ); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); - short[] scanIndices = scanOrder.Scan; + ReadOnlySpan scanIndices = scanOrder.Scan; int maxValue = (1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())) - 1; int minValue = -(1 << (7 + this.sequenceHeader.ColorConfig.BitDepth.GetBitCount())); Av1TransformSize qmTransformSize = transformSize.GetAdjusted(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index a80b62e3d7..57de6659c5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -217,7 +217,7 @@ public int ReadChromaFromLumaAlphaV(int jointSignPlus1) return r.ReadSymbol(this.chromeForLumaAlpha[context]); } - public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; int pos = scan[i]; @@ -240,7 +240,7 @@ public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int end levels[GetPaddedIndex(pos, bwl)] = level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startSi, int endSi, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endSi; c >= startSi; --c) { @@ -265,7 +265,7 @@ public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startS } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startSi, int endSi, Span scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startSi, int endSi, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endSi; c >= startSi; --c) { @@ -290,7 +290,7 @@ public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1Transform } } - public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, Span scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 934355e708..e4b5b6a9aa 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -591,7 +591,7 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo); Av1TransformClass transformClass = transformInfo.Type.ToClass(); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); - Span scan = scanOrder.Scan; + ReadOnlySpan scan = scanOrder.Scan; endOfBlockPoint = reader.ReadEndOfBlockFlag(planeType, transformClass, transformSize); int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs index 94650b1766..b2c2587407 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1ScanOrder.cs @@ -5,23 +5,27 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Transform; internal readonly struct Av1ScanOrder { + private readonly short[] scan; + private readonly short[] inverseScan; + private readonly short[] neighbors; + public Av1ScanOrder(short[] scan) { - this.Scan = scan; - this.InverseScan = []; - this.Neighbors = []; + this.scan = scan; + this.inverseScan = []; + this.neighbors = []; } public Av1ScanOrder(short[] scan, short[] inverseScan, short[] neighbors) { - this.Scan = scan; - this.InverseScan = inverseScan; - this.Neighbors = neighbors; + this.scan = scan; + this.inverseScan = inverseScan; + this.neighbors = neighbors; } - public short[] Scan { get; } + public ReadOnlySpan Scan => this.scan; - public short[] InverseScan { get; } + public ReadOnlySpan InverseScan => this.inverseScan; - public short[] Neighbors { get; } + public ReadOnlySpan Neighbors => this.neighbors; } From e58a775cc014ffe448a59d5b21b79fcc8809ec9c Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 15 Nov 2024 20:33:56 +0100 Subject: [PATCH 181/190] Move end-of-block position reading to Av1SymbolDecoder --- .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 77 ++++++++++++++----- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 41 +--------- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index 57de6659c5..a327d7094a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -10,6 +10,8 @@ internal ref struct Av1SymbolDecoder { private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; + private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; @@ -177,26 +179,12 @@ public Av1TransformSize ReadTransformSize(Av1BlockSize blockSize, int context) return transformSize; } - public int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize) - { - int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; - int endOfBlockMultiSize = transformSize.GetLog2Minus4(); - ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1; - } - public bool ReadTransformBlockSkip(Av1TransformSize transformSizeContext, int skipContext) { ref Av1SymbolReader r = ref this.reader; return r.ReadSymbol(this.transformBlockSkip[(int)transformSizeContext][skipContext]) > 0; } - public bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) - { - ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; - } - public int ReadChromFromLumaSign() { ref Av1SymbolReader r = ref this.reader; @@ -217,6 +205,34 @@ public int ReadChromaFromLumaAlphaV(int jointSignPlus1) return r.ReadSymbol(this.chromeForLumaAlpha[context]); } + public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + { + int endOfBlockExtra = 0; + int endOfBlockPoint = this.ReadEndOfBlockFlag(planeType, transformClass, transformSize); + int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; + if (endOfBlockShift > 0) + { + int endOfBlockContext = endOfBlockPoint; + bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); + if (bit) + { + endOfBlockExtra += 1 << (endOfBlockShift - 1); + } + else + { + for (int j = 1; j < endOfBlockShift; j++) + { + if (this.ReadLiteral(1) != 0) + { + endOfBlockExtra += 1 << (endOfBlockShift - 1 - j); + } + } + } + } + + return RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); + } + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; @@ -240,9 +256,9 @@ public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int end levels[GetPaddedIndex(pos, bwl)] = level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startSi, int endSi, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { - for (int c = endSi; c >= startSi; --c) + for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); @@ -265,9 +281,9 @@ public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startS } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startSi, int endSi, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { - for (int c = endSi; c >= startSi; --c) + for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); @@ -335,6 +351,20 @@ public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadO return culLevel; } + private int ReadEndOfBlockFlag(Av1PlaneType planeType, Av1TransformClass transformClass, Av1TransformSize transformSize) + { + int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; + int endOfBlockMultiSize = transformSize.GetLog2Minus4(); + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.endOfBlockFlag[endOfBlockMultiSize][(int)planeType][endOfBlockContext]) + 1; + } + + private bool ReadEndOfBlockExtra(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int endOfBlockContext) + { + ref Av1SymbolReader r = ref this.reader; + return r.ReadSymbol(this.endOfBlockExtra[(int)transformSizeContext][(int)planeType][endOfBlockContext]) > 0; + } + private int ReadCoefficientsBaseRange(Av1TransformSize transformSizeContext, Av1PlaneType planeType, int baseRangeContext) { ref Av1SymbolReader r = ref this.reader; @@ -359,6 +389,17 @@ private int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transf return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } + private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) + { + int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; + if (endOfBlock > 2) + { + endOfBlock += endOfBlockExtra; + } + + return endOfBlock; + } + private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) { int row = pos >> bwl; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index e4b5b6a9aa..2d6372ad6c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -22,9 +22,6 @@ internal class Av1TileReader : IAv1TileReader private static readonly int[][] SkipContexts = [ [1, 2, 2, 2, 3], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 5], [1, 4, 4, 4, 6]]; - private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; - private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - private int[][] referenceSgrXqd = []; private int[][][] referenceLrWiener = []; private readonly Av1ParseAboveNeighbor4x4Context aboveNeighborContext; @@ -585,37 +582,12 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part return 0; } - int endOfBlockExtra = 0; - int endOfBlockPoint = 0; - transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo); Av1TransformClass transformClass = transformInfo.Type.ToClass(); Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); ReadOnlySpan scan = scanOrder.Scan; - endOfBlockPoint = reader.ReadEndOfBlockFlag(planeType, transformClass, transformSize); - int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; - if (endOfBlockShift > 0) - { - int endOfBlockContext = endOfBlockPoint; - bool bit = reader.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); - if (bit) - { - endOfBlockExtra += 1 << (endOfBlockShift - 1); - } - else - { - for (int j = 1; j < endOfBlockShift; j++) - { - if (reader.ReadLiteral(1) != 0) - { - endOfBlockExtra += 1 << (endOfBlockShift - 1 - j); - } - } - } - } - - endOfBlock = RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); + endOfBlock = reader.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); if (endOfBlock > 1) { Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); @@ -679,17 +651,6 @@ private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, } } - private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) - { - int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; - if (endOfBlock > 2) - { - endOfBlock += endOfBlockExtra; - } - - return endOfBlock; - } - private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo) { Av1TransformType transformType = Av1TransformType.DctDct; From 51d54c68d7bcddc0b4dfad11be20fae79e039893 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 16 Nov 2024 11:00:10 +0100 Subject: [PATCH 182/190] Bounds checks for Predictors --- .../Heif/Av1/Prediction/Av1DcFillPredictor.cs | 13 ++-- .../Heif/Av1/Prediction/Av1DcLeftPredictor.cs | 17 +++-- .../Heif/Av1/Prediction/Av1DcPredictor.cs | 21 ++++-- .../Heif/Av1/Prediction/Av1DcTopPredictor.cs | 17 +++-- .../Av1/Prediction/Av1PredictionDecoder.cs | 73 +++++++++---------- .../Av1/Prediction/Av1PredictorFactory.cs | 16 ++-- .../Heif/Av1/Prediction/IAv1Predictor.cs | 2 +- .../Formats/Heif/Av1/Tiling/Av1TileInfo.cs | 2 - ...PredictorTests.cs => Av1PredictorTests.cs} | 22 +++--- 9 files changed, 99 insertions(+), 84 deletions(-) rename tests/ImageSharp.Tests/Formats/Heif/Av1/{PredictorTests.cs => Av1PredictorTests.cs} (81%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs index 719d574b87..d4340c9c5b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcFillPredictor.cs @@ -23,16 +23,19 @@ public Av1DcFillPredictor(Av1TransformSize transformSize) this.blockHeight = (uint)transformSize.GetHeight(); } - public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) - => new Av1DcFillPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left) + => new Av1DcFillPredictor(transformSize).PredictScalar(destination, stride, above, left); - public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + public void PredictScalar(Span destination, nuint stride, Span above, Span left) { const byte expectedDc = 0x80; + Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); + Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); + ref byte destinationRef = ref destination[0]; for (uint r = 0; r < this.blockHeight; r++) { - Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); - destination = ref Unsafe.Add(ref destination, stride); + Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); + destinationRef = ref Unsafe.Add(ref destinationRef, stride); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs index 1983396f17..9237fe751e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcLeftPredictor.cs @@ -23,22 +23,27 @@ public Av1DcLeftPredictor(Av1TransformSize transformSize) this.blockHeight = (uint)transformSize.GetHeight(); } - public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) - => new Av1DcLeftPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left) + => new Av1DcLeftPredictor(transformSize).PredictScalar(destination, stride, above, left); - public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + public void PredictScalar(Span destination, nuint stride, Span above, Span left) { int sum = 0; + Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); + Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); + Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); + ref byte leftRef = ref left[0]; + ref byte destinationRef = ref destination[0]; for (uint i = 0; i < this.blockHeight; i++) { - sum += Unsafe.Add(ref left, i); + sum += Unsafe.Add(ref leftRef, i); } byte expectedDc = (byte)((sum + (this.blockHeight >> 1)) / this.blockHeight); for (uint r = 0; r < this.blockHeight; r++) { - Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); - destination = ref Unsafe.Add(ref destination, stride); + Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); + destinationRef = ref Unsafe.Add(ref destinationRef, stride); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs index 51eef4b783..f91735fc7d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcPredictor.cs @@ -23,28 +23,35 @@ public Av1DcPredictor(Av1TransformSize transformSize) this.blockHeight = (uint)transformSize.GetHeight(); } - public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) - => new Av1DcPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left) + => new Av1DcPredictor(transformSize).PredictScalar(destination, stride, above, left); - public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + public void PredictScalar(Span destination, nuint stride, Span above, Span left) { int sum = 0; + Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); + Guard.MustBeSizedAtLeast(left, (int)this.blockHeight, nameof(left)); + Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); + Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); + ref byte leftRef = ref left[0]; + ref byte aboveRef = ref above[0]; + ref byte destinationRef = ref destination[0]; uint count = this.blockWidth + this.blockHeight; for (uint i = 0; i < this.blockWidth; i++) { - sum += Unsafe.Add(ref above, i); + sum += Unsafe.Add(ref aboveRef, i); } for (uint i = 0; i < this.blockHeight; i++) { - sum += Unsafe.Add(ref left, i); + sum += Unsafe.Add(ref leftRef, i); } byte expectedDc = (byte)((sum + (count >> 1)) / count); for (uint r = 0; r < this.blockHeight; r++) { - Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); - destination = ref Unsafe.Add(ref destination, stride); + Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); + destinationRef = ref Unsafe.Add(ref destinationRef, stride); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs index d429b64549..93c98fcaaf 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1DcTopPredictor.cs @@ -23,22 +23,27 @@ public Av1DcTopPredictor(Av1TransformSize transformSize) this.blockHeight = (uint)transformSize.GetHeight(); } - public static void PredictScalar(Av1TransformSize transformSize, ref byte destination, nuint stride, ref byte above, ref byte left) - => new Av1DcTopPredictor(transformSize).PredictScalar(ref destination, stride, ref above, ref left); + public static void PredictScalar(Av1TransformSize transformSize, Span destination, nuint stride, Span above, Span left) + => new Av1DcTopPredictor(transformSize).PredictScalar(destination, stride, above, left); - public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left) + public void PredictScalar(Span destination, nuint stride, Span above, Span left) { int sum = 0; + Guard.MustBeGreaterThanOrEqualTo(stride, this.blockWidth, nameof(stride)); + Guard.MustBeSizedAtLeast(above, (int)this.blockWidth, nameof(above)); + Guard.MustBeSizedAtLeast(destination, (int)this.blockHeight * (int)stride, nameof(destination)); + ref byte aboveRef = ref above[0]; + ref byte destinationRef = ref destination[0]; for (uint i = 0; i < this.blockWidth; i++) { - sum += Unsafe.Add(ref above, i); + sum += Unsafe.Add(ref aboveRef, i); } byte expectedDc = (byte)((sum + (this.blockWidth >> 1)) / this.blockWidth); for (uint r = 0; r < this.blockHeight; r++) { - Unsafe.InitBlock(ref destination, expectedDc, this.blockWidth); - destination = ref Unsafe.Add(ref destination, stride); + Unsafe.InitBlock(ref destinationRef, expectedDc, this.blockWidth); + destinationRef = ref Unsafe.Add(ref destinationRef, stride); } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs index f9ceab2b83..6ac11106bf 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs @@ -6,7 +6,6 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; @@ -37,12 +36,9 @@ public void Decode( int blockModeInfoRowOffset) { int bytesPerPixel = (bitDepth == Av1BitDepth.EightBit && !this.is16BitPipeline) ? 2 : 1; - ref byte pixelRef = ref pixelBuffer[0]; - ref byte topNeighbor = ref pixelRef; - ref byte leftNeighbor = ref pixelRef; int stride = pixelStride * bytesPerPixel; - topNeighbor = Unsafe.Subtract(ref topNeighbor, stride); - leftNeighbor = Unsafe.Subtract(ref leftNeighbor, 1); + Span topNeighbor = pixelBuffer.Slice(-stride); + Span leftNeighbor = pixelBuffer.Slice(-1); bool is16BitPipeline = this.is16BitPipeline; Av1PredictionMode mode = (plane == Av1Plane.Y) ? partitionInfo.ModeInfo.YMode : partitionInfo.ModeInfo.UvMode; @@ -54,10 +50,10 @@ public void Decode( plane, transformSize, tileInfo, - ref pixelRef, + pixelBuffer, stride, - ref topNeighbor, - ref leftNeighbor, + topNeighbor, + leftNeighbor, stride, mode, blockModeInfoColumnOffset, @@ -80,10 +76,10 @@ public void Decode( plane, transformSize, tileInfo, - ref pixelRef, + pixelBuffer, stride, - ref topNeighbor, - ref leftNeighbor, + topNeighbor, + leftNeighbor, stride, mode, blockModeInfoColumnOffset, @@ -199,10 +195,10 @@ private void PredictIntraBlock( Av1Plane plane, Av1TransformSize transformSize, Av1TileInfo tileInfo, - ref byte pixelBuffer, + Span pixelBuffer, int pixelBufferStride, - ref byte topNeighbor, - ref byte leftNeighbor, + Span topNeighbor, + Span leftNeighbor, int referenceStride, Av1PredictionMode mode, int blockModeInfoColumnOffset, @@ -290,10 +286,10 @@ private void PredictIntraBlock( { this.DecodeBuildIntraPredictors( partitionInfo, - ref topNeighbor, - ref leftNeighbor, + topNeighbor, + leftNeighbor, (nuint)referenceStride, - ref pixelBuffer, + pixelBuffer, (nuint)pixelBufferStride, mode, angleDelta, @@ -560,10 +556,10 @@ private static bool IntraHasTopRight(Av1BlockSize superblockSize, Av1BlockSize b private void DecodeBuildIntraPredictors( Av1PartitionInfo partitionInfo, - ref byte aboveNeighbor, - ref byte leftNeighbor, + Span aboveNeighbor, + Span leftNeighbor, nuint referenceStride, - ref byte destination, + Span destination, nuint destinationStride, Av1PredictionMode mode, int angleDelta, @@ -630,17 +626,18 @@ private void DecodeBuildIntraPredictors( byte val; if (needLeft) { - val = (byte)((topPixelCount > 0) ? aboveNeighbor : 129); + val = (byte)((topPixelCount > 0) ? aboveNeighbor[0] : 129); } else { - val = (byte)((leftPixelCount > 0) ? leftNeighbor : 127); + val = (byte)((leftPixelCount > 0) ? leftNeighbor[0] : 127); } + ref byte destinationRef = ref destination[0]; for (int i = 0; i < transformHeight; ++i) { - Unsafe.InitBlock(ref destination, val, (uint)transformWidth); - destination = ref Unsafe.Add(ref destination, destinationStride); + Unsafe.InitBlock(ref destinationRef, val, (uint)transformWidth); + destinationRef = ref Unsafe.Add(ref destinationRef, destinationStride); } return; @@ -666,7 +663,7 @@ private void DecodeBuildIntraPredictors( { for (; i < leftPixelCount; i++) { - leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride); + leftColumn[i] = leftNeighbor[i * (int)referenceStride]; } if (needBottom && bottomLeftPixelCount > 0) @@ -674,7 +671,7 @@ private void DecodeBuildIntraPredictors( Guard.IsTrue(i == transformHeight, nameof(i), string.Empty); for (; i < transformHeight + bottomLeftPixelCount; i++) { - leftColumn[i] = Unsafe.Add(ref leftNeighbor, i * (int)referenceStride); + leftColumn[i] = leftNeighbor[i * (int)referenceStride]; } } @@ -687,7 +684,7 @@ private void DecodeBuildIntraPredictors( { if (topPixelCount > 0) { - Unsafe.InitBlock(ref leftColumn[0], aboveNeighbor, numLeftPixelsNeeded); + Unsafe.InitBlock(ref leftColumn[0], aboveNeighbor[0], numLeftPixelsNeeded); } else { @@ -713,12 +710,12 @@ private void DecodeBuildIntraPredictors( uint numTopPixelsNeeded = (uint)(transformWidth + (needRight ? transformHeight : 0)); if (topPixelCount > 0) { - Unsafe.CopyBlock(ref aboveRow[0], ref aboveNeighbor, (uint)topPixelCount); + Unsafe.CopyBlock(ref aboveRow[0], ref aboveNeighbor[0], (uint)topPixelCount); int i = topPixelCount; if (needRight && topPixelCount > 0) { Guard.IsTrue(topPixelCount == transformWidth, nameof(topPixelCount), string.Empty); - Unsafe.CopyBlock(ref aboveRow[transformWidth], ref Unsafe.Add(ref aboveNeighbor, transformWidth), (uint)topPixelCount); + Unsafe.CopyBlock(ref aboveRow[transformWidth], ref aboveNeighbor[transformWidth], (uint)topPixelCount); i += topPixelCount; } @@ -731,7 +728,7 @@ private void DecodeBuildIntraPredictors( { if (leftPixelCount > 0) { - Unsafe.InitBlock(ref aboveRow[0], leftNeighbor, numTopPixelsNeeded); + Unsafe.InitBlock(ref aboveRow[0], leftNeighbor[0], numTopPixelsNeeded); } else { @@ -744,15 +741,15 @@ private void DecodeBuildIntraPredictors( { if (topPixelCount > 0 && leftPixelCount > 0) { - aboveRow[-1] = Unsafe.Subtract(ref aboveNeighbor, 1); + aboveRow[-1] = aboveNeighbor[-1]; } else if (topPixelCount > 0) { - aboveRow[-1] = aboveNeighbor; + aboveRow[-1] = aboveNeighbor[0]; } else if (leftPixelCount > 0) { - aboveRow[-1] = leftNeighbor; + aboveRow[-1] = leftNeighbor[0]; } else { @@ -764,7 +761,7 @@ private void DecodeBuildIntraPredictors( if (useFilterIntra) { - Av1PredictorFactory.FilterIntraPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode); + Av1PredictorFactory.FilterIntraPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, filterIntraMode); return; } @@ -819,18 +816,18 @@ private void DecodeBuildIntraPredictors( } } - Av1PredictorFactory.DirectionalPredictor(ref destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle); + Av1PredictorFactory.DirectionalPredictor(destination, destinationStride, transformSize, aboveRow, leftColumn, upsampleAbove, upsampleLeft, angle); return; } // predict if (mode == Av1PredictionMode.DC) { - Av1PredictorFactory.DcPredictor(leftPixelCount > 0, topPixelCount > 0, transformSize, ref destination, destinationStride, aboveRow, leftColumn); + Av1PredictorFactory.DcPredictor(leftPixelCount > 0, topPixelCount > 0, transformSize, destination, destinationStride, aboveRow, leftColumn); } else { - Av1PredictorFactory.GeneralPredictor(mode, transformSize, ref destination, destinationStride, aboveRow, leftColumn); + Av1PredictorFactory.GeneralPredictor(mode, transformSize, destination, destinationStride, aboveRow, leftColumn); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs index 12349cc010..9cddf0de38 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictorFactory.cs @@ -8,35 +8,35 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; internal class Av1PredictorFactory { - internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) + internal static void DcPredictor(bool hasLeft, bool hasAbove, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span aboveRow, Span leftColumn) { if (hasLeft) { if (hasAbove) { - Av1DcPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + Av1DcPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); } else { - Av1DcLeftPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + Av1DcLeftPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); } } else { if (hasAbove) { - Av1DcTopPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + Av1DcTopPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); } else { - Av1DcFillPredictor.PredictScalar(transformSize, ref destination, destinationStride, ref aboveRow[0], ref leftColumn[0]); + Av1DcFillPredictor.PredictScalar(transformSize, destination, destinationStride, aboveRow, leftColumn); } } } - internal static void DirectionalPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException(); + internal static void DirectionalPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, bool upsampleAbove, bool upsampleLeft, int angle) => throw new NotImplementedException(); - internal static void FilterIntraPredictor(ref byte destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException(); + internal static void FilterIntraPredictor(Span destination, nuint destinationStride, Av1TransformSize transformSize, Span aboveRow, Span leftColumn, Av1FilterIntraMode filterIntraMode) => throw new NotImplementedException(); - internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, ref byte destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException(); + internal static void GeneralPredictor(Av1PredictionMode mode, Av1TransformSize transformSize, Span destination, nuint destinationStride, Span aboveRow, Span leftColumn) => throw new NotImplementedException(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs index 3bf8907789..567e6c6520 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/IAv1Predictor.cs @@ -15,5 +15,5 @@ internal interface IAv1Predictor /// The stride of the destination buffer. /// Pointer to the first element of the block above. /// Pointer to the first element of the block to the left. - public void PredictScalar(ref byte destination, nuint stride, ref byte above, ref byte left); + public void PredictScalar(Span destination, nuint stride, Span above, Span left); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs index 4a5e503c83..ec70fbf689 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileInfo.cs @@ -23,8 +23,6 @@ public Av1TileInfo(int row, int column, ObuFrameHeader frameHeader) public Point TileIndex { get; private set; } - public int TileIndexInRasterOrder { get; } - public void SetTileRow(ObuTileGroupHeader tileGroupHeader, int modeInfoRowCount, int row) { this.ModeInfoRowStart = tileGroupHeader.TileRowStartModeInfo[row]; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs similarity index 81% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs index 42dfaa568a..f72e4421a8 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/PredictorTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1PredictorTests.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class PredictorTests +public class Av1PredictorTests { [Theory] [MemberData(nameof(GetTransformSizes))] @@ -21,7 +21,7 @@ public void VerifyDcFill(int width, int height) // Act Av1DcFillPredictor predictor = new(new Size(width, height)); - predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + predictor.PredictScalar(destination, (nuint)width, above, left); // Assert Assert.All(destination, (b) => AssertValue(expected, b)); @@ -33,8 +33,8 @@ public void VerifyDc(int width, int height) { // Assign byte[] destination = new byte[width * height]; - byte[] left = new byte[width * height]; - byte[] above = new byte[width * height]; + byte[] left = new byte[height]; + byte[] above = new byte[width]; Array.Fill(left, (byte)5); Array.Fill(above, (byte)28); int count = width + height; @@ -43,7 +43,7 @@ public void VerifyDc(int width, int height) // Act Av1DcPredictor predictor = new(new Size(width, height)); - predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + predictor.PredictScalar(destination, (nuint)width, above, left); // Assert Assert.Equal((5 * height) + (28 * width), sum); @@ -56,15 +56,15 @@ public void VerifyDcLeft(int width, int height) { // Assign byte[] destination = new byte[width * height]; - byte[] left = new byte[width * height]; - byte[] above = new byte[width * height]; + byte[] left = new byte[height]; + byte[] above = new byte[width]; Array.Fill(left, (byte)5); Array.Fill(above, (byte)28); byte expected = left[0]; // Act Av1DcLeftPredictor predictor = new(new Size(width, height)); - predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + predictor.PredictScalar(destination, (nuint)width, above, left); // Assert Assert.All(destination, (b) => AssertValue(expected, b)); @@ -76,15 +76,15 @@ public void VerifyDcTop(int width, int height) { // Assign byte[] destination = new byte[width * height]; - byte[] left = new byte[width * height]; - byte[] above = new byte[width * height]; + byte[] left = new byte[height]; + byte[] above = new byte[width]; Array.Fill(left, (byte)5); Array.Fill(above, (byte)28); byte expected = above[0]; // Act Av1DcTopPredictor predictor = new(new Size(width, height)); - predictor.PredictScalar(ref destination[0], (nuint)width, ref above[0], ref left[0]); + predictor.PredictScalar(destination, (nuint)width, above, left); // Assert Assert.All(destination, (b) => AssertValue(expected, b)); From 13be5eb7cb410d22a7920b038f2108a619c8768e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 18 Nov 2024 22:31:48 +0100 Subject: [PATCH 183/190] Initial coefficient encoding --- .../Formats/Heif/Av1/Av1Constants.cs | 5 + src/ImageSharp/Formats/Heif/Av1/Av1Math.cs | 25 + .../Av1/Prediction/Av1PredictionDecoder.cs | 4 +- .../Heif/Av1/Tiling/Av1BlockGeometry.cs | 24 + .../Av1/Tiling/Av1BlockModeInfoEncoder.cs | 30 + .../Formats/Heif/Av1/Tiling/Av1BlockStruct.cs | 8 + .../Formats/Heif/Av1/Tiling/Av1Common.cs | 22 + .../Heif/Av1/Tiling/Av1ComponentType.cs | 14 + .../Av1/Tiling/Av1DefaultDistributions.cs | 190 +++++++ .../Heif/Av1/Tiling/Av1EncoderBlockStruct.cs | 16 + .../Av1/Tiling/Av1EntropyCodingContext.cs | 14 + .../Heif/Av1/Tiling/Av1FilterIntraMode.cs | 2 +- .../Tiling/Av1FilterIntraModeExtensions.cs | 15 + .../Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs | 22 + .../Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs | 16 + .../Heif/Av1/Tiling/Av1NeighborArrayUnit.cs | 11 + .../Formats/Heif/Av1/Tiling/Av1NzMap.cs | 2 +- .../Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs | 11 + .../Heif/Av1/Tiling/Av1PictureControlSet.cs | 22 + .../Av1/Tiling/Av1PictureParentControlSet.cs | 20 + .../Heif/Av1/Tiling/Av1SequenceControlSet.cs | 14 + .../Formats/Heif/Av1/Tiling/Av1Superblock.cs | 11 + .../Heif/Av1/Tiling/Av1SymbolDecoder.cs | 44 +- .../Heif/Av1/Tiling/Av1SymbolEncoder.cs | 535 +++++++++++++++++- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 6 +- .../Formats/Heif/Av1/SymbolTests.cs | 12 +- 26 files changed, 1057 insertions(+), 38 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 6539012e5b..089415d25c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -160,6 +160,8 @@ internal static class Av1Constants public const int TransformPadTop = 2; + public const int TransformPadBottom = 4; + public const int BaseRangeSizeMinus1 = 3; public const int MaxBaseRange = 15; @@ -182,4 +184,7 @@ internal static class Av1Constants public const int MaxTransformStageNumber = 12; public const int PartitionProbabilitySet = 4; + + // Number of transform sizes that use extended transforms. + public const int ExtendedTransformCount = 4; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs index 62d6d63c08..f390cbdb8b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Math.cs @@ -2,6 +2,8 @@ // Licensed under the Six Labors Split License. using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -50,6 +52,29 @@ public static int Log2(int n) return result; } + /// + /// Long Log 2 + /// This is a quick adaptation of a Number + /// Leading Zeros(NLZ) algorithm to get the log2f of a 32-bit number + /// + internal static uint Log2_32(uint x) + { + uint log = 0; + int i; + for (i = 4; i >= 0; --i) + { + uint shift = 1u << i; + uint n = x >> (int)shift; + if (n != 0) + { + x = n; + log += shift; + } + } + + return log; + } + public static uint FloorLog2(uint value) { uint s = 0; diff --git a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs index 6ac11106bf..a7c14a0e59 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Prediction/Av1PredictionDecoder.cs @@ -224,7 +224,7 @@ private void PredictIntraBlock( } Av1FilterIntraMode filterIntraMode = (plane == Av1Plane.Y && modeInfo.FilterIntraModeInfo.UseFilterIntra) - ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.FilterIntraModes; + ? modeInfo.FilterIntraModeInfo.Mode : Av1FilterIntraMode.AllFilterIntraModes; int angleDelta = modeInfo.AngleDelta[Math.Min(1, (int)plane)]; @@ -584,7 +584,7 @@ private void DecodeBuildIntraPredictors( bool needAbove = (need & Av1NeighborNeed.Above) == Av1NeighborNeed.Above; bool needAboveLeft = (need & Av1NeighborNeed.AboveLeft) == Av1NeighborNeed.AboveLeft; int angle = 0; - bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.FilterIntraModes; + bool useFilterIntra = filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes; if (isDirectionalMode) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs new file mode 100644 index 0000000000..104099eea8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs @@ -0,0 +1,24 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1BlockGeometry + { + public Av1BlockSize BlockSize { get; internal set; } + + public Point Origin { get; internal set; } + + public bool HasUv { get; internal set; } + + public int BlockWidth { get; internal set; } + + public int BlockHeight { get; internal set; } + + public required Av1TransformSize[] TransformSize { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs new file mode 100644 index 0000000000..a8113a7ece --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1BlockModeInfoEncoder + { + public Av1BlockSize BlockSize { get; } + + public Av1PredictionMode PredictionMode { get; } + + public Av1PartitionType PartitionType { get; } + + public Av1PredictionMode UvPredictionMode { get; } + + public bool Skip { get; } = true; + + public bool SkipMode { get; } = true; + + public bool UseIntraBlockCopy { get; } = true; + + public int SegmentId { get; } + + public int TransformDepth { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs new file mode 100644 index 0000000000..53ee7ef8bb --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs @@ -0,0 +1,8 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal class Av1BlockStruct +{ +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs new file mode 100644 index 0000000000..c3c29deae4 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1Common + { + public int ModeInfoRowCount { get; internal set; } + + public int ModeInfoColumnCount { get; internal set; } + + public int ModeInfoStride { get; internal set; } + + public required ObuFrameSize FrameSize { get; internal set; } + + public required ObuTileGroupHeader TilesInfo { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs new file mode 100644 index 0000000000..b8546e72bf --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1ComponentType.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal enum Av1ComponentType +{ + Luminance = 0, // luma + Chroma = 1, // chroma (Cb+Cr) + ChromaCb = 2, // chroma Cb + ChromaCr = 3, // chroma Cr + All = 4, // Y+Cb+Cr + None = 15 +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs index b2c5259e10..49d93ba24b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs @@ -2006,6 +2006,196 @@ internal static class Av1DefaultDistributions new(14738, 21678, 25779, 27901, 29024, 30302, 30980, 31843, 32144, 32413, 32520, 32594, 32622, 32656, 32660) ]; + public static Av1Distribution[][][] IntraExtendedTransform => + [ + [ + [ + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0) + ], + [ + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0) + ], + [ + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0) + ], + [ + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0), + new(0) + ], + ], + [ + [ + new(1535, 8035, 9461, 12751, 23467, 27825), + new(564, 3335, 9709, 10870, 18143, 28094), + new(672, 3247, 3676, 11982, 19415, 23127), + new(5279, 13885, 15487, 18044, 23527, 30252), + new(4423, 6074, 7985, 10416, 25693, 29298), + new(1486, 4241, 9460, 10662, 16456, 27694), + new(439, 2838, 3522, 6737, 18058, 23754), + new(1190, 4233, 4855, 11670, 20281, 24377), + new(1045, 4312, 8647, 10159, 18644, 29335), + new(202, 3734, 4747, 7298, 17127, 24016), + new(447, 4312, 6819, 8884, 16010, 23858), + new(277, 4369, 5255, 8905, 16465, 22271), + new(3409, 5436, 10599, 15599, 19687, 24040) + ], + [ + new(1870, 13742, 14530, 16498, 23770, 27698), + new(326, 8796, 14632, 15079, 19272, 27486), + new(484, 7576, 7712, 14443, 19159, 22591), + new(1126, 15340, 15895, 17023, 20896, 30279), + new(655, 4854, 5249, 5913, 22099, 27138), + new(1299, 6458, 8885, 9290, 14851, 25497), + new(311, 5295, 5552, 6885, 16107, 22672), + new(883, 8059, 8270, 11258, 17289, 21549), + new(741, 7580, 9318, 10345, 16688, 29046), + new(110, 7406, 7915, 9195, 16041, 23329), + new(363, 7974, 9357, 10673, 15629, 24474), + new(153, 7647, 8112, 9936, 15307, 19996), + new(3511, 6332, 11165, 15335, 19323, 23594) + ], + [ + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087) + ], + [ + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + new(4681, 9362, 14043, 18725, 23406, 28087), + ], + ], + [ + [ + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214) + ], + [ + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214) + ], + [ + new(1127, 12814, 22772, 27483), + new(145, 6761, 11980, 26667), + new(362, 5887, 11678, 16725), + new(385, 15213, 18587, 30693), + new(25, 2914, 23134, 27903), + new(60, 4470, 11749, 23991), + new(37, 3332, 14511, 21448), + new(157, 6320, 13036, 17439), + new(119, 6719, 12906, 29396), + new(47, 5537, 12576, 21499), + new(269, 6076, 11258, 23115), + new(83, 5615, 12001, 17228), + new(1968, 5556, 12023, 18547) + ], + [ + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214), + new(6554, 13107, 19661, 26214) + ] + ], + ]; + public static Av1Distribution[][][] GetEndOfBlockFlag(int baseQIndex) { int qContext = GetQContext(baseQIndex); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs new file mode 100644 index 0000000000..61ebeb364f --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EncoderBlockStruct.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1EncoderBlockStruct + { + public required Av1MacroBlockD av1xd { get; internal set; } + + public required int[] PaletteSize { get; internal set; } + + public int QIndex { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs new file mode 100644 index 0000000000..05c501d4d9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1EntropyCodingContext + { + public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } + + public Point SuperblockOrigin { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs index eaf33a3d8c..f536982e01 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraMode.cs @@ -10,5 +10,5 @@ internal enum Av1FilterIntraMode Horizontal, Directional157, Paeth, - FilterIntraModes, + AllFilterIntraModes, } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs new file mode 100644 index 0000000000..0a2e2e8325 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1FilterIntraModeExtensions.cs @@ -0,0 +1,15 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal static class Av1FilterIntraModeExtensions +{ + private static readonly Av1PredictionMode[] IntraDirection = + [Av1PredictionMode.DC, Av1PredictionMode.Vertical, Av1PredictionMode.Horizontal, Av1PredictionMode.Directional157Degrees, Av1PredictionMode.DC]; + + public static Av1PredictionMode ToIntraDirection(this Av1FilterIntraMode mode) + => IntraDirection[(int)mode]; +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs new file mode 100644 index 0000000000..da1469173b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1MacroBlockD + { + public required Av1BlockModeInfo ModeInfo { get; internal set; } + + public required Av1TileInfo Tile { get; internal set; } + + public bool IsUpAvailable { get; } + + public bool IsLeftAvailable { get; } + + public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; } + + public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs new file mode 100644 index 0000000000..8fb96f4d2d --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1MacroBlockModeInfo + { + public required Av1BlockModeInfoEncoder Block { get; internal set; } + + public required Av1PaletteLumaModeInfo Palette { get; internal set; } + + public int CdefStrength { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs new file mode 100644 index 0000000000..0aa6a68b09 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1NeighborArrayUnit + { + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs index c5505899ad..89fab2b84b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs @@ -282,7 +282,7 @@ internal static class Av1NzMap NzMapContextOffset64x32, // TX_64x16 ]; - public static int GetNzMagnitude(Span levels, int bwl, Av1TransformClass transformClass) + public static int GetNzMagnitude(ReadOnlySpan levels, int bwl, Av1TransformClass transformClass) { int mag; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs new file mode 100644 index 0000000000..bb40ca53a8 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1PaletteLumaModeInfo + { + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs new file mode 100644 index 0000000000..851f71dae3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1PictureControlSet + { + public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; } + + public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; } + + public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } + + public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } + + public required Av1SequenceControlSet Sequence { get; internal set; } + + public required Av1PictureParentControlSet Parent { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs new file mode 100644 index 0000000000..6bbc651eac --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -0,0 +1,20 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1PictureParentControlSet + { + public required Av1Common Common { get; internal set; } + + public required ObuFrameHeader FrameHeader { get; internal set; } + + public required int[] PreviousQIndex { get; internal set; } + + public int PaletteLevel { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs new file mode 100644 index 0000000000..1ad59b31ab --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs @@ -0,0 +1,14 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1SequenceControlSet + { + public required ObuSequenceHeader SequenceHeader { get; internal set; } + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs new file mode 100644 index 0000000000..776451aeaf --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Superblock.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal partial class Av1TileWriter +{ + internal class Av1Superblock + { + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs index a327d7094a..f116abe8df 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs @@ -10,8 +10,8 @@ internal ref struct Av1SymbolDecoder { private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; - private static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - private static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; + public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; @@ -233,7 +233,7 @@ public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformCl return RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); } - public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; int pos = scan[i]; @@ -253,10 +253,10 @@ public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int end } } - levels[GetPaddedIndex(pos, bwl)] = level; + levels[GetPaddedIndex(pos, bwl)] = (byte)level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { @@ -277,11 +277,11 @@ public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startS } } - levels[GetPaddedIndex(pos, bwl)] = level; + levels[GetPaddedIndex(pos, bwl)] = (byte)level; } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { @@ -302,11 +302,11 @@ public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1Transform } } - levels[GetPaddedIndex(pos, bwl)] = level; + levels[GetPaddedIndex(pos, bwl)] = (byte)level; } } - public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; @@ -439,7 +439,7 @@ private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanI return 3; } - private static int GetBaseRangeContext2d(Span levels, int c, int bwl) + private static int GetBaseRangeContext2d(Span levels, int c, int bwl) { DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); int row = c >> bwl; @@ -447,9 +447,9 @@ private static int GetBaseRangeContext2d(Span levels, int c, int bwl) int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; int pos = (row * stride) + col; int mag = - Math.Min(levels[pos + 1], Av1Constants.MaxBaseRange) + - Math.Min(levels[pos + stride], Av1Constants.MaxBaseRange) + - Math.Min(levels[pos + 1 + stride], Av1Constants.MaxBaseRange); + Math.Min((int)levels[pos + 1], Av1Constants.MaxBaseRange) + + Math.Min((int)levels[pos + stride], Av1Constants.MaxBaseRange) + + Math.Min((int)levels[pos + 1 + stride], Av1Constants.MaxBaseRange); mag = Math.Min((mag + 1) >> 1, 6); if ((row | col) < 2) { @@ -459,22 +459,22 @@ private static int GetBaseRangeContext2d(Span levels, int c, int bwl) return mag + 14; } - private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) + private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) { DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); int mag; levels = levels[GetPaddedIndex(pos, bwl)..]; - mag = Math.Min(levels[1], 3); // { 0, 1 } - mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } - mag += Math.Min(levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } - mag += Math.Min(levels[2], 3); // { 0, 2 } - mag += Math.Min(levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } + mag = Math.Min((int)levels[1], 3); // { 0, 1 } + mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } + mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } + mag += Math.Min((int)levels[2], 3); // { 0, 2 } + mag += Math.Min((int)levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } int ctx = Math.Min((mag + 1) >> 1, 4); return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); } - private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) + private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) { int row = c >> bwl; int col = c - (row << bwl); @@ -533,13 +533,13 @@ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1Tran return mag + 14; } - private static int GetLowerLevelsContext(Span levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + private static int GetLowerLevelsContext(ReadOnlySpan levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) { int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); } - private static int GetPaddedIndex(int scanIndex, int bwl) + public static int GetPaddedIndex(int scanIndex, int bwl) => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2); private int ReadGolomb() diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs index 78bc2eec49..64664f1c1b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs @@ -1,20 +1,90 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System; using System.Buffers; +using System.Drawing; +using System.Formats.Asn1; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1SymbolEncoder : IDisposable { + private static readonly int[][] ExtendedTransformIndices = [ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 3, 4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 5, 6, 4, 0, 0, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0], + [3, 4, 5, 8, 6, 7, 9, 10, 11, 0, 1, 2, 0, 0, 0, 0], + [7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], + ]; + + private static readonly byte[] EndOfBlockToPositionSmall = [ + 0, 1, 2, // 0-2 + 3, 3, // 3-4 + 4, 4, 4, 4, // 5-8 + 5, 5, 5, 5, 5, 5, 5, 5, // 9-16 + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32 + ]; + + private static readonly byte[] EndOfBlockToPositionLarge = [ + 6, // place holder + 7, // 33-64 + 8, + 8, // 65-128 + 9, + 9, + 9, + 9, // 129-256 + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, // 257-512 + 11 // 513- + ]; + + private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; + + // Maps tx set types to the indices. + private static readonly int[][] ExtendedTransformSetToIndex = [ + + // Intra + [0, -1, 2, 1, -1, -1], + + // Inter + [0, 3, -1, -1, 2, 1] + ]; + private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; - + private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; + private readonly Av1Distribution[][][] endOfBlockFlag; + private readonly Av1Distribution[][][] coefficientsBaseRange; + private readonly Av1Distribution[][][] coefficientsBase; + private readonly Av1Distribution[][][] coefficientsBaseEndOfBlock; + private readonly Av1Distribution[][] dcSign; + private readonly Av1Distribution[][][] endOfBlockExtra; + private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private bool isDisposed; private Av1SymbolWriter writer; - public Av1SymbolEncoder(Configuration configuration, int initialSize) - => this.writer = new(configuration, initialSize); + public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) + { + this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); + this.coefficientsBaseRange = Av1DefaultDistributions.GetCoefficientsBaseRange(qIndex); + this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); + this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); + this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex); + this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); + this.writer = new(configuration, initialSize); + } public void WriteUseIntraBlockCopy(bool value) { @@ -44,6 +114,332 @@ public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize bl w.WriteSymbol(value, distribution); } + /// + /// SVT: av1_write_coeffs_txb_1d + /// + public int WriteCoefficients( + Av1TransformSize transformSize, + Av1TransformType transformType, + uint txb_index, // TODO: Doesn't seem to be used, remove. + Av1PredictionMode intraLumaDir, + Span coeff_buffer_ptr, + Av1ComponentType componentType, + short txb_skip_ctx, + short dc_sign_ctx, + ushort eob, + bool useReducedTransformSet, + int baseQIndex, + Av1FilterIntraMode filterIntraMode) + { + int c; + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + Av1TransformClass transformClass = transformType.ToClass(); + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); + ReadOnlySpan scan = scanOrder.Scan; + int bwl = transformSize.GetBlockWidthLog2(); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); + + ref Av1SymbolWriter w = ref this.writer; + + Span levels_buf = new byte[Av1Constants.TransformPad2d]; + Span levels = SetLevels(levels_buf, width); + Span coeff_contexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; + + Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); + + bool hasEndOfBlock = eob != 0; + this.WriteSkip(!hasEndOfBlock, txb_skip_ctx); + + if (eob == 0) + { + return 0; + } + + InitializeLevels(coeff_buffer_ptr, width, height, levels); + if (componentType == Av1ComponentType.Luminance) + { + this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraLumaDir); + } + + short endOfBlockPosition = GetEndOfBlockPosition(eob, out int eob_extra); + this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); + + int eob_offset_bits = Av1SymbolDecoder.EndOfBlockOffsetBits[endOfBlockPosition]; + if (eob_offset_bits > 0) + { + int eob_shift = eob_offset_bits - 1; + int bit = Math.Max(1, eob_extra & (1 << eob_shift)); + w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); + for (int i = 1; i < eob_offset_bits; i++) + { + eob_shift = eob_offset_bits - 1 - i; + bit = Math.Max(1, eob_extra & (1 << eob_shift)); + w.WriteLiteral((uint)bit, 1); + } + } + + GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); + int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); + for (c = eob - 1; c >= 0; --c) + { + short pos = scan[c]; + int v = coeff_buffer_ptr[pos]; + short coeff_ctx = coeff_contexts[pos]; + int level = Math.Abs(v); + + if (c == eob - 1) + { + w.WriteSymbol(Math.Min(level, 3) - 1, this.coefficientsBaseEndOfBlock[(int)transformSizeContext][(int)componentType][coeff_ctx]); + } + else + { + w.WriteSymbol(Math.Min(level, 3), this.coefficientsBase[(int)transformSizeContext][(int)componentType][coeff_ctx]); + } + + if (level > Av1Constants.BaseLevelsCount) + { + // level is above 1. + int baseRange = level - 1 - Av1Constants.BaseLevelsCount; + int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) + { + int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); + w.WriteSymbol(k, this.coefficientsBaseRange[limitedTransformSizeContext][(int)componentType][baseRangeContext]); + if (k < Av1Constants.BaseRangeSizeMinus1) + { + break; + } + } + } + } + + // Loop to code all signs in the transform block, + // starting with the sign of DC (if applicable) + int cul_level = 0; + for (c = 0; c < eob; ++c) + { + short pos = scan[c]; + int v = coeff_buffer_ptr[pos]; + int level = Math.Abs(v); + cul_level += level; + + uint sign = (v < 0) ? 1u : 0u; + if (level > 0) + { + if (c == 0) + { + w.WriteSymbol((int)sign, this.dcSign[(int)componentType][dc_sign_ctx]); + } + else + { + w.WriteLiteral(sign, 1); + } + + if (level > Av1Constants.CoefficientBaseRange + Av1Constants.BaseLevelsCount) + { + this.WriteGolomb(level - Av1Constants.CoefficientBaseRange - 1 - Av1Constants.BaseLevelsCount); + } + } + } + + cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); + + // DC value + SetDcSign(ref cul_level, coeff_buffer_ptr[0]); + return cul_level; + } + + /// + /// SVT: set_dc_sign + /// + private static void SetDcSign(ref int cul_level, int dc_val) + { + if (dc_val < 0) + { + cul_level |= 1 << Av1Constants.CoefficientContextBitCount; + } + else if (dc_val > 0) + { + cul_level += 2 << Av1Constants.CoefficientContextBitCount; + } + } + + /// + /// SVT: get_br_ctx + /// + private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1TransformClass transformClass) + { + int row = c >> bwl; + int col = c - (row << bwl); + int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; + int pos = (row * stride) + col; + int mag = levels[pos + 1]; + mag += levels[pos + stride]; + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += levels[pos + stride + 1]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if ((row < 2) && (col < 2)) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassHorizontal: + mag += levels[pos + 2]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (col == 0) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassVertical: + mag += levels[pos + (stride << 1)]; + mag = Math.Min((mag + 1) >> 1, 6); + if (c == 0) + { + return mag; + } + + if (row == 0) + { + return mag + 7; + } + + break; + default: + break; + } + + return mag + 14; + } + + /// + /// SVT: get_eob_pos_token + /// + private static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) + { + short t; + if (endOfBlock < 33) + { + t = EndOfBlockToPositionSmall[endOfBlock]; + } + else + { + int e = Math.Min((endOfBlock - 1) >> 5, 16); + t = EndOfBlockToPositionLarge[e]; + } + + extra = endOfBlock - Av1SymbolDecoder.EndOfBlockGroupStart[t]; + return t; + } + + /// + /// SVT: get_nz_map_ctx + /// + private static sbyte GetNzMapContext( + ReadOnlySpan levels, + int pos, + int bwl, + int height, + int scan_idx, + bool is_eob, + Av1TransformSize transformSize, + Av1TransformClass transformClass) + { + if (is_eob) + { + if (scan_idx == 0) + { + return 0; + } + + if (scan_idx <= (height << bwl) / 8) + { + return 1; + } + + if (scan_idx <= (height << bwl) / 4) + { + return 2; + } + + return 3; + } + + int stats = Av1NzMap.GetNzMagnitude(levels[Av1SymbolDecoder.GetPaddedIndex(pos, bwl)..], bwl, transformClass); + return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + } + + /// + /// SVT: svt_av1_get_nz_map_contexts_c + /// + private static void GetNzMapContexts( + ReadOnlySpan levels, + ReadOnlySpan scan, + ushort eob, + Av1TransformSize tx_size, + Av1TransformClass tx_class, + Span coeff_contexts) + { + int bwl = tx_size.GetBlockWidthLog2(); + int height = tx_size.GetHeight(); + for (int i = 0; i < eob; ++i) + { + int pos = scan[i]; + coeff_contexts[pos] = GetNzMapContext(levels, pos, bwl, height, i, i == eob - 1, tx_size, tx_class); + } + } + + /// + /// SVT: svt_av1_txb_init_levels_c + /// + private static void InitializeLevels(Span coefficientBuffer, int width, int height, Span levels) + { + int stride = width + Av1Constants.TransformPadHorizontal; + ref byte ls = ref levels[0]; + + Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); + Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd)); + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(i * width) + j]), 0, byte.MaxValue); + ls = ref Unsafe.Add(ref ls, 1); + } + + Unsafe.InitBlock(ref ls, 0, Av1Constants.TransformPadHorizontal); + } + } + + /// + /// SVT: set_levels from EbCommonUtils.h + /// + private static Span SetLevels(Span levels_buf, int width) + => levels_buf.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal)); + + private void WriteSkip(bool hasEndOfBlock, int context) + { + // Has EOB, means we won't skip, negating the logic. + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(hasEndOfBlock ? 0 : 1, this.skip[context]); + } + public IMemoryOwner Exit() { ref Av1SymbolWriter w = ref this.writer; @@ -58,4 +454,137 @@ public void Dispose() this.isDisposed = true; } } + + /// + /// SVT: write_golomb + /// + private void WriteGolomb(int level) + { + int x = level + 1; + int i = x; + int length = (int)Av1Math.Log2_32((uint)x) + 1; + + Guard.MustBeGreaterThan(length, 0, nameof(length)); + + ref Av1SymbolWriter w = ref this.writer; + for (i = 0; i < length - 1; ++i) + { + w.WriteLiteral(0, 1); + } + + for (int j = length - 1; j >= 0; --j) + { + w.WriteLiteral((uint)((x >> j) & 0x01), 1); + } + } + + private void WriteEndOfBlockFlag(Av1ComponentType componentType, Av1TransformClass transformClass, Av1TransformSize transformSize, int endOfBlockPosition) + { + int endOfBlockMultiSize = transformSize.GetLog2Minus4(); + int endOfBlockContext = transformClass == Av1TransformClass.Class2D ? 0 : 1; + ref Av1SymbolWriter w = ref this.writer; + w.WriteSymbol(endOfBlockPosition - 1, this.endOfBlockFlag[endOfBlockMultiSize][(int)componentType][endOfBlockContext]); + } + + /// + /// SVT: av1_write_tx_type + /// + private void WriteTransformType( + Av1TransformType transformType, + Av1TransformSize transformSize, + bool useReducedTransformSet, + int baseQIndex, + Av1FilterIntraMode filterIntraMode, + Av1PredictionMode intraDirection) + { + ref Av1SymbolWriter w = ref this.writer; + bool isInter = false; // mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); + if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && (baseQIndex > 0)) + { + Av1TransformSize square_tx_size = transformSize.GetSquareSize(); + Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size)); + + Av1TransformSetType tx_set_type = GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); + int eset = GetExtendedTransformSet(transformSize, isInter, useReducedTransformSet); + + // eset == 0 should correspond to a set with only DCT_DCT and there + // is no need to send the tx_type + Guard.MustBeGreaterThan(eset, 0, nameof(eset)); + + // assert(av1_ext_tx_used[tx_set_type][transformType]); + if (isInter) + { + /* + w.WriteSymbol( + av1_ext_tx_ind[tx_set_type][transformType], + this.inter_ext_tx_cdf[eset][square_tx_size]);*/ + } + else + { + Av1PredictionMode intra_dir; + if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) + { + intra_dir = filterIntraMode.ToIntraDirection(); + } + else + { + intra_dir = intraDirection; + } + + Guard.MustBeLessThan((int)intra_dir, 13, nameof(intra_dir)); + Guard.MustBeLessThan((int)square_tx_size, 4, nameof(square_tx_size)); + w.WriteSymbol( + ExtendedTransformIndices[(int)tx_set_type][(int)transformType], + this.intraExtendedTransform[eset][(int)square_tx_size][(int)intra_dir]); + } + } + } + + /// + /// SVT: get_ext_tx_set + /// + private static int GetExtendedTransformSet(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) + { + int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); + return ExtendedTransformSetToIndex[isInter ? 1 : 0][set_type]; + } + + /// + /// SVT: get_ext_tx_set_type + /// + private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) + { + Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); + + if (transformSizeSquareUp > Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (transformSizeSquareUp == Av1TransformSize.Size32x32) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; + } + + if (useReducedTransformSet) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); + if (isInter) + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; + } + else + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + } + + private static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) + { + int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); + return TransformCountInSet[set_type]; + } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 2d6372ad6c..751f610a5c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -564,8 +564,8 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; - int[] levelsBuffer = new int[Av1Constants.TransformPad2d]; - Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; + byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; + Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); int bwl = transformSize.GetBlockWidthLog2(); @@ -590,7 +590,7 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part endOfBlock = reader.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); if (endOfBlock > 1) { - Array.Fill(levelsBuffer, 0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); } reader.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs index 81f2d84776..327932b24e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs @@ -2,17 +2,17 @@ // Licensed under the Six Labors Split License. using System.Buffers; -using System.Reflection; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Memory; -using static System.Runtime.InteropServices.JavaScript.JSType; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] public class SymbolTests { + private const int baseQIndex = 23; + [Fact] public void ReadRandomLiteral() { @@ -194,7 +194,7 @@ public void RoundTripPartitionType() // Assign int ctx = 7; Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None]; @@ -231,7 +231,7 @@ public void RoundTripSplitOrHorizontalPartitionType(int blockSize, int context) { // Assign Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Horizontal]; @@ -268,7 +268,7 @@ public void RoundTripSplitOrVerticalPartitionType(int blockSize, int context) { // Assign Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Vertical]; @@ -299,7 +299,7 @@ public void RoundTripUseIntraBlockCopy() // Assign bool[] values = [true, true, false, true, false, false, false]; Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); bool[] actuals = new bool[values.Length]; // Act From 87c57f7705a3cf0e083379a45b7b2773f32c98c9 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 19 Nov 2024 19:46:52 +0100 Subject: [PATCH 184/190] Entropy directory and namespace --- .../Av1DefaultDistributions.cs | 2 +- .../{Tiling => Entropy}/Av1Distribution.cs | 8 ++-- .../Heif/Av1/{Tiling => Entropy}/Av1NzMap.cs | 4 +- .../{Tiling => Entropy}/Av1SymbolDecoder.cs | 37 ++++++++++--------- .../{Tiling => Entropy}/Av1SymbolEncoder.cs | 36 +++++++++--------- .../{Tiling => Entropy}/Av1SymbolReader.cs | 20 +++++----- .../{Tiling => Entropy}/Av1SymbolWriter.cs | 30 +++++++-------- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 1 + .../Av1/{SymbolTests.cs => EntropyTests.cs} | 14 +++---- 9 files changed, 78 insertions(+), 74 deletions(-) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1DefaultDistributions.cs (99%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1Distribution.cs (94%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1NzMap.cs (99%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1SymbolDecoder.cs (95%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1SymbolEncoder.cs (95%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1SymbolReader.cs (89%) rename src/ImageSharp/Formats/Heif/Av1/{Tiling => Entropy}/Av1SymbolWriter.cs (86%) rename tests/ImageSharp.Tests/Formats/Heif/Av1/{SymbolTests.cs => EntropyTests.cs} (96%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs similarity index 99% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs index 49d93ba24b..6e03564721 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1DefaultDistributions.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1DefaultDistributions.cs @@ -3,7 +3,7 @@ using System; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal static class Av1DefaultDistributions { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs similarity index 94% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs index 1f3cf6916f..2750aca35b 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Distribution.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1Distribution.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; /// /// Class representing the probability distribution used for symbol coding. @@ -111,15 +111,15 @@ public void Update(int value) // Single loop (faster) for (int i = 0; i < this.NumberOfSymbols - 1; i++) { - tmp = (i == value) ? 0 : tmp; + tmp = i == value ? 0 : tmp; uint p = this.probabilities[i]; if (tmp < p) { - this.probabilities[i] -= (ushort)((p - tmp) >> rate); + this.probabilities[i] -= (ushort)(p - tmp >> rate); } else { - this.probabilities[i] += (ushort)((tmp - p) >> rate); + this.probabilities[i] += (ushort)(tmp - p >> rate); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs similarity index 99% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs index 89fab2b84b..b66d092ba5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs @@ -3,7 +3,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal static class Av1NzMap { @@ -321,7 +321,7 @@ public static int GetNzMapContextFromStats(int stats, int pos, int bwl, Av1Trans return 0; } - int ctx = (stats + 1) >> 1; + int ctx = stats + 1 >> 1; ctx = Math.Min(ctx, 4); switch (transformClass) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs similarity index 95% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index f116abe8df..78dd1efe34 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -2,9 +2,10 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal ref struct Av1SymbolDecoder { @@ -140,7 +141,7 @@ public int ReadSegmentId(int ctx) public int ReadAngleDelta(Av1PredictionMode mode) { ref Av1SymbolReader r = ref this.reader; - return r.ReadSymbol(this.angleDelta[((int)mode) - 1]); + return r.ReadSymbol(this.angleDelta[(int)mode - 1]); } public bool ReadUseFilterUltra(Av1BlockSize blockSize) @@ -216,7 +217,7 @@ public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformCl bool bit = this.ReadEndOfBlockExtra(transformSizeContext, planeType, endOfBlockContext); if (bit) { - endOfBlockExtra += 1 << (endOfBlockShift - 1); + endOfBlockExtra += 1 << endOfBlockShift - 1; } else { @@ -224,7 +225,7 @@ public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformCl { if (this.ReadLiteral(1) != 0) { - endOfBlockExtra += 1 << (endOfBlockShift - 1 - j); + endOfBlockExtra += 1 << endOfBlockShift - 1 - j; } } } @@ -409,9 +410,9 @@ private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformC return 0; } - if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || - (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || - (transformClass == Av1TransformClass.ClassVertical && row == 0)) + if (transformClass == Av1TransformClass.Class2D && row < 2 && col < 2 || + transformClass == Av1TransformClass.ClassHorizontal && col == 0 || + transformClass == Av1TransformClass.ClassVertical && row == 0) { return 7; } @@ -426,12 +427,12 @@ private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanI return 0; } - if (scanIndex <= (height << bwl) >> 3) + if (scanIndex <= height << bwl >> 3) { return 1; } - if (scanIndex <= (height << bwl) >> 2) + if (scanIndex <= height << bwl >> 2) { return 2; } @@ -445,12 +446,12 @@ private static int GetBaseRangeContext2d(Span levels, int c, int bwl) int row = c >> bwl; int col = c - (row << bwl); int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = (row * stride) + col; + int pos = row * stride + col; int mag = Math.Min((int)levels[pos + 1], Av1Constants.MaxBaseRange) + Math.Min((int)levels[pos + stride], Av1Constants.MaxBaseRange) + Math.Min((int)levels[pos + 1 + stride], Av1Constants.MaxBaseRange); - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if ((row | col) < 2) { return mag + 7; @@ -470,7 +471,7 @@ private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, mag += Math.Min((int)levels[2], 3); // { 0, 2 } mag += Math.Min((int)levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } - int ctx = Math.Min((mag + 1) >> 1, 4); + int ctx = Math.Min(mag + 1 >> 1, 4); return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); } @@ -479,20 +480,20 @@ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1Tra int row = c >> bwl; int col = c - (row << bwl); int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = (row * stride) + col; + int pos = row * stride + col; int mag = levels[pos + 1]; mag += levels[pos + stride]; switch (transformClass) { case Av1TransformClass.Class2D: mag += levels[pos + stride + 1]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; } - if ((row < 2) && (col < 2)) + if (row < 2 && col < 2) { return mag + 7; } @@ -500,7 +501,7 @@ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1Tra break; case Av1TransformClass.ClassHorizontal: mag += levels[pos + 2]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; @@ -514,7 +515,7 @@ private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1Tra break; case Av1TransformClass.ClassVertical: mag += levels[pos + (stride << 1)]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; @@ -540,7 +541,7 @@ private static int GetLowerLevelsContext(ReadOnlySpan levels, int pos, int } public static int GetPaddedIndex(int scanIndex, int bwl) - => scanIndex + ((scanIndex >> bwl) << Av1Constants.TransformPadHorizontalLog2); + => scanIndex + (scanIndex >> bwl << Av1Constants.TransformPadHorizontalLog2); private int ReadGolomb() { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs similarity index 95% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 64664f1c1b..f28f161d86 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -6,10 +6,12 @@ using System.Drawing; using System.Formats.Asn1; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal class Av1SymbolEncoder : IDisposable { @@ -101,7 +103,7 @@ public void WritePartitionType(Av1PartitionType partitionType, int context) public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) { Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrHorizontalDistribution(this.tilePartitionTypes, blockSize, context); - int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + int value = partitionType == Av1PartitionType.Split ? 1 : 0; ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(value, distribution); } @@ -109,7 +111,7 @@ public void WriteSplitOrHorizontal(Av1PartitionType partitionType, Av1BlockSize public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize blockSize, int context) { Av1Distribution distribution = Av1SymbolDecoder.GetSplitOrVerticalDistribution(this.tilePartitionTypes, blockSize, context); - int value = (partitionType == Av1PartitionType.Split) ? 1 : 0; + int value = partitionType == Av1PartitionType.Split ? 1 : 0; ref Av1SymbolWriter w = ref this.writer; w.WriteSymbol(value, distribution); } @@ -138,7 +140,7 @@ public int WriteCoefficients( Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; int bwl = transformSize.GetBlockWidthLog2(); - Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); + Av1TransformSize transformSizeContext = (Av1TransformSize)((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1 >> 1); ref Av1SymbolWriter w = ref this.writer; @@ -169,12 +171,12 @@ public int WriteCoefficients( if (eob_offset_bits > 0) { int eob_shift = eob_offset_bits - 1; - int bit = Math.Max(1, eob_extra & (1 << eob_shift)); + int bit = Math.Max(1, eob_extra & 1 << eob_shift); w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); for (int i = 1; i < eob_offset_bits; i++) { eob_shift = eob_offset_bits - 1 - i; - bit = Math.Max(1, eob_extra & (1 << eob_shift)); + bit = Math.Max(1, eob_extra & 1 << eob_shift); w.WriteLiteral((uint)bit, 1); } } @@ -224,7 +226,7 @@ public int WriteCoefficients( int level = Math.Abs(v); cul_level += level; - uint sign = (v < 0) ? 1u : 0u; + uint sign = v < 0 ? 1u : 0u; if (level > 0) { if (c == 0) @@ -273,20 +275,20 @@ private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1T int row = c >> bwl; int col = c - (row << bwl); int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = (row * stride) + col; + int pos = row * stride + col; int mag = levels[pos + 1]; mag += levels[pos + stride]; switch (transformClass) { case Av1TransformClass.Class2D: mag += levels[pos + stride + 1]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; } - if ((row < 2) && (col < 2)) + if (row < 2 && col < 2) { return mag + 7; } @@ -294,7 +296,7 @@ private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1T break; case Av1TransformClass.ClassHorizontal: mag += levels[pos + 2]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; @@ -308,7 +310,7 @@ private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1T break; case Av1TransformClass.ClassVertical: mag += levels[pos + (stride << 1)]; - mag = Math.Min((mag + 1) >> 1, 6); + mag = Math.Min(mag + 1 >> 1, 6); if (c == 0) { return mag; @@ -339,7 +341,7 @@ private static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) } else { - int e = Math.Min((endOfBlock - 1) >> 5, 16); + int e = Math.Min(endOfBlock - 1 >> 5, 16); t = EndOfBlockToPositionLarge[e]; } @@ -413,13 +415,13 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int ref byte ls = ref levels[0]; Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); - Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd)); + Unsafe.InitBlock(ref levels[stride * height], 0, (uint)(Av1Constants.TransformPadBottom * stride + Av1Constants.TransformPadEnd)); for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { - ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(i * width) + j]), 0, byte.MaxValue); + ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[i * width + j]), 0, byte.MaxValue); ls = ref Unsafe.Add(ref ls, 1); } @@ -474,7 +476,7 @@ private void WriteGolomb(int level) for (int j = length - 1; j >= 0; --j) { - w.WriteLiteral((uint)((x >> j) & 0x01), 1); + w.WriteLiteral((uint)(x >> j & 0x01), 1); } } @@ -499,7 +501,7 @@ private void WriteTransformType( { ref Av1SymbolWriter w = ref this.writer; bool isInter = false; // mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); - if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && (baseQIndex > 0)) + if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && baseQIndex > 0) { Av1TransformSize square_tx_size = transformSize.GetSquareSize(); Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size)); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs similarity index 89% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs index 112151b152..dbf9a478f2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolReader.cs @@ -1,7 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal ref struct Av1SymbolReader { @@ -32,7 +32,7 @@ public Av1SymbolReader(Span span) { this.buffer = span; this.position = 0; - this.difference = (1U << (DecoderWindowsSize - 1)) - 1; + this.difference = (1U << DecoderWindowsSize - 1) - 1; this.range = 0x8000; this.count = -15; this.Refill(); @@ -49,7 +49,7 @@ public int ReadSymbol(Av1Distribution distribution) public int ReadLiteral(int bitCount) { - const uint prob = (0x7FFFFFU - (128 << 15) + 128) >> 8; + const uint prob = 0x7FFFFFU - (128 << 15) + 128 >> 8; int literal = 0; for (int bit = bitCount - 1; bit >= 0; bit--) { @@ -82,9 +82,9 @@ private bool DecodeBoolQ15(uint frequency) // assert(dif >> (DecoderWindowsSize - 16) < r); // assert(32768U <= r); - v = ((range >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v = (range >> 8) * (frequency >> Av1Distribution.ProbabilityShift) >> 7 - Av1Distribution.ProbabilityShift; v += Av1Distribution.ProbabilityMinimum; - vw = v << (DecoderWindowsSize - 16); + vw = v << DecoderWindowsSize - 16; ret = true; newRange = v; if (dif >= vw) @@ -118,17 +118,17 @@ private int DecodeIntegerQ15(Av1Distribution distribution) uint r = this.range; int n = distribution.NumberOfSymbols - 1; - DebugGuard.MustBeLessThan(dif >> (DecoderWindowsSize - 16), r, nameof(r)); + DebugGuard.MustBeLessThan(dif >> DecoderWindowsSize - 16, r, nameof(r)); DebugGuard.IsTrue(distribution[n] == 0, "Last value in probability array needs to be zero."); DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); DebugGuard.MustBeGreaterThanOrEqualTo(7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift, 0, nameof(Av1Distribution.CdfShift)); - c = dif >> (DecoderWindowsSize - 16); + c = dif >> DecoderWindowsSize - 16; v = r; ret = -1; do { u = v; - v = ((r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift); + v = (r >> 8) * (distribution[++ret] >> Av1Distribution.ProbabilityShift) >> 7 - Av1Distribution.ProbabilityShift - Av1Distribution.CdfShift; v += (uint)(Av1Distribution.ProbabilityMinimum * (n - ret)); } while (c < v); @@ -136,7 +136,7 @@ private int DecodeIntegerQ15(Av1Distribution distribution) DebugGuard.MustBeLessThan(v, u, nameof(v)); DebugGuard.MustBeLessThanOrEqualTo(u, r, nameof(u)); r = u - v; - dif -= v << (DecoderWindowsSize - 16); + dif -= v << DecoderWindowsSize - 16; this.Normalize(dif, r); return ret; } @@ -156,7 +156,7 @@ private void Normalize(uint dif, uint rng) /*d bits in dec->dif are consumed.*/ this.count -= d; /*This is equivalent to shifting in 1's instead of 0's.*/ - this.difference = ((dif + 1) << d) - 1; + this.difference = (dif + 1 << d) - 1; this.range = rng << d; if (this.count < 0) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs similarity index 86% rename from src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs rename to src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs index 8765561800..98c9d60084 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SymbolWriter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolWriter.cs @@ -4,7 +4,7 @@ using System.Buffers; using SixLabors.ImageSharp.Memory; -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; internal class Av1SymbolWriter : IDisposable { @@ -20,7 +20,7 @@ internal class Av1SymbolWriter : IDisposable public Av1SymbolWriter(Configuration configuration, int initialSize) { this.configuration = configuration; - this.memory = new AutoExpandingMemory(configuration, (initialSize + 1) >> 1); + this.memory = new AutoExpandingMemory(configuration, initialSize + 1 >> 1); } public void Dispose() => this.memory.Dispose(); @@ -40,7 +40,7 @@ public void WriteLiteral(uint value, int bitCount) const uint p = 0x4000U; // (0x7FFFFFU - (128 << 15) + 128) >> 8; for (int bit = bitCount - 1; bit >= 0; bit--) { - bool bitValue = ((value >> bit) & 0x1) > 0; + bool bitValue = (value >> bit & 0x1) > 0; this.EncodeBoolQ15(bitValue, p); } } @@ -54,15 +54,15 @@ public IMemoryOwner Exit() int pos = this.position; int s = 10; uint m = 0x3FFFU; - uint e = ((l + m) & ~m) | (m + 1); + uint e = l + m & ~m | m + 1; s += c; - Span buffer = this.memory.GetSpan(this.position + ((s + 7) >> 3)); + Span buffer = this.memory.GetSpan(this.position + (s + 7 >> 3)); if (s > 0) { - uint n = (1U << (c + 16)) - 1; + uint n = (1U << c + 16) - 1; do { - buffer[pos] = (ushort)(e >> (c + 16)); + buffer[pos] = (ushort)(e >> c + 16); pos++; e &= n; s -= 8; @@ -72,7 +72,7 @@ public IMemoryOwner Exit() while (s > 0); } - c = Math.Max((s + 7) >> 3, 0); + c = Math.Max(s + 7 >> 3, 0); IMemoryOwner output = this.configuration.MemoryAllocator.Allocate(pos + c); // Perform carry propagation. @@ -104,7 +104,7 @@ private void EncodeBoolQ15(bool val, uint frequency) l = this.low; r = this.rng; DebugGuard.MustBeGreaterThanOrEqualTo(r, 32768U, nameof(r)); - v = ((r >> 8) * (frequency >> Av1Distribution.ProbabilityShift)) >> (7 - Av1Distribution.ProbabilityShift); + v = (r >> 8) * (frequency >> Av1Distribution.ProbabilityShift) >> 7 - Av1Distribution.ProbabilityShift; v += Av1Distribution.ProbabilityMinimum; if (val) { @@ -145,17 +145,17 @@ private void EncodeIntegerQ15(uint lowFrequency, uint highFrequency, int symbol, { uint u; uint v; - u = (uint)((((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + - (Av1Distribution.ProbabilityMinimum * (n - (symbol - 1)))); - v = (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + - (Av1Distribution.ProbabilityMinimum * (n - symbol))); + u = (uint)(((r >> 8) * (lowFrequency >> Av1Distribution.ProbabilityShift) >> totalShift) + + Av1Distribution.ProbabilityMinimum * (n - (symbol - 1))); + v = (uint)(((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift) >> totalShift) + + Av1Distribution.ProbabilityMinimum * (n - symbol)); l += r - u; r = u - v; } else { - r -= (uint)((((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift)) >> totalShift) + - (Av1Distribution.ProbabilityMinimum * (n - symbol))); + r -= (uint)(((r >> 8) * (highFrequency >> Av1Distribution.ProbabilityShift) >> totalShift) + + Av1Distribution.ProbabilityMinimum * (n - symbol)); } this.Normalize(l, r); diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 751f610a5c..b1cd2ee61d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs similarity index 96% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs index 327932b24e..e4c710ad7a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/SymbolTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs @@ -3,15 +3,15 @@ using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class SymbolTests +public class EntropyTests { - private const int baseQIndex = 23; + private const int BaseQIndex = 23; [Fact] public void ReadRandomLiteral() @@ -194,7 +194,7 @@ public void RoundTripPartitionType() // Assign int ctx = 7; Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.None, Av1PartitionType.None]; @@ -231,7 +231,7 @@ public void RoundTripSplitOrHorizontalPartitionType(int blockSize, int context) { // Assign Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Horizontal, Av1PartitionType.Horizontal]; @@ -268,7 +268,7 @@ public void RoundTripSplitOrVerticalPartitionType(int blockSize, int context) { // Assign Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Av1PartitionType[] values = [ Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Split, Av1PartitionType.Split, Av1PartitionType.Vertical, Av1PartitionType.Vertical]; @@ -299,7 +299,7 @@ public void RoundTripUseIntraBlockCopy() // Assign bool[] values = [true, true, false, true, false, false, false]; Configuration configuration = Configuration.Default; - Av1SymbolEncoder encoder = new(configuration, 100 / 8, baseQIndex); + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); bool[] actuals = new bool[values.Length]; // Act From 4f96ba9854e8a8e9c580862dcd506e0f2aade5a3 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 19 Nov 2024 19:54:55 +0100 Subject: [PATCH 185/190] Make Av1FrameBuffer generic --- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 2 +- src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs | 15 ++++++++------- .../Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs | 4 ++-- .../Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs | 4 ++-- .../Pipeline/LoopFilter/Av1LoopFilterDecoder.cs | 4 ++-- .../Formats/Heif/Av1/Transform/Av1BlockDecoder.cs | 6 +++--- .../Formats/Heif/Av1/Av1ForwardTransformTests.cs | 2 +- .../Formats/Heif/Av1/Av1InverseTransformTests.cs | 3 ++- .../Formats/Heif/Av1/Av1TilingTests.cs | 2 +- 9 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index fbaca19a65..3b3663522e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -26,7 +26,7 @@ public Av1Decoder(Configuration configuration) public Av1FrameInfo? FrameInfo { get; private set; } - public Av1FrameBuffer? FrameBuffer { get; private set; } + public Av1FrameBuffer? FrameBuffer { get; private set; } public void Decode(Span buffer) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs index 301804bf65..f3d32187c0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs @@ -9,7 +9,8 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// /// Buffer for the pixels of a single frame. /// -internal class Av1FrameBuffer : IDisposable +internal class Av1FrameBuffer : IDisposable + where T : struct { private const int DecoderPaddingValue = 72; private const int PictureBufferYFlag = 1 << 0; @@ -67,17 +68,17 @@ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHea this.BufferCr = null; if ((this.BufferEnableMask & PictureBufferYFlag) != 0) { - this.BufferY = configuration.MemoryAllocator.Allocate2D(strideY * bitsPerPixel, heightY); + this.BufferY = configuration.MemoryAllocator.Allocate2D(strideY * bitsPerPixel, heightY); } if ((this.BufferEnableMask & PictureBufferCbFlag) != 0) { - this.BufferCb = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); + this.BufferCb = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); } if ((this.BufferEnableMask & PictureBufferCrFlag) != 0) { - this.BufferCr = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); + this.BufferCr = configuration.MemoryAllocator.Allocate2D(strideChroma * bitsPerPixel, heightChroma); } this.BitIncrementY = null; @@ -93,17 +94,17 @@ public Av1FrameBuffer(Configuration configuration, ObuSequenceHeader sequenceHea /// /// Gets the Y luma buffer. /// - public Buffer2D? BufferY { get; private set; } + public Buffer2D? BufferY { get; private set; } /// /// Gets the U chroma buffer. /// - public Buffer2D? BufferCb { get; private set; } + public Buffer2D? BufferCb { get; private set; } /// /// Gets the V chroma buffer. /// - public Buffer2D? BufferCr { get; private set; } + public Buffer2D? BufferCr { get; private set; } public Buffer2D? BitIncrementY { get; private set; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 8c1b68d414..811a51491e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -13,12 +13,12 @@ internal class Av1FrameDecoder : IAv1FrameDecoder private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; private readonly Av1FrameInfo frameInfo; - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameBuffer frameBuffer; private readonly Av1InverseQuantizer inverseQuantizer; private readonly Av1DeQuantizationContext deQuants; private readonly Av1BlockDecoder blockDecoder; - public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) + public Av1FrameDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs index 90529ab1fd..6bdd55ac54 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs @@ -7,9 +7,9 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; internal class Av1FrameEncoder { - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameBuffer frameBuffer; - public Av1FrameEncoder(Av1FrameBuffer frameBuffer) + public Av1FrameEncoder(Av1FrameBuffer frameBuffer) { this.frameBuffer = frameBuffer; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs index 9a895ea7e5..bf0c83c956 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs @@ -11,10 +11,10 @@ internal class Av1LoopFilterDecoder private readonly ObuSequenceHeader sequenceHeader; private readonly ObuFrameHeader frameHeader; private readonly Av1FrameInfo frameInfo; - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameBuffer frameBuffer; private readonly Av1LoopFilterContext loopFilterContext; - public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) + public Av1LoopFilterDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index fc10f30994..cfe3fa093c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -17,13 +17,13 @@ internal class Av1BlockDecoder private readonly Av1FrameInfo frameInfo; - private readonly Av1FrameBuffer frameBuffer; + private readonly Av1FrameBuffer frameBuffer; private readonly bool isLoopFilterEnabled; private readonly int[] currentCoefficientIndex; - public Av1BlockDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) + public Av1BlockDecoder(ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; @@ -221,7 +221,7 @@ public void DecodeBlock(Av1BlockModeInfo modeInfo, Point modeInfoPosition, Av1Bl } } - private static void DeriveBlockPointers(Av1FrameBuffer frameBuffer, int plane, int blockColumnInPixels, int blockRowInPixels, out Span blockReconstructionBuffer, out int reconstructionStride, int subX, int subY) + private static void DeriveBlockPointers(Av1FrameBuffer frameBuffer, int plane, int blockColumnInPixels, int blockRowInPixels, out Span blockReconstructionBuffer, out int reconstructionStride, int subX, int subY) { int blockOffset; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 53667c140a..488bc8726d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -67,7 +67,7 @@ public void ConfigTest(int txSize, int txType, int _) if (actual.EndsWith("Identity", StringComparison.InvariantCulture)) { - actual = "Vertical" + actual.Replace("Identity", ""); + actual = "Vertical" + actual.Replace("Identity", string.Empty); } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs index c84531a76a..a5983ef645 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Globalization; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform.Forward; @@ -485,7 +486,7 @@ private static int GetMaximumError(Span expected, Span actual) int count = Math.Min(expected.Length, 32); for (int ni = 0; ni < count; ++ni) { - maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(Convert.ToInt32(actual[ni]) - Convert.ToInt32(expected[ni]))); + maximumErrorInTest = Math.Max(maximumErrorInTest, Math.Abs(Convert.ToInt32(actual[ni], CultureInfo.InvariantCulture) - Convert.ToInt32(expected[ni], CultureInfo.InvariantCulture))); } return maximumErrorInTest; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index 46c39e50d6..fe756dd7c3 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -24,7 +24,7 @@ public void ReadFirstTile(string filename, int dataOffset, int dataSize, int til IAv1TileReader stub = new Av1TileDecoderStub(); ObuReader obuReader = new(); obuReader.ReadAll(ref bitStreamReader, dataSize, () => stub); - Av1FrameBuffer frameBuffer = new(Configuration.Default, obuReader.SequenceHeader, Av1ColorFormat.Yuv444, false); + Av1FrameBuffer frameBuffer = new(Configuration.Default, obuReader.SequenceHeader, Av1ColorFormat.Yuv444, false); Av1FrameInfo frameInfo = new(obuReader.SequenceHeader); Av1FrameDecoderStub frameDecoder = new(); Av1TileReader tileReader = new(Configuration.Default, obuReader.SequenceHeader, obuReader.FrameHeader, frameDecoder); From 682b05c5d0816b15adadb845f47d28da74eb5dc6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 20 Nov 2024 20:43:43 +0100 Subject: [PATCH 186/190] Fill some more data structures --- .../Formats/Heif/Av1/Av1Constants.cs | 15 ++ .../Heif/Av1/Tiling/Av1BlockGeometry.cs | 33 +++- .../Formats/Heif/Av1/Tiling/Av1BlockStruct.cs | 11 ++ .../Av1/Tiling/Av1EntropyCodingContext.cs | 4 + .../Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs | 17 +- .../Heif/Av1/Tiling/Av1NeighborArrayUnit.cs | 163 +++++++++++++++++- .../Av1/Tiling/Av1PictureParentControlSet.cs | 2 + .../Heif/Av1/Tiling/Av1TransformUnit.cs | 13 ++ 8 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs index 089415d25c..dc0e9e68b2 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Constants.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -187,4 +188,18 @@ internal static class Av1Constants // Number of transform sizes that use extended transforms. public const int ExtendedTransformCount = 4; + + public const int MaxVarTransform = 2; + + /// + /// Maximum number of transform blocks per depth + /// + public const int MaxTransformBlockCount = 16; + + /// + /// Number of items in the enumeration. + /// + public const int PlaneTypeCount = 2; + + public const int MaxTransformUnitCount = 16; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs index 104099eea8..a6e24821d6 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockGeometry.cs @@ -9,16 +9,47 @@ internal partial class Av1TileWriter { internal class Av1BlockGeometry { + public Av1BlockGeometry() + { + this.TransformOrigin = new Point[Av1Constants.MaxVarTransform + 1][]; + for (int i = 0; i < this.TransformOrigin.Length; i++) + { + this.TransformOrigin[i] = new Point[Av1Constants.MaxTransformBlockCount]; + } + } + public Av1BlockSize BlockSize { get; internal set; } + public Av1BlockSize BlockSizeUv { get; internal set; } + + /// + /// Gets or sets the Origin point from lop left of the superblock. + /// public Point Origin { get; internal set; } public bool HasUv { get; internal set; } + /// + /// Gets or sets the blocks width. + /// public int BlockWidth { get; internal set; } + /// + /// Gets or sets the blocks height. + /// public int BlockHeight { get; internal set; } - public required Av1TransformSize[] TransformSize { get; internal set; } + public int[] TransformBlockCount { get; } = new int[Av1Constants.MaxVarTransform + 1]; + + public Av1TransformSize[] TransformSize { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; + + public Av1TransformSize[] TransformSizeUv { get; } = new Av1TransformSize[Av1Constants.MaxVarTransform + 1]; + + public Point[][] TransformOrigin { get; private set; } + + /// + /// Gets or sets the block index in md scan. + /// + public int BlockIndex { get; set; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs index 53ee7ef8bb..44bceded44 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockStruct.cs @@ -5,4 +5,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; internal class Av1BlockStruct { + public Av1TransformUnit[] TransformBlocks { get; } = new Av1TransformUnit[Av1Constants.MaxTransformUnitCount]; + + public required Av1MacroBlockD av1xd { get; set; } + + public int MdScanIndex { get; set; } + + public int QIndex { get; set; } + + public int SegmentId { get; set; } + + public Av1FilterIntraMode FilterIntraMode { get; set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs index 05c501d4d9..ead8f44623 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1EntropyCodingContext.cs @@ -10,5 +10,9 @@ internal class Av1EntropyCodingContext public required Av1MacroBlockModeInfo MacroBlockModeInfo { get; internal set; } public Point SuperblockOrigin { get; internal set; } + + public int CodedAreaSuperblock { get; internal set; } + + public int CodedAreaSuperblockUv { get; internal set; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs index da1469173b..62dd3972bb 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockD.cs @@ -3,20 +3,17 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1MacroBlockD { - internal class Av1MacroBlockD - { - public required Av1BlockModeInfo ModeInfo { get; internal set; } + public required Av1BlockModeInfo ModeInfo { get; internal set; } - public required Av1TileInfo Tile { get; internal set; } + public required Av1TileInfo Tile { get; internal set; } - public bool IsUpAvailable { get; } + public bool IsUpAvailable { get; } - public bool IsLeftAvailable { get; } + public bool IsLeftAvailable { get; } - public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; } + public Av1MacroBlockModeInfo? AboveMacroBlock { get; internal set; } - public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; } - } + public Av1MacroBlockModeInfo? LeftMacroBlock { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs index 0aa6a68b09..7607d1bffd 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -1,11 +1,168 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Numerics; +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1NeighborArrayUnit + where T : struct, IMinMaxValue { - internal class Av1NeighborArrayUnit + public static readonly T InvalidNeighborData = T.MaxValue; + + [Flags] + public enum UnitMask + { + Left = 1, + Top = 2, + TopLeft = 4, + } + + private readonly T[] left; + private readonly T[] top; + private readonly T[] topLeft; + + public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize) + { + this.left = new T[leftSize]; + this.top = new T[topSize]; + this.topLeft = new T[topLeftSize]; + } + + public Span Left => this.left; + + public Span Top => this.top; + + public Span TopLeft => this.topLeft; + + public required int GranularityNormalLog2 { get; set; } + + public required int GranularityTopLeftLog2 { get; set; } + + public int UnitSize { get; private set; } + + public int GetLeftIndex(Point loc) => loc.Y >> this.GranularityNormalLog2; + + public int GetTopIndex(Point loc) => loc.X >> this.GranularityNormalLog2; + + public int GetTopLeftIndex(Point loc) + => this.left.Length + (loc.X >> this.GranularityTopLeftLog2) - (loc.Y >> this.GranularityTopLeftLog2); + + public void UnitModeWrite(Span value, Point origin, Size blockSize, UnitMask mask) { + int idx, j; + + int count; + int na_offset; + int na_unit_size; + + na_unit_size = this.UnitSize; + + if ((mask & UnitMask.Top) == UnitMask.Top) + { + // Top Neighbor Array + // ----------12345678--------------------- + // ^ ^ + // | | + // | | + // xxxxxxxx + // x x + // x x + // 12345678 + // + // The top neighbor array is updated with the samples from the + // bottom row of the source block + // + // Index = org_x + na_offset = this.GetTopIndex(origin); + + ref T dst_ptr = ref this.Top[na_offset * na_unit_size]; + + count = blockSize.Width >> this.GranularityNormalLog2; + + for (idx = 0; idx < count; ++idx) + { + /* svt_memcpy less that 10 bytes*/ + for (j = 0; j < na_unit_size; ++j) + { + dst_ptr = value[j]; + dst_ptr = Unsafe.Add(ref dst_ptr, 1); + } + } + } + + if ((mask & UnitMask.Left) == UnitMask.Left) + { + // Left Neighbor Array + // + // | + // | + // 1 xxxxxxx1 + // 2 <---- x 2 + // 3 <---- x 3 + // 4 xxxxxxx4 + // | + // | + // + // The left neighbor array is updated with the samples from the + // right column of the source block + // + // Index = org_y + na_offset = this.GetLeftIndex(origin); + + ref T dst_ptr = ref this.Left[na_offset * na_unit_size]; + + count = blockSize.Height >> this.GranularityNormalLog2; + + for (idx = 0; idx < count; ++idx) + { + /* svt_memcpy less that 10 bytes*/ + for (j = 0; j < na_unit_size; ++j) + { + dst_ptr = value[j]; + dst_ptr = Unsafe.Add(ref dst_ptr, 1); + } + } + } + + if ((mask & UnitMask.TopLeft) == UnitMask.TopLeft) + { + // Top-left Neighbor Array + // + // 4-5--6--7------------ + // 3 \ \ + // 2 \ \ + // 1 \ \ + // |\ xxxxxx7 + // | \ x 6 + // | \ x 5 + // | \1x2x3x4 + // | + // + // The top-left neighbor array is updated with the reversed samples + // from the right column and bottom row of the source block + // + // Index = org_x - org_y + Point topLeft = origin; + topLeft.Offset(0, blockSize.Height - 1); + na_offset = this.GetTopLeftIndex(topLeft); + + // Copy bottom-row + right-column + // *Note - start from the bottom-left corner + ref T dst_ptr = ref this.TopLeft[na_offset * na_unit_size]; + + count = ((blockSize.Width + blockSize.Height) >> this.GranularityTopLeftLog2) - 1; + + for (idx = 0; idx < count; ++idx) + { + /* svt_memcpy less that 10 bytes*/ + for (j = 0; j < na_unit_size; ++j) + { + dst_ptr = value[j]; + dst_ptr = Unsafe.Add(ref dst_ptr, 1); + } + } + } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index 6bbc651eac..bf79f5a5c3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -16,5 +16,7 @@ internal class Av1PictureParentControlSet public required int[] PreviousQIndex { get; internal set; } public int PaletteLevel { get; internal set; } + public int AlignedWidth { get; internal set; } + public int AlignedHeight { get; internal set; } } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs new file mode 100644 index 0000000000..6670f99eda --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TransformUnit.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal class Av1TransformUnit +{ + public ushort[] NzCoefficientCount { get; } = new ushort[3]; + + public Av1TransformType[] TransformType { get; } = new Av1TransformType[Av1Constants.PlaneTypeCount]; +} From 5aca40718b44f7e236b4e363b8bd2d245121a347 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 20 Nov 2024 21:21:14 +0100 Subject: [PATCH 187/190] Rename of test classes --- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 38 +++++++++---------- ...ansformer.cs => Av1EchoTestTransformer.cs} | 2 +- .../{EntropyTests.cs => Av1EntropyTests.cs} | 2 +- .../Heif/Av1/Av1ForwardTransformTests.cs | 14 +++---- .../Heif/Av1/Av1InverseTransformTests.cs | 10 ++--- 5 files changed, 33 insertions(+), 33 deletions(-) rename tests/ImageSharp.Tests/Formats/Heif/Av1/{EchoTestTransformer.cs => Av1EchoTestTransformer.cs} (83%) rename tests/ImageSharp.Tests/Formats/Heif/Av1/{EntropyTests.cs => Av1EntropyTests.cs} (99%) diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index f28f161d86..1e79671dee 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -122,12 +122,12 @@ public void WriteSplitOrVertical(Av1PartitionType partitionType, Av1BlockSize bl public int WriteCoefficients( Av1TransformSize transformSize, Av1TransformType transformType, - uint txb_index, // TODO: Doesn't seem to be used, remove. - Av1PredictionMode intraLumaDir, - Span coeff_buffer_ptr, + int txbIndex, // TODO: Doesn't seem to be used, remove. + Av1PredictionMode intraDirection, + Span coeffBuffer, Av1ComponentType componentType, - short txb_skip_ctx, - short dc_sign_ctx, + short transformBlockSkipContext, + short dcSignContext, ushort eob, bool useReducedTransformSet, int baseQIndex, @@ -151,17 +151,17 @@ public int WriteCoefficients( Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); bool hasEndOfBlock = eob != 0; - this.WriteSkip(!hasEndOfBlock, txb_skip_ctx); + this.WriteSkip(!hasEndOfBlock, transformBlockSkipContext); if (eob == 0) { return 0; } - InitializeLevels(coeff_buffer_ptr, width, height, levels); + InitializeLevels(coeffBuffer, width, height, levels); if (componentType == Av1ComponentType.Luminance) { - this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraLumaDir); + this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraDirection); } short endOfBlockPosition = GetEndOfBlockPosition(eob, out int eob_extra); @@ -186,7 +186,7 @@ public int WriteCoefficients( for (c = eob - 1; c >= 0; --c) { short pos = scan[c]; - int v = coeff_buffer_ptr[pos]; + int v = coeffBuffer[pos]; short coeff_ctx = coeff_contexts[pos]; int level = Math.Abs(v); @@ -222,7 +222,7 @@ public int WriteCoefficients( for (c = 0; c < eob; ++c) { short pos = scan[c]; - int v = coeff_buffer_ptr[pos]; + int v = coeffBuffer[pos]; int level = Math.Abs(v); cul_level += level; @@ -231,7 +231,7 @@ public int WriteCoefficients( { if (c == 0) { - w.WriteSymbol((int)sign, this.dcSign[(int)componentType][dc_sign_ctx]); + w.WriteSymbol((int)sign, this.dcSign[(int)componentType][dcSignContext]); } else { @@ -248,22 +248,22 @@ public int WriteCoefficients( cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); // DC value - SetDcSign(ref cul_level, coeff_buffer_ptr[0]); + SetDcSign(ref cul_level, coeffBuffer[0]); return cul_level; } /// /// SVT: set_dc_sign /// - private static void SetDcSign(ref int cul_level, int dc_val) + private static void SetDcSign(ref int culLevel, int dcValue) { - if (dc_val < 0) + if (dcValue < 0) { - cul_level |= 1 << Av1Constants.CoefficientContextBitCount; + culLevel |= 1 << Av1Constants.CoefficientContextBitCount; } - else if (dc_val > 0) + else if (dcValue > 0) { - cul_level += 2 << Av1Constants.CoefficientContextBitCount; + culLevel += 2 << Av1Constants.CoefficientContextBitCount; } } @@ -432,8 +432,8 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int /// /// SVT: set_levels from EbCommonUtils.h /// - private static Span SetLevels(Span levels_buf, int width) - => levels_buf.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal)); + private static Span SetLevels(Span levelsBuffer, int width) + => levelsBuffer.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal)); private void WriteSkip(bool hasEndOfBlock, int context) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EchoTestTransformer.cs similarity index 83% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EchoTestTransformer.cs index 79c8f0c504..e47a77ad27 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/EchoTestTransformer.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EchoTestTransformer.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; -internal class EchoTestTransformer : IAv1Forward1dTransformer +internal class Av1EchoTestTransformer : IAv1Forward1dTransformer { public void Transform(Span input, Span output, int cosBit, Span stageRange) => input.CopyTo(output); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs similarity index 99% rename from tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs rename to tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index e4c710ad7a..37c4aa4a61 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; [Trait("Format", "Avif")] -public class EntropyTests +public class Av1EntropyTests { private const int BaseQIndex = 23; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs index 488bc8726d..4300b642b6 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ForwardTransformTests.cs @@ -85,7 +85,7 @@ public void ScaleFactorTest(int txSize, int txType, int _) Array.Fill(input, 1); int[] actual = new int[64 * 64]; Av1Transform2dFlipConfiguration config = new(transformType, transformSize); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); int width = transformSize.GetWidth(); int height = transformSize.GetHeight(); int blockSize = width * height; @@ -123,7 +123,7 @@ public void FlipNothingTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(false, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( @@ -157,7 +157,7 @@ public void FlipHorizontalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(false, true); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( @@ -191,7 +191,7 @@ public void FlipVerticalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(true, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( @@ -225,7 +225,7 @@ public void FlipHorizontalAndVerticalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(true, true); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( @@ -261,7 +261,7 @@ public void NonSquareTransformSizeLandscapeTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size8x4); config.SetFlip(true, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( @@ -305,7 +305,7 @@ public void NonSquareTransformSizePortraitTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x8); config.SetFlip(true, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1ForwardTransformer.Transform2d( diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs index a5983ef645..ce409789fa 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs @@ -74,7 +74,7 @@ public void AccuracyOfIdentity1dTransformSize64Test() [Fact] public void AccuracyOfEchoTransformSize4Test() - => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 0, new EchoTestTransformer(), new EchoTestTransformer()); + => AssertAccuracy1d(Av1TransformType.Identity, Av1TransformSize.Size4x4, 0, new Av1EchoTestTransformer(), new Av1EchoTestTransformer()); [Fact] public void FlipNothingTest() @@ -96,7 +96,7 @@ public void FlipNothingTest() config.GenerateStageRange(8); config.SetFlip(false, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1Inverse2dTransformer.Transform2dAdd( @@ -132,7 +132,7 @@ public void FlipHorizontalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(false, true); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1Inverse2dTransformer.Transform2dAdd( @@ -168,7 +168,7 @@ public void FlipVerticalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(true, false); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1Inverse2dTransformer.Transform2dAdd( @@ -204,7 +204,7 @@ public void FlipHorizontalAndVerticalTest() Av1Transform2dFlipConfiguration config = new(Av1TransformType.Identity, Av1TransformSize.Size4x4); config.SetFlip(true, true); config.SetShift(0, 0, 0); - IAv1Forward1dTransformer transformer = new EchoTestTransformer(); + IAv1Forward1dTransformer transformer = new Av1EchoTestTransformer(); // Act Av1Inverse2dTransformer.Transform2dAdd( From fa886a4d25be86f7a1dbfcd0f2f86b1bf617ee37 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 23 Nov 2024 11:44:55 +0100 Subject: [PATCH 188/190] Test case for coefficient round trip --- .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 182 ++++++++++++++++++ .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 13 +- .../Av1/Tiling/Av1BlockModeInfoEncoder.cs | 25 ++- .../Formats/Heif/Av1/Tiling/Av1Common.cs | 15 +- .../Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs | 11 +- .../Heif/Av1/Tiling/Av1NeighborArrayUnit.cs | 16 +- .../Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs | 7 +- .../Heif/Av1/Tiling/Av1PictureControlSet.cs | 17 +- .../Av1/Tiling/Av1PictureParentControlSet.cs | 19 +- .../Heif/Av1/Tiling/Av1SequenceControlSet.cs | 9 +- .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 165 +--------------- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 57 ++++++ 12 files changed, 303 insertions(+), 233 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index 78dd1efe34..eeaafe87ed 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -206,6 +206,88 @@ public int ReadChromaFromLumaAlphaV(int jointSignPlus1) return r.ReadSymbol(this.chromeForLumaAlpha[context]); } + /// + /// 5.11.39. Coefficients syntax. + /// + /// + /// The implementation is taken from SVT-AV1 library, which deviates from the code flow in the specification. + /// + public int ReadCoefficients( + Av1BlockModeInfo modeInfo, + Point blockPosition, + int[] aboveContexts, + int[] leftContexts, + int aboveOffset, + int leftOffset, + int plane, + int blocksWide, + int blocksHigh, + Av1TransformBlockContext transformBlockContext, + Av1TransformSize transformSize, + bool isLossless, + bool useReducedTransformSet, + Av1TransformInfo transformInfo, + int modeBlocksToRightEdge, + int modeBlocksToBottomEdge, + Span coefficientBuffer) + { + int width = transformSize.GetWidth(); + int height = transformSize.GetHeight(); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); + Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); + int culLevel = 0; + + byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; + Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; + + bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); + int bwl = transformSize.GetBlockWidthLog2(); + int endOfBlock; + if (allZero) + { + if (plane == 0) + { + transformInfo.Type = Av1TransformType.DctDct; + transformInfo.CodeBlockFlag = false; + } + + this.UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); + return 0; + } + + transformInfo.Type = ComputeTransformType(planeType, modeInfo, isLossless, transformSize, transformInfo, useReducedTransformSet); + Av1TransformClass transformClass = transformInfo.Type.ToClass(); + Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); + ReadOnlySpan scan = scanOrder.Scan; + + endOfBlock = this.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); + if (endOfBlock > 1) + { + Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + } + + this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); + if (endOfBlock > 1) + { + if (transformClass == Av1TransformClass.Class2D) + { + this.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + this.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); + } + else + { + this.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); + } + } + + DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); + culLevel = this.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); + this.UpdateCoefficientContext(modeInfo, aboveContexts, leftContexts, blocksWide, blocksHigh, transformSize, blockPosition, aboveOffset, leftOffset, culLevel, modeBlocksToRightEdge, modeBlocksToBottomEdge); + + transformInfo.CodeBlockFlag = true; + return endOfBlock; + } + public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformClass transformClass, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int endOfBlockExtra = 0; @@ -581,6 +663,106 @@ private static void SetDcSign(ref int culLevel, int dcValue) } } + + private void UpdateCoefficientContext( + Av1BlockModeInfo modeInfo, + int[] aboveContexts, + int[] leftContexts, + int blocksWide, + int blocksHigh, + Av1TransformSize transformSize, + Point blockPosition, + int aboveOffset, + int leftOffset, + int culLevel, + int modeBlockToRightEdge, + int modeBlockToBottomEdge) + { + int transformSizeWide = transformSize.Get4x4WideCount(); + int transformSizeHigh = transformSize.Get4x4HighCount(); + + if (modeBlockToRightEdge < 0) + { + int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset); + Array.Fill(aboveContexts, culLevel, 0, aboveContextCount); + Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount); + } + else + { + Array.Fill(aboveContexts, culLevel, 0, transformSizeWide); + } + + if (modeBlockToBottomEdge < 0) + { + int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); + Array.Fill(leftContexts, culLevel, 0, leftContextCount); + Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); + } + else + { + Array.Fill(leftContexts, culLevel, 0, transformSizeHigh); + } + } + + private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1BlockModeInfo modeInfo, bool isLossless, Av1TransformSize transformSize, Av1TransformInfo transformInfo, bool useReducedTransformSet) + { + Av1TransformType transformType = Av1TransformType.DctDct; + if (isLossless || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) + { + transformType = Av1TransformType.DctDct; + } + else + { + if (planeType == Av1PlaneType.Y) + { + transformType = transformInfo.Type; + } + else + { + // In intra mode, uv planes don't share the same prediction mode as y + // plane, so the tx_type should not be shared + transformType = ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); + } + } + + Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, useReducedTransformSet); + if (!transformType.IsExtendedSetUsed(transformSetType)) + { + transformType = Av1TransformType.DctDct; + } + + return transformType; + } + + private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) + { + Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); + + if (squareUpSize >= Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (useReducedSet) + { + return Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize squareSize = transformSize.GetSquareSize(); + return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + + private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) + { + Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; + if (mode == Av1PredictionMode.UvChromaFromLuma) + { + mode = Av1PredictionMode.DC; + } + + return mode.ToTransformType(); + } + internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) { Av1Distribution input = inputs[context]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 1e79671dee..417c58a5a5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -415,13 +415,13 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int ref byte ls = ref levels[0]; Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); - Unsafe.InitBlock(ref levels[stride * height], 0, (uint)(Av1Constants.TransformPadBottom * stride + Av1Constants.TransformPadEnd)); + Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd)); - for (int i = 0; i < height; i++) + for (int y = 0; y < height; y++) { - for (int j = 0; j < width; j++) + for (int x = 0; x < width; x++) { - ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[i * width + j]), 0, byte.MaxValue); + ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(y * width) + x]), 0, byte.MaxValue); ls = ref Unsafe.Add(ref ls, 1); } @@ -433,7 +433,10 @@ private static void InitializeLevels(Span coefficientBuffer, int width, int /// SVT: set_levels from EbCommonUtils.h /// private static Span SetLevels(Span levelsBuffer, int width) - => levelsBuffer.Slice(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal)); + { + int stride = width + Av1Constants.TransformPadHorizontal; + return levelsBuffer[(Av1Constants.TransformPadTop * stride)..]; + } private void WriteSkip(bool hasEndOfBlock, int context) { diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs index a8113a7ece..20989f3cef 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfoEncoder.cs @@ -1,30 +1,27 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1BlockModeInfoEncoder { - internal class Av1BlockModeInfoEncoder - { - public Av1BlockSize BlockSize { get; } + public Av1BlockSize BlockSize { get; } - public Av1PredictionMode PredictionMode { get; } + public Av1PredictionMode PredictionMode { get; } - public Av1PartitionType PartitionType { get; } + public Av1PartitionType PartitionType { get; } - public Av1PredictionMode UvPredictionMode { get; } + public Av1PredictionMode UvPredictionMode { get; } - public bool Skip { get; } = true; + public bool Skip { get; } = true; - public bool SkipMode { get; } = true; + public bool SkipMode { get; } = true; - public bool UseIntraBlockCopy { get; } = true; + public bool UseIntraBlockCopy { get; } = true; - public int SegmentId { get; } + public int SegmentId { get; } - public int TransformDepth { get; internal set; } - } + public int TransformDepth { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs index c3c29deae4..8ec17e55e7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1Common.cs @@ -5,18 +5,15 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1Common { - internal class Av1Common - { - public int ModeInfoRowCount { get; internal set; } + public int ModeInfoRowCount { get; internal set; } - public int ModeInfoColumnCount { get; internal set; } + public int ModeInfoColumnCount { get; internal set; } - public int ModeInfoStride { get; internal set; } + public int ModeInfoStride { get; internal set; } - public required ObuFrameSize FrameSize { get; internal set; } + public required ObuFrameSize FrameSize { get; internal set; } - public required ObuTileGroupHeader TilesInfo { get; internal set; } - } + public required ObuTileGroupHeader TilesInfo { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs index 8fb96f4d2d..e1823931d9 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1MacroBlockModeInfo.cs @@ -3,14 +3,11 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1MacroBlockModeInfo { - internal class Av1MacroBlockModeInfo - { - public required Av1BlockModeInfoEncoder Block { get; internal set; } + public required Av1BlockModeInfoEncoder Block { get; internal set; } - public required Av1PaletteLumaModeInfo Palette { get; internal set; } + public required Av1PaletteLumaModeInfo Palette { get; internal set; } - public int CdefStrength { get; internal set; } - } + public int CdefStrength { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs index 7607d1bffd..215dc2ad02 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1NeighborArrayUnit.cs @@ -11,14 +11,6 @@ internal class Av1NeighborArrayUnit { public static readonly T InvalidNeighborData = T.MaxValue; - [Flags] - public enum UnitMask - { - Left = 1, - Top = 2, - TopLeft = 4, - } - private readonly T[] left; private readonly T[] top; private readonly T[] topLeft; @@ -30,6 +22,14 @@ public Av1NeighborArrayUnit(int leftSize, int topSize, int topLeftSize) this.topLeft = new T[topLeftSize]; } + [Flags] + public enum UnitMask + { + Left = 1, + Top = 2, + TopLeft = 4, + } + public Span Left => this.left; public Span Top => this.top; diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs index bb40ca53a8..aa1e1e94f7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PaletteLumaModeInfo.cs @@ -1,11 +1,8 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PaletteLumaModeInfo { - internal class Av1PaletteLumaModeInfo - { - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs index 851f71dae3..ebfe36faf3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureControlSet.cs @@ -3,20 +3,17 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PictureControlSet { - internal class Av1PictureControlSet - { - public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] luma_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] cr_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } + public required Av1NeighborArrayUnit[] cb_dc_sign_level_coeff_na { get; internal set; } - public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } + public required Av1NeighborArrayUnit[] txfm_context_array { get; internal set; } - public required Av1SequenceControlSet Sequence { get; internal set; } + public required Av1SequenceControlSet Sequence { get; internal set; } - public required Av1PictureParentControlSet Parent { get; internal set; } - } + public required Av1PictureParentControlSet Parent { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs index bf79f5a5c3..b1a183048d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1PictureParentControlSet.cs @@ -5,18 +5,17 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1PictureParentControlSet { - internal class Av1PictureParentControlSet - { - public required Av1Common Common { get; internal set; } + public required Av1Common Common { get; internal set; } - public required ObuFrameHeader FrameHeader { get; internal set; } + public required ObuFrameHeader FrameHeader { get; internal set; } - public required int[] PreviousQIndex { get; internal set; } + public required int[] PreviousQIndex { get; internal set; } - public int PaletteLevel { get; internal set; } - public int AlignedWidth { get; internal set; } - public int AlignedHeight { get; internal set; } - } + public int PaletteLevel { get; internal set; } + + public int AlignedWidth { get; internal set; } + + public int AlignedHeight { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs index 1ad59b31ab..39f7c2b197 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1SequenceControlSet.cs @@ -1,14 +1,11 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -internal partial class Av1TileWriter +internal class Av1SequenceControlSet { - internal class Av1SequenceControlSet - { - public required ObuSequenceHeader SequenceHeader { get; internal set; } - } + public required ObuSequenceHeader SequenceHeader { get; internal set; } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index b1cd2ee61d..0001ecc04a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -491,22 +491,6 @@ public static bool HasChroma(ObuSequenceHeader sequenceHeader, Point modeInfoLoc return hasChroma; } - private Av1TransformSize GetSize(int plane, object transformSize) => throw new NotImplementedException(); - - /// - /// 5.11.38. Get plane residual size function. - /// The GetPlaneResidualSize returns the size of a residual block for the specified plane. (The residual block will always - /// have width and height at least equal to 4.) - /// - private Av1BlockSize GetPlaneResidualSize(Av1BlockSize sizeChunk, int plane) - { - bool subsamplingX = this.SequenceHeader.ColorConfig.SubSamplingX; - bool subsamplingY = this.SequenceHeader.ColorConfig.SubSamplingY; - bool subX = plane > 0 && subsamplingX; - bool subY = plane > 0 && subsamplingY; - return sizeChunk.GetSubsampled(subX, subY); - } - /// /// 5.11.35. Transform block syntax. /// @@ -563,152 +547,15 @@ private int ParseCoefficients(ref Av1SymbolDecoder reader, Av1PartitionInfo part int height = transformSize.GetHeight(); Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + ((int)transformSize.GetSquareUpSize() + 1)) >> 1); Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); - int culLevel = 0; - - byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; - Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; - - bool allZero = reader.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); - int bwl = transformSize.GetBlockWidthLog2(); - int endOfBlock; - if (allZero) - { - if (plane == 0) - { - transformInfo.Type = Av1TransformType.DctDct; - transformInfo.CodeBlockFlag = false; - } - - this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); - return 0; - } - - transformInfo.Type = this.ComputeTransformType(planeType, partitionInfo, transformSize, transformInfo); - Av1TransformClass transformClass = transformInfo.Type.ToClass(); - Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformInfo.Type); - ReadOnlySpan scan = scanOrder.Scan; - - endOfBlock = reader.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); - if (endOfBlock > 1) - { - Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); - } - - reader.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); - if (endOfBlock > 1) - { - if (transformClass == Av1TransformClass.Class2D) - { - reader.ReadCoefficientsReverse2d(transformSize, 1, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - reader.ReadCoefficientsReverse(transformSize, transformClass, 0, 0, scan, bwl, levels, transformSizeContext, planeType); - } - else - { - reader.ReadCoefficientsReverse(transformSize, transformClass, 0, endOfBlock - 1 - 1, scan, bwl, levels, transformSizeContext, planeType); - } - } - - DebugGuard.MustBeGreaterThan(scan.Length, 0, nameof(scan)); - culLevel = reader.ReadCoefficientsDc(coefficientBuffer, endOfBlock, scan, bwl, levels, transformBlockContext.DcSignContext, planeType); - this.UpdateCoefficientContext(plane, partitionInfo, transformSize, blockRow, blockColumn, aboveOffset, leftOffset, culLevel); - - transformInfo.CodeBlockFlag = true; - return endOfBlock; - } - - private void UpdateCoefficientContext(int plane, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, int blockRow, int blockColumn, int aboveOffset, int leftOffset, int culLevel) - { + Point blockPosition = new(blockColumn, blockRow); + bool isLossless = this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId]; bool subX = this.SequenceHeader.ColorConfig.SubSamplingX; bool subY = this.SequenceHeader.ColorConfig.SubSamplingY; - int[] aboveContexts = this.aboveNeighborContext.GetContext(plane); - int[] leftContexts = this.leftNeighborContext.GetContext(plane); - int transformSizeWide = transformSize.Get4x4WideCount(); - int transformSizeHigh = transformSize.Get4x4HighCount(); - - if (partitionInfo.ModeBlockToRightEdge < 0) - { - Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); - int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); - int aboveContextCount = Math.Min(transformSizeWide, blocksWide - aboveOffset); - Array.Fill(aboveContexts, culLevel, 0, aboveContextCount); - Array.Fill(aboveContexts, 0, aboveContextCount, transformSizeWide - aboveContextCount); - } - else - { - Array.Fill(aboveContexts, culLevel, 0, transformSizeWide); - } - - if (partitionInfo.ModeBlockToBottomEdge < 0) - { - Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); - int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); - int leftContextCount = Math.Min(transformSizeHigh, blocksHigh - leftOffset); - Array.Fill(leftContexts, culLevel, 0, leftContextCount); - Array.Fill(leftContexts, 0, leftContextCount, transformSizeWide - leftContextCount); - } - else - { - Array.Fill(leftContexts, culLevel, 0, transformSizeHigh); - } - } - - private Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1PartitionInfo partitionInfo, Av1TransformSize transformSize, Av1TransformInfo transformInfo) - { - Av1TransformType transformType = Av1TransformType.DctDct; - if (this.FrameHeader.LosslessArray[partitionInfo.ModeInfo.SegmentId] || transformSize.GetSquareUpSize() > Av1TransformSize.Size32x32) - { - transformType = Av1TransformType.DctDct; - } - else - { - if (planeType == Av1PlaneType.Y) - { - transformType = transformInfo.Type; - } - else - { - // In intra mode, uv planes don't share the same prediction mode as y - // plane, so the tx_type should not be shared - transformType = ConvertIntraModeToTransformType(partitionInfo.ModeInfo, Av1PlaneType.Uv); - } - } - - Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, this.FrameHeader.UseReducedTransformSet); - if (!transformType.IsExtendedSetUsed(transformSetType)) - { - transformType = Av1TransformType.DctDct; - } - - return transformType; - } - - private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) - { - Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); - - if (squareUpSize >= Av1TransformSize.Size32x32) - { - return Av1TransformSetType.DctOnly; - } - - if (useReducedSet) - { - return Av1TransformSetType.Dtt4Identity; - } - - Av1TransformSize squareSize = transformSize.GetSquareSize(); - return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; - } - - private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) - { - Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; - if (mode == Av1PredictionMode.UvChromaFromLuma) - { - mode = Av1PredictionMode.DC; - } + Av1BlockSize planeBlockSize = partitionInfo.ModeInfo.BlockSize.GetSubsampled(subX, subY); + int blocksWide = partitionInfo.GetMaxBlockWide(planeBlockSize, subX); + int blocksHigh = partitionInfo.GetMaxBlockHigh(planeBlockSize, subY); - return mode.ToTransformType(); + return reader.ReadCoefficients(partitionInfo.ModeInfo, blockPosition, this.aboveNeighborContext.GetContext(plane), this.leftNeighborContext.GetContext(plane), aboveOffset, leftOffset, plane, blocksWide, blocksHigh, transformBlockContext, transformSize, isLossless, this.FrameHeader.UseReducedTransformSet, transformInfo, partitionInfo.ModeBlockToRightEdge, partitionInfo.ModeBlockToBottomEdge, coefficientBuffer); } private Av1TransformBlockContext GetTransformBlockContext(Av1TransformSize transformSize, int plane, Av1BlockSize planeBlockSize, int transformBlockUnitHighCount, int transformBlockUnitWideCount, int startY, int startX) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs new file mode 100644 index 0000000000..2e1bd61914 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using Microsoft.VisualBasic; +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; + +[Trait("Format", "Avif")] +public class Av1CoefficientsEntropyTests +{ + private const int BaseQIndex = 23; + + [Fact] + public void RoundTripZeroEndOfBlock() + { + // Assign + const short transformBlockSkipContext = 0; + const short dcSignContext = 0; + const int txbIndex = 0; + Av1BlockSize blockSize = Av1BlockSize.Block4x4; + Av1TransformSize transformSize = Av1TransformSize.Size4x4; + Av1TransformType transformType = Av1TransformType.Identity; + Av1PredictionMode intraDirection = Av1PredictionMode.DC; + Av1ComponentType componentType = Av1ComponentType.Luminance; + Av1FilterIntraMode filterIntraMode = Av1FilterIntraMode.DC; + ushort endOfBlock = 16; + Av1BlockModeInfo modeInfo = new(Av1Constants.MaxPlanes, blockSize, new Point(0, 0)); + Av1TransformInfo transformInfo = new(transformSize, 0, 0); + int[] aboveContexts = new int[1]; + int[] leftContexts = new int[1]; + Av1TransformBlockContext transformBlockContext = new(); + Configuration configuration = Configuration.Default; + Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); + Span coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + Span actuals = new int[16]; + + // Act + encoder.WriteCoefficients(transformSize, transformType, txbIndex, intraDirection, coefficientsBuffer, componentType, transformBlockSkipContext, dcSignContext, endOfBlock, true, BaseQIndex, filterIntraMode); + + using IMemoryOwner encoded = encoder.Exit(); + + Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolReader reader = new(encoded.GetSpan()); + decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); + + // Assert + Assert.Equal(coefficientsBuffer, actuals); + } + +} From e592ef6881fe8d2ce8cbc52ae806f2ecd7994dc8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 24 Nov 2024 18:30:40 +0100 Subject: [PATCH 189/190] Introduce Av1LevelBuffer class --- .../Formats/Heif/Av1/Entropy/Av1NzMap.cs | 39 +- .../Av1/Entropy/Av1SymbolContextHelper.cs | 382 ++++++++++++++++++ .../Heif/Av1/Entropy/Av1SymbolDecoder.cs | 244 ++--------- .../Heif/Av1/Entropy/Av1SymbolEncoder.cs | 313 +------------- .../Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs | 73 ++++ .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 2 +- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 2 +- .../Formats/Heif/Av1/Av1EntropyTests.cs | 8 +- 8 files changed, 526 insertions(+), 537 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs index b66d092ba5..412f6f80e0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1NzMap.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; @@ -282,31 +283,45 @@ internal static class Av1NzMap NzMapContextOffset64x32, // TX_64x16 ]; - public static int GetNzMagnitude(ReadOnlySpan levels, int bwl, Av1TransformClass transformClass) + /// + /// SVT: get_nz_mag + /// + public static int GetNzMagnitude(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) + => GetNzMagnitude(levels, index >> blockWidthLog2, transformClass); + + /// + /// SVT: get_nz_mag + /// + public static int GetNzMagnitude(Av1LevelBuffer levels, int y, Av1TransformClass transformClass) { int mag; + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + Span row2 = levels.GetRow(y + 2); // Note: AOMMIN(level, 3) is useless for decoder since level < 3. - mag = ClipMax3[levels[1]]; // { 0, 1 } - mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal]]; // { 1, 0 } + mag = ClipMax3[row0[1]]; // { 0, 1 } + mag += ClipMax3[row1[0]]; // { 1, 0 } switch (transformClass) { case Av1TransformClass.Class2D: - mag += ClipMax3[levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1]]; // { 1, 1 } - mag += ClipMax3[levels[2]]; // { 0, 2 } - mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } + mag += ClipMax3[row1[1]]; // { 1, 1 } + mag += ClipMax3[row0[2]]; // { 0, 2 } + mag += ClipMax3[row2[0]]; // { 2, 0 } break; case Av1TransformClass.ClassVertical: - mag += ClipMax3[levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)]]; // { 2, 0 } - mag += ClipMax3[levels[(3 << bwl) + (3 << Av1Constants.TransformPadHorizontalLog2)]]; // { 3, 0 } - mag += ClipMax3[levels[(4 << bwl) + (4 << Av1Constants.TransformPadHorizontalLog2)]]; // { 4, 0 } + Span row3 = levels.GetRow(y + 3); + Span row4 = levels.GetRow(y + 4); + mag += ClipMax3[row2[0]]; // { 2, 0 } + mag += ClipMax3[row3[0]]; // { 3, 0 } + mag += ClipMax3[row4[0]]; // { 4, 0 } break; case Av1TransformClass.ClassHorizontal: - mag += ClipMax3[levels[2]]; // { 0, 2 } - mag += ClipMax3[levels[3]]; // { 0, 3 } - mag += ClipMax3[levels[4]]; // { 0, 4 } + mag += ClipMax3[row0[2]]; // { 0, 2 } + mag += ClipMax3[row0[3]]; // { 0, 3 } + mag += ClipMax3[row0[4]]; // { 0, 4 } break; } diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs new file mode 100644 index 0000000000..ea321205f3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolContextHelper.cs @@ -0,0 +1,382 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; +using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; + +internal static class Av1SymbolContextHelper +{ + public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; + private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; + private static readonly byte[] EndOfBlockToPositionSmall = [ + 0, 1, 2, // 0-2 + 3, 3, // 3-4 + 4, 4, 4, 4, // 5-8 + 5, 5, 5, 5, 5, 5, 5, 5, // 9-16 + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32 + ]; + + private static readonly byte[] EndOfBlockToPositionLarge = [ + 6, // place holder + 7, // 33-64 + 8, + 8, // 65-128 + 9, + 9, + 9, + 9, // 129-256 + 10, + 10, + 10, + 10, + 10, + 10, + 10, + 10, // 257-512 + 11 // 513- + ]; + + // Maps tx set types to the indices. INTRA values only + private static readonly int[] ExtendedTransformSetToIndex = [0, -1, 2, 1, -1, -1]; + + internal static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) + { + int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; + if (endOfBlock > 2) + { + endOfBlock += endOfBlockExtra; + } + + return endOfBlock; + } + + internal static int GetBaseRangeContextEndOfBlock(int index, int blockWidthLog2, Av1TransformClass transformClass) + { + int row = index >> blockWidthLog2; + int col = index - (row << blockWidthLog2); + if (index == 0) + { + return 0; + } + + if ((transformClass == Av1TransformClass.Class2D && row < 2 && col < 2) || + (transformClass == Av1TransformClass.ClassHorizontal && col == 0) || + (transformClass == Av1TransformClass.ClassVertical && row == 0)) + { + return 7; + } + + return 14; + } + + /// + /// SVT: get_lower_levels_ctx_eob + /// + internal static int GetLowerLevelContextEndOfBlock(int blockWidthLog2, int height, int scanIndex) + { + if (scanIndex == 0) + { + return 0; + } + + if (scanIndex <= height << blockWidthLog2 >> 3) + { + return 1; + } + + if (scanIndex <= height << blockWidthLog2 >> 2) + { + return 2; + } + + return 3; + } + + /// + /// SVT: get_br_ctx_2d + /// + internal static int GetBaseRangeContext2d(Av1LevelBuffer levels, int index, int blockWidthLog2) + { + DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + int stride = (1 << blockWidthLog2) + Av1Constants.TransformPadHorizontal; + int pos = (y * stride) + x; + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + int mag = + Math.Min((int)row0[1], Av1Constants.MaxBaseRange) + + Math.Min((int)row1[0], Av1Constants.MaxBaseRange) + + Math.Min((int)row1[1], Av1Constants.MaxBaseRange); + mag = Math.Min((mag + 1) >> 1, 6); + if ((y | x) < 2) + { + return mag + 7; + } + + return mag + 14; + } + + /// + /// SVT: get_lower_levels_ctx_2d + /// + internal static int GetLowerLevelsContext2d(Av1LevelBuffer levelBuffer, int index, int blockWidthLog2, Av1TransformSize transformSize) + { + DebugGuard.MustBeGreaterThan(index, 0, nameof(index)); + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + int mag; + Span row0 = levelBuffer.GetRow(y); + Span row1 = levelBuffer.GetRow(y + 1); + Span row2 = levelBuffer.GetRow(y + 2); + mag = Math.Min((int)row0[1], 3); // { 0, 1 } + mag += Math.Min((int)row1[0], 3); // { 1, 0 } + mag += Math.Min((int)row1[1], 3); // { 1, 1 } + mag += Math.Min((int)row0[2], 3); // { 0, 2 } + mag += Math.Min((int)row2[0], 3); // { 2, 0 } + + int ctx = Math.Min((mag + 1) >> 1, 4); + return ctx + Av1NzMap.GetNzMapContext(transformSize, index); + } + + /// + /// SVT: get_br_ctx + /// + internal static int GetBaseRangeContext(Av1LevelBuffer levels, int index, int blockWidthLog2, Av1TransformClass transformClass) + { + int y = index >> blockWidthLog2; + int x = index - (y << blockWidthLog2); + Span row0 = levels.GetRow(y); + Span row1 = levels.GetRow(y + 1); + int mag = row0[x + 1]; + mag += row1[x]; + switch (transformClass) + { + case Av1TransformClass.Class2D: + mag += row1[x + 1]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (y < 2 && x < 2) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassHorizontal: + mag += row0[2]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (x == 0) + { + return mag + 7; + } + + break; + case Av1TransformClass.ClassVertical: + mag += levels.GetRow(y + 2)[0]; + mag = Math.Min((mag + 1) >> 1, 6); + if (index == 0) + { + return mag; + } + + if (y == 0) + { + return mag + 7; + } + + break; + default: + break; + } + + return mag + 14; + } + + internal static int GetLowerLevelsContext(Av1LevelBuffer levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) + { + int stats = Av1NzMap.GetNzMagnitude(levels, pos >> bwl, transformClass); + return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); + } + + internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) + { + Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); + + if (squareUpSize >= Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (useReducedSet) + { + return Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize squareSize = transformSize.GetSquareSize(); + return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + + internal static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) + { + Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; + if (mode == Av1PredictionMode.UvChromaFromLuma) + { + mode = Av1PredictionMode.DC; + } + + return mode.ToTransformType(); + } + + /// + /// SVT: get_nz_map_ctx + /// + internal static sbyte GetNzMapContext( + Av1LevelBuffer levels, + int index, + int blockWidthLog2, + int height, + int scan_idx, + bool is_eob, + Av1TransformSize transformSize, + Av1TransformClass transformClass) + { + if (is_eob) + { + if (scan_idx == 0) + { + return 0; + } + + if (scan_idx <= (height << blockWidthLog2) / 8) + { + return 1; + } + + if (scan_idx <= (height << blockWidthLog2) / 4) + { + return 2; + } + + return 3; + } + + int stats = Av1NzMap.GetNzMagnitude(levels, index, blockWidthLog2, transformClass); + return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, index, blockWidthLog2, transformSize, transformClass); + } + + /// + /// SVT: svt_av1_get_nz_map_contexts_c + /// + internal static void GetNzMapContexts( + Av1LevelBuffer levels, + ReadOnlySpan scan, + ushort eob, + Av1TransformSize transformSize, + Av1TransformClass transformClass, + Span coefficientContexts) + { + int blockWidthLog2 = transformSize.GetBlockWidthLog2(); + int height = transformSize.GetHeight(); + for (int i = 0; i < eob; ++i) + { + int pos = scan[i]; + coefficientContexts[pos] = GetNzMapContext(levels, pos, blockWidthLog2, height, i, i == eob - 1, transformSize, transformClass); + } + } + + /// + /// SVT: get_ext_tx_set_type + /// + internal static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) + { + Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); + + if (transformSizeSquareUp > Av1TransformSize.Size32x32) + { + return Av1TransformSetType.DctOnly; + } + + if (transformSizeSquareUp == Av1TransformSize.Size32x32) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; + } + + if (useReducedTransformSet) + { + return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; + } + + Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); + if (isInter) + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; + } + else + { + return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; + } + } + + internal static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool useReducedTransformSet) + { + int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); + return TransformCountInSet[setType]; + } + + /// + /// SVT: get_ext_tx_set + /// + internal static int GetExtendedTransformSet(Av1TransformSize transformSize, bool useReducedTransformSet) + { + int setType = (int)GetExtendedTransformSetType(transformSize, useReducedTransformSet); + return ExtendedTransformSetToIndex[setType]; + } + + /// + /// SVT: set_dc_sign + /// + internal static void SetDcSign(ref int culLevel, int dcValue) + { + if (dcValue < 0) + { + culLevel |= 1 << Av1Constants.CoefficientContextBitCount; + } + else if (dcValue > 0) + { + culLevel += 2 << Av1Constants.CoefficientContextBitCount; + } + } + + /// + /// SVT: get_eob_pos_token + /// + internal static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) + { + short t; + if (endOfBlock < 33) + { + t = EndOfBlockToPositionSmall[endOfBlock]; + } + else + { + int e = Math.Min((endOfBlock - 1) >> 5, 16); + t = EndOfBlockToPositionLarge[e]; + } + + extra = endOfBlock - EndOfBlockGroupStart[t]; + return t; + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs index eeaafe87ed..544e8915b3 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolDecoder.cs @@ -11,8 +11,6 @@ internal ref struct Av1SymbolDecoder { private static readonly int[] IntraModeContext = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; private static readonly int[] AlphaVContexts = [-1, 0, 3, -1, 1, 4, -1, 2, 5]; - public static readonly int[] EndOfBlockOffsetBits = [0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - public static readonly int[] EndOfBlockGroupStart = [0, 1, 2, 3, 5, 9, 17, 33, 65, 129, 257, 513]; private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; @@ -35,10 +33,12 @@ internal ref struct Av1SymbolDecoder private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution chromeForLumaSign = Av1DefaultDistributions.ChromeForLumaSign; private readonly Av1Distribution[] chromeForLumaAlpha = Av1DefaultDistributions.ChromeForLumaAlpha; + private Configuration configuration; private Av1SymbolReader reader; - public Av1SymbolDecoder(Span tileData, int qIndex) + public Av1SymbolDecoder(Configuration configuration, Span tileData, int qIndex) { + this.configuration = configuration; this.reader = new Av1SymbolReader(tileData); this.endOfBlockFlag = Av1DefaultDistributions.GetEndOfBlockFlag(qIndex); this.coefficientsBase = Av1DefaultDistributions.GetCoefficientsBase(qIndex); @@ -237,8 +237,7 @@ public int ReadCoefficients( Av1PlaneType planeType = (Av1PlaneType)Math.Min(plane, 1); int culLevel = 0; - byte[] levelsBuffer = new byte[Av1Constants.TransformPad2d]; - Span levels = levelsBuffer.AsSpan()[(Av1Constants.TransformPadTop * (width + Av1Constants.TransformPadHorizontal))..]; + Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); bool allZero = this.ReadTransformBlockSkip(transformSizeContext, transformBlockContext.SkipContext); int bwl = transformSize.GetBlockWidthLog2(); @@ -263,7 +262,7 @@ public int ReadCoefficients( endOfBlock = this.ReadEndOfBlockPosition(transformSize, transformClass, transformSizeContext, planeType); if (endOfBlock > 1) { - Array.Fill(levelsBuffer, (byte)0, 0, ((width + Av1Constants.TransformPadHorizontal) * (height + Av1Constants.TransformPadVertical)) + Av1Constants.TransformPadEnd); + levels.Clear(); } this.ReadCoefficientsEndOfBlock(transformClass, endOfBlock, height, scan, bwl, levels, transformSizeContext, planeType); @@ -292,7 +291,7 @@ public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformCl { int endOfBlockExtra = 0; int endOfBlockPoint = this.ReadEndOfBlockFlag(planeType, transformClass, transformSize); - int endOfBlockShift = EndOfBlockOffsetBits[endOfBlockPoint]; + int endOfBlockShift = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPoint]; if (endOfBlockShift > 0) { int endOfBlockContext = endOfBlockPoint; @@ -313,18 +312,18 @@ public int ReadEndOfBlockPosition(Av1TransformSize transformSize, Av1TransformCl } } - return RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); + return Av1SymbolContextHelper.RecordEndOfBlockPosition(endOfBlockPoint, endOfBlockExtra); } - public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int endOfBlock, int height, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { int i = endOfBlock - 1; int pos = scan[i]; - int coefficientContext = GetLowerLevelContextEndOfBlock(bwl, height, i); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelContextEndOfBlock(blockWidthLog2, height, i); int level = this.ReadBaseEndOfBlock(transformSizeContext, planeType, coefficientContext); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContextEndOfBlock(pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContextEndOfBlock(pos, blockWidthLog2, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange / Av1Constants.BaseRangeSizeMinus1; idx++) { int coefficinetBaseRange = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -336,19 +335,19 @@ public void ReadCoefficientsEndOfBlock(Av1TransformClass transformClass, int end } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } - public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext2d(levels, pos, bwl, transformSize); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext2d(levels, pos, blockWidthLog2, transformSize); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContext2d(levels, pos, bwl); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext2d(levels, pos, blockWidthLog2); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -360,20 +359,20 @@ public void ReadCoefficientsReverse2d(Av1TransformSize transformSize, int startS } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } } - public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int bwl, Span levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) + public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1TransformClass transformClass, int startScanIndex, int endScanIndex, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, Av1TransformSize transformSizeContext, Av1PlaneType planeType) { for (int c = endScanIndex; c >= startScanIndex; --c) { int pos = scan[c]; - int coefficientContext = GetLowerLevelsContext(levels, pos, bwl, transformSize, transformClass); + int coefficientContext = Av1SymbolContextHelper.GetLowerLevelsContext(levels, pos, blockWidthLog2, transformSize, transformClass); int level = this.ReadCoefficientsBase(coefficientContext, transformSizeContext, planeType); if (level > Av1Constants.BaseLevelsCount) { - int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, blockWidthLog2, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = this.ReadCoefficientsBaseRange(transformSizeContext, planeType, baseRangeContext); @@ -385,11 +384,11 @@ public void ReadCoefficientsReverse(Av1TransformSize transformSize, Av1Transform } } - levels[GetPaddedIndex(pos, bwl)] = (byte)level; + levels.GetPaddedRow(pos, blockWidthLog2)[0] = (byte)level; } } - public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int bwl, Span levels, int dcSignContext, Av1PlaneType planeType) + public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadOnlySpan scan, int blockWidthLog2, Av1LevelBuffer levels, int dcSignContext, Av1PlaneType planeType) { int maxScanLine = 0; int culLevel = 0; @@ -398,7 +397,7 @@ public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadO for (int c = 0; c < endOfBlock; c++) { int sign = 0; - int level = levels[GetPaddedIndex(scan[c], bwl)]; + int level = levels.GetPaddedRow(scan[c], blockWidthLog2)[0]; if (level != 0) { maxScanLine = Math.Max(maxScanLine, scan[c]); @@ -429,7 +428,7 @@ public int ReadCoefficientsDc(Span coefficientBuffer, int endOfBlock, ReadO } culLevel = Math.Min(Av1Constants.CoefficientContextMask, culLevel); - SetDcSign(ref culLevel, dcValue); + Av1SymbolContextHelper.SetDcSign(ref culLevel, dcValue); return culLevel; } @@ -472,159 +471,6 @@ private int ReadCoefficientsBase(int coefficientContext, Av1TransformSize transf return r.ReadSymbol(this.coefficientsBase[(int)transformSizeContext][(int)planeType][coefficientContext]); } - private static int RecordEndOfBlockPosition(int endOfBlockPoint, int endOfBlockExtra) - { - int endOfBlock = EndOfBlockGroupStart[endOfBlockPoint]; - if (endOfBlock > 2) - { - endOfBlock += endOfBlockExtra; - } - - return endOfBlock; - } - - private static int GetBaseRangeContextEndOfBlock(int pos, int bwl, Av1TransformClass transformClass) - { - int row = pos >> bwl; - int col = pos - (row << bwl); - if (pos == 0) - { - return 0; - } - - if (transformClass == Av1TransformClass.Class2D && row < 2 && col < 2 || - transformClass == Av1TransformClass.ClassHorizontal && col == 0 || - transformClass == Av1TransformClass.ClassVertical && row == 0) - { - return 7; - } - - return 14; - } - - private static int GetLowerLevelContextEndOfBlock(int bwl, int height, int scanIndex) - { - if (scanIndex == 0) - { - return 0; - } - - if (scanIndex <= height << bwl >> 3) - { - return 1; - } - - if (scanIndex <= height << bwl >> 2) - { - return 2; - } - - return 3; - } - - private static int GetBaseRangeContext2d(Span levels, int c, int bwl) - { - DebugGuard.MustBeGreaterThan(c, 0, nameof(c)); - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = - Math.Min((int)levels[pos + 1], Av1Constants.MaxBaseRange) + - Math.Min((int)levels[pos + stride], Av1Constants.MaxBaseRange) + - Math.Min((int)levels[pos + 1 + stride], Av1Constants.MaxBaseRange); - mag = Math.Min(mag + 1 >> 1, 6); - if ((row | col) < 2) - { - return mag + 7; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext2d(Span levels, int pos, int bwl, Av1TransformSize transformSize) - { - DebugGuard.MustBeGreaterThan(pos, 0, nameof(pos)); - int mag; - levels = levels[GetPaddedIndex(pos, bwl)..]; - mag = Math.Min((int)levels[1], 3); // { 0, 1 } - mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal], 3); // { 1, 0 } - mag += Math.Min((int)levels[(1 << bwl) + Av1Constants.TransformPadHorizontal + 1], 3); // { 1, 1 } - mag += Math.Min((int)levels[2], 3); // { 0, 2 } - mag += Math.Min((int)levels[(2 << bwl) + (2 << Av1Constants.TransformPadHorizontalLog2)], 3); // { 2, 0 } - - int ctx = Math.Min(mag + 1 >> 1, 4); - return ctx + Av1NzMap.GetNzMapContext(transformSize, pos); - } - - private static int GetBaseRangeContext(Span levels, int c, int bwl, Av1TransformClass transformClass) - { - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = levels[pos + 1]; - mag += levels[pos + stride]; - switch (transformClass) - { - case Av1TransformClass.Class2D: - mag += levels[pos + stride + 1]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row < 2 && col < 2) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassHorizontal: - mag += levels[pos + 2]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (col == 0) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassVertical: - mag += levels[pos + (stride << 1)]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row == 0) - { - return mag + 7; - } - - break; - default: - break; - } - - return mag + 14; - } - - private static int GetLowerLevelsContext(ReadOnlySpan levels, int pos, int bwl, Av1TransformSize transformSize, Av1TransformClass transformClass) - { - int stats = Av1NzMap.GetNzMagnitude(levels[GetPaddedIndex(pos, bwl)..], bwl, transformClass); - return Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); - } - - public static int GetPaddedIndex(int scanIndex, int bwl) - => scanIndex + (scanIndex >> bwl << Av1Constants.TransformPadHorizontalLog2); - private int ReadGolomb() { int x = 1; @@ -651,19 +497,6 @@ private int ReadGolomb() return x - 1; } - private static void SetDcSign(ref int culLevel, int dcValue) - { - if (dcValue < 0) - { - culLevel |= 1 << Av1Constants.CoefficientContextBitCount; - } - else if (dcValue > 0) - { - culLevel += 2 << Av1Constants.CoefficientContextBitCount; - } - } - - private void UpdateCoefficientContext( Av1BlockModeInfo modeInfo, int[] aboveContexts, @@ -721,11 +554,11 @@ private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1 { // In intra mode, uv planes don't share the same prediction mode as y // plane, so the tx_type should not be shared - transformType = ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); + transformType = Av1SymbolContextHelper.ConvertIntraModeToTransformType(modeInfo, Av1PlaneType.Uv); } } - Av1TransformSetType transformSetType = GetExtendedTransformSetType(transformSize, useReducedTransformSet); + Av1TransformSetType transformSetType = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); if (!transformType.IsExtendedSetUsed(transformSetType)) { transformType = Av1TransformType.DctDct; @@ -734,35 +567,6 @@ private static Av1TransformType ComputeTransformType(Av1PlaneType planeType, Av1 return transformType; } - private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool useReducedSet) - { - Av1TransformSize squareUpSize = transformSize.GetSquareUpSize(); - - if (squareUpSize >= Av1TransformSize.Size32x32) - { - return Av1TransformSetType.DctOnly; - } - - if (useReducedSet) - { - return Av1TransformSetType.Dtt4Identity; - } - - Av1TransformSize squareSize = transformSize.GetSquareSize(); - return squareSize == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; - } - - private static Av1TransformType ConvertIntraModeToTransformType(Av1BlockModeInfo modeInfo, Av1PlaneType planeType) - { - Av1PredictionMode mode = (planeType == Av1PlaneType.Y) ? modeInfo.YMode : modeInfo.UvMode; - if (mode == Av1PredictionMode.UvChromaFromLuma) - { - mode = Av1PredictionMode.DC; - } - - return mode.ToTransformType(); - } - internal static Av1Distribution GetSplitOrHorizontalDistribution(Av1Distribution[] inputs, Av1BlockSize blockSize, int context) { Av1Distribution input = inputs[context]; diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 417c58a5a5..0822489d3d 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -1,11 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Buffers; -using System.Drawing; -using System.Formats.Asn1; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -24,46 +20,6 @@ internal class Av1SymbolEncoder : IDisposable [7, 8, 9, 12, 10, 11, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6], ]; - private static readonly byte[] EndOfBlockToPositionSmall = [ - 0, 1, 2, // 0-2 - 3, 3, // 3-4 - 4, 4, 4, 4, // 5-8 - 5, 5, 5, 5, 5, 5, 5, 5, // 9-16 - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 // 17-32 - ]; - - private static readonly byte[] EndOfBlockToPositionLarge = [ - 6, // place holder - 7, // 33-64 - 8, - 8, // 65-128 - 9, - 9, - 9, - 9, // 129-256 - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, // 257-512 - 11 // 513- - ]; - - private static readonly int[] TransformCountInSet = [1, 2, 5, 7, 12, 16]; - - // Maps tx set types to the indices. - private static readonly int[][] ExtendedTransformSetToIndex = [ - - // Intra - [0, -1, 2, 1, -1, -1], - - // Inter - [0, 3, -1, -1, 2, 1] - ]; - private readonly Av1Distribution tileIntraBlockCopy = Av1DefaultDistributions.IntraBlockCopy; private readonly Av1Distribution[] tilePartitionTypes = Av1DefaultDistributions.PartitionTypes; private readonly Av1Distribution[] skip = Av1DefaultDistributions.Skip; @@ -75,6 +31,7 @@ internal class Av1SymbolEncoder : IDisposable private readonly Av1Distribution[][][] endOfBlockExtra; private readonly Av1Distribution[][][] intraExtendedTransform = Av1DefaultDistributions.IntraExtendedTransform; private bool isDisposed; + private readonly Configuration configuration; private Av1SymbolWriter writer; public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex) @@ -85,6 +42,7 @@ public Av1SymbolEncoder(Configuration configuration, int initialSize, int qIndex this.coefficientsBaseEndOfBlock = Av1DefaultDistributions.GetBaseEndOfBlock(qIndex); this.dcSign = Av1DefaultDistributions.GetDcSign(qIndex); this.endOfBlockExtra = Av1DefaultDistributions.GetEndOfBlockExtra(qIndex); + this.configuration = configuration; this.writer = new(configuration, initialSize); } @@ -140,12 +98,11 @@ public int WriteCoefficients( Av1ScanOrder scanOrder = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType); ReadOnlySpan scan = scanOrder.Scan; int bwl = transformSize.GetBlockWidthLog2(); - Av1TransformSize transformSizeContext = (Av1TransformSize)((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1 >> 1); + Av1TransformSize transformSizeContext = (Av1TransformSize)(((int)transformSize.GetSquareSize() + (int)transformSize.GetSquareUpSize() + 1) >> 1); ref Av1SymbolWriter w = ref this.writer; - Span levels_buf = new byte[Av1Constants.TransformPad2d]; - Span levels = SetLevels(levels_buf, width); + Av1LevelBuffer levels = new(this.configuration, new Size(width, height)); Span coeff_contexts = new sbyte[Av1Constants.MaxTransformSize * Av1Constants.MaxTransformSize]; Guard.MustBeLessThan((int)transformSizeContext, (int)Av1TransformSize.AllSizes, nameof(transformSizeContext)); @@ -158,16 +115,16 @@ public int WriteCoefficients( return 0; } - InitializeLevels(coeffBuffer, width, height, levels); + levels.Initialize(coeffBuffer); if (componentType == Av1ComponentType.Luminance) { this.WriteTransformType(transformType, transformSize, useReducedTransformSet, baseQIndex, filterIntraMode, intraDirection); } - short endOfBlockPosition = GetEndOfBlockPosition(eob, out int eob_extra); + short endOfBlockPosition = Av1SymbolContextHelper.GetEndOfBlockPosition(eob, out int eob_extra); this.WriteEndOfBlockFlag(componentType, transformClass, transformSize, endOfBlockPosition); - int eob_offset_bits = Av1SymbolDecoder.EndOfBlockOffsetBits[endOfBlockPosition]; + int eob_offset_bits = Av1SymbolContextHelper.EndOfBlockOffsetBits[endOfBlockPosition]; if (eob_offset_bits > 0) { int eob_shift = eob_offset_bits - 1; @@ -181,7 +138,7 @@ public int WriteCoefficients( } } - GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); + Av1SymbolContextHelper.GetNzMapContexts(levels, scan, eob, transformSize, transformClass, coeff_contexts); int limitedTransformSizeContext = Math.Min((int)transformSizeContext, (int)Av1TransformSize.Size32x32); for (c = eob - 1; c >= 0; --c) { @@ -203,7 +160,7 @@ public int WriteCoefficients( { // level is above 1. int baseRange = level - 1 - Av1Constants.BaseLevelsCount; - int baseRangeContext = GetBaseRangeContext(levels, pos, bwl, transformClass); + int baseRangeContext = Av1SymbolContextHelper.GetBaseRangeContext(levels, pos, bwl, transformClass); for (int idx = 0; idx < Av1Constants.CoefficientBaseRange; idx += Av1Constants.BaseRangeSizeMinus1) { int k = Math.Min(baseRange - idx, Av1Constants.BaseRangeSizeMinus1); @@ -248,196 +205,10 @@ public int WriteCoefficients( cul_level = Math.Min(Av1Constants.CoefficientContextMask, cul_level); // DC value - SetDcSign(ref cul_level, coeffBuffer[0]); + Av1SymbolContextHelper.SetDcSign(ref cul_level, coeffBuffer[0]); return cul_level; } - /// - /// SVT: set_dc_sign - /// - private static void SetDcSign(ref int culLevel, int dcValue) - { - if (dcValue < 0) - { - culLevel |= 1 << Av1Constants.CoefficientContextBitCount; - } - else if (dcValue > 0) - { - culLevel += 2 << Av1Constants.CoefficientContextBitCount; - } - } - - /// - /// SVT: get_br_ctx - /// - private static int GetBaseRangeContext(Span levels, short c, int bwl, Av1TransformClass transformClass) - { - int row = c >> bwl; - int col = c - (row << bwl); - int stride = (1 << bwl) + Av1Constants.TransformPadHorizontal; - int pos = row * stride + col; - int mag = levels[pos + 1]; - mag += levels[pos + stride]; - switch (transformClass) - { - case Av1TransformClass.Class2D: - mag += levels[pos + stride + 1]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row < 2 && col < 2) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassHorizontal: - mag += levels[pos + 2]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (col == 0) - { - return mag + 7; - } - - break; - case Av1TransformClass.ClassVertical: - mag += levels[pos + (stride << 1)]; - mag = Math.Min(mag + 1 >> 1, 6); - if (c == 0) - { - return mag; - } - - if (row == 0) - { - return mag + 7; - } - - break; - default: - break; - } - - return mag + 14; - } - - /// - /// SVT: get_eob_pos_token - /// - private static short GetEndOfBlockPosition(ushort endOfBlock, out int extra) - { - short t; - if (endOfBlock < 33) - { - t = EndOfBlockToPositionSmall[endOfBlock]; - } - else - { - int e = Math.Min(endOfBlock - 1 >> 5, 16); - t = EndOfBlockToPositionLarge[e]; - } - - extra = endOfBlock - Av1SymbolDecoder.EndOfBlockGroupStart[t]; - return t; - } - - /// - /// SVT: get_nz_map_ctx - /// - private static sbyte GetNzMapContext( - ReadOnlySpan levels, - int pos, - int bwl, - int height, - int scan_idx, - bool is_eob, - Av1TransformSize transformSize, - Av1TransformClass transformClass) - { - if (is_eob) - { - if (scan_idx == 0) - { - return 0; - } - - if (scan_idx <= (height << bwl) / 8) - { - return 1; - } - - if (scan_idx <= (height << bwl) / 4) - { - return 2; - } - - return 3; - } - - int stats = Av1NzMap.GetNzMagnitude(levels[Av1SymbolDecoder.GetPaddedIndex(pos, bwl)..], bwl, transformClass); - return (sbyte)Av1NzMap.GetNzMapContextFromStats(stats, pos, bwl, transformSize, transformClass); - } - - /// - /// SVT: svt_av1_get_nz_map_contexts_c - /// - private static void GetNzMapContexts( - ReadOnlySpan levels, - ReadOnlySpan scan, - ushort eob, - Av1TransformSize tx_size, - Av1TransformClass tx_class, - Span coeff_contexts) - { - int bwl = tx_size.GetBlockWidthLog2(); - int height = tx_size.GetHeight(); - for (int i = 0; i < eob; ++i) - { - int pos = scan[i]; - coeff_contexts[pos] = GetNzMapContext(levels, pos, bwl, height, i, i == eob - 1, tx_size, tx_class); - } - } - - /// - /// SVT: svt_av1_txb_init_levels_c - /// - private static void InitializeLevels(Span coefficientBuffer, int width, int height, Span levels) - { - int stride = width + Av1Constants.TransformPadHorizontal; - ref byte ls = ref levels[0]; - - Unsafe.InitBlock(ref levels[-Av1Constants.TransformPadTop * stride], 0, (uint)(Av1Constants.TransformPadTop * stride)); - Unsafe.InitBlock(ref levels[stride * height], 0, (uint)((Av1Constants.TransformPadBottom * stride) + Av1Constants.TransformPadEnd)); - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - ls = (byte)Av1Math.Clamp(Math.Abs(coefficientBuffer[(y * width) + x]), 0, byte.MaxValue); - ls = ref Unsafe.Add(ref ls, 1); - } - - Unsafe.InitBlock(ref ls, 0, Av1Constants.TransformPadHorizontal); - } - } - - /// - /// SVT: set_levels from EbCommonUtils.h - /// - private static Span SetLevels(Span levelsBuffer, int width) - { - int stride = width + Av1Constants.TransformPadHorizontal; - return levelsBuffer[(Av1Constants.TransformPadTop * stride)..]; - } - private void WriteSkip(bool hasEndOfBlock, int context) { // Has EOB, means we won't skip, negating the logic. @@ -503,28 +274,20 @@ private void WriteTransformType( Av1PredictionMode intraDirection) { ref Av1SymbolWriter w = ref this.writer; - bool isInter = false; // mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); - if (GetExtendedTransformTypeCount(transformSize, isInter, useReducedTransformSet) > 1 && baseQIndex > 0) + // bool isInter = mbmi->block_mi.use_intrabc || is_inter_mode(mbmi->block_mi.mode); + if (Av1SymbolContextHelper.GetExtendedTransformTypeCount(transformSize, useReducedTransformSet) > 1 && baseQIndex > 0) { Av1TransformSize square_tx_size = transformSize.GetSquareSize(); Guard.MustBeLessThanOrEqualTo((int)square_tx_size, Av1Constants.ExtendedTransformCount, nameof(square_tx_size)); - Av1TransformSetType tx_set_type = GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - int eset = GetExtendedTransformSet(transformSize, isInter, useReducedTransformSet); + Av1TransformSetType tx_set_type = Av1SymbolContextHelper.GetExtendedTransformSetType(transformSize, useReducedTransformSet); + int eset = Av1SymbolContextHelper.GetExtendedTransformSet(transformSize, useReducedTransformSet); // eset == 0 should correspond to a set with only DCT_DCT and there // is no need to send the tx_type Guard.MustBeGreaterThan(eset, 0, nameof(eset)); // assert(av1_ext_tx_used[tx_set_type][transformType]); - if (isInter) - { - /* - w.WriteSymbol( - av1_ext_tx_ind[tx_set_type][transformType], - this.inter_ext_tx_cdf[eset][square_tx_size]);*/ - } - else { Av1PredictionMode intra_dir; if (filterIntraMode != Av1FilterIntraMode.AllFilterIntraModes) @@ -544,52 +307,4 @@ private void WriteTransformType( } } } - - /// - /// SVT: get_ext_tx_set - /// - private static int GetExtendedTransformSet(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - return ExtendedTransformSetToIndex[isInter ? 1 : 0][set_type]; - } - - /// - /// SVT: get_ext_tx_set_type - /// - private static Av1TransformSetType GetExtendedTransformSetType(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - Av1TransformSize transformSizeSquareUp = transformSize.GetSquareUpSize(); - - if (transformSizeSquareUp > Av1TransformSize.Size32x32) - { - return Av1TransformSetType.DctOnly; - } - - if (transformSizeSquareUp == Av1TransformSize.Size32x32) - { - return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.DctOnly; - } - - if (useReducedTransformSet) - { - return isInter ? Av1TransformSetType.DctIdentity : Av1TransformSetType.Dtt4Identity; - } - - Av1TransformSize transformSizeSquare = transformSize.GetSquareSize(); - if (isInter) - { - return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt9Identity1dDct : Av1TransformSetType.All16; - } - else - { - return transformSizeSquare == Av1TransformSize.Size16x16 ? Av1TransformSetType.Dtt4Identity : Av1TransformSetType.Dtt4Identity1dDct; - } - } - - private static int GetExtendedTransformTypeCount(Av1TransformSize transformSize, bool isInter, bool useReducedTransformSet) - { - int set_type = (int)GetExtendedTransformSetType(transformSize, isInter, useReducedTransformSet); - return TransformCountInSet[set_type]; - } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs new file mode 100644 index 0000000000..d3dd37836e --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs @@ -0,0 +1,73 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; + +internal sealed class Av1LevelBuffer : IDisposable +{ + private IMemoryOwner? memory; + + public Av1LevelBuffer(Configuration configuration) + : this(configuration, new Size(Av1Constants.MaxTransformSize, Av1Constants.MaxTransformSize)) + { + } + + public Av1LevelBuffer(Configuration configuration, Size size) + { + this.Size = size; + int totalHeight = Av1Constants.TransformPadTop + size.Height + Av1Constants.TransformPadBottom; + this.Stride = Av1Constants.TransformPadHorizontal + size.Width; + this.memory = configuration.MemoryAllocator.Allocate(this.Stride * totalHeight, AllocationOptions.Clean); + } + + public Size Size { get; } + + public int Stride { get; } + + public void Initialize(Span coefficientBuffer) + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + ArgumentOutOfRangeException.ThrowIfLessThan(coefficientBuffer.Length, this.Size.Width * this.Size.Height, nameof(coefficientBuffer)); + for (int y = 0; y < this.Size.Height; y++) + { + ref byte destRef = ref this.GetRow(y)[0]; + ref int sourceRef = ref coefficientBuffer[y * this.Size.Width]; + for (int x = 0; x < this.Size.Width; x++) + { + destRef = (byte)Av1Math.Clamp(sourceRef, 0, byte.MaxValue); + destRef = ref Unsafe.Add(ref destRef, 1); + sourceRef = ref Unsafe.Add(ref sourceRef, 1); + } + } + } + + public Span GetRow(int y) + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + ArgumentOutOfRangeException.ThrowIfLessThan(y, -Av1Constants.TransformPadTop); + int row = y + Av1Constants.TransformPadTop; + return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width); + } + + public void Dispose() + { + this.memory?.Dispose(); + this.memory = null; + } + + internal void Clear() + { + ObjectDisposedException.ThrowIf(this.memory == null, this); + this.memory.Memory.Span.Clear(); + } + + internal Span GetPaddedRow(int index, int blockWidthLog2) + { + int y = index >> blockWidthLog2; + return this.GetRow(y); + } +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index 0001ecc04a..63827c5e98 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -77,7 +77,7 @@ public Av1TileReader(Configuration configuration, ObuSequenceHeader sequenceHead ///
public void ReadTile(Span tileData, int tileNum) { - Av1SymbolDecoder reader = new(tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); + Av1SymbolDecoder reader = new(this.configuration, tileData, this.FrameHeader.QuantizationParameters.BaseQIndex); int tileColumnIndex = tileNum % this.FrameHeader.TilesInfo.TileColumnCount; int tileRowIndex = tileNum / this.FrameHeader.TilesInfo.TileColumnCount; diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 2e1bd61914..e877ef7130 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -46,7 +46,7 @@ public void RoundTripZeroEndOfBlock() using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index 37c4aa4a61..a23acfc046 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -208,7 +208,7 @@ public void RoundTripPartitionType() using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -245,7 +245,7 @@ public void RoundTripSplitOrHorizontalPartitionType(int blockSize, int context) using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -282,7 +282,7 @@ public void RoundTripSplitOrVerticalPartitionType(int blockSize, int context) using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); Av1SymbolReader reader = new(encoded.GetSpan()); for (int i = 0; i < values.Length; i++) { @@ -310,7 +310,7 @@ public void RoundTripUseIntraBlockCopy() using IMemoryOwner encoded = encoder.Exit(); - Av1SymbolDecoder decoder = new(encoded.GetSpan(), 0); + Av1SymbolDecoder decoder = new(Configuration.Default, encoded.GetSpan(), 0); for (int i = 0; i < values.Length; i++) { actuals[i] = decoder.ReadUseIntraBlockCopy(); From c7c00391cf55a270465d18092aba23604e30313c Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 24 Nov 2024 18:43:48 +0100 Subject: [PATCH 190/190] Round trip empty Coefficients buffer --- src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs | 4 ++-- src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs | 2 +- .../Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs index 0822489d3d..228ce3e8da 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Entropy/Av1SymbolEncoder.cs @@ -128,12 +128,12 @@ public int WriteCoefficients( if (eob_offset_bits > 0) { int eob_shift = eob_offset_bits - 1; - int bit = Math.Max(1, eob_extra & 1 << eob_shift); + int bit = (eob_extra & (1 << eob_shift)) != 0 ? 1 : 0; w.WriteSymbol(bit, this.endOfBlockExtra[(int)transformSizeContext][(int)componentType][endOfBlockPosition]); for (int i = 1; i < eob_offset_bits; i++) { eob_shift = eob_offset_bits - 1 - i; - bit = Math.Max(1, eob_extra & 1 << eob_shift); + bit = (eob_extra & (1 << eob_shift)) != 0 ? 1 : 0; w.WriteLiteral((uint)bit, 1); } } diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs index d3dd37836e..2fa4c03f03 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1LevelBuffer.cs @@ -50,7 +50,7 @@ public Span GetRow(int y) ObjectDisposedException.ThrowIf(this.memory == null, this); ArgumentOutOfRangeException.ThrowIfLessThan(y, -Av1Constants.TransformPadTop); int row = y + Av1Constants.TransformPadTop; - return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width); + return this.memory.Memory.Span.Slice(row * this.Stride, this.Size.Width + Av1Constants.TransformPadHorizontal); } public void Dispose() diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index e877ef7130..3be90137a5 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -39,6 +39,7 @@ public void RoundTripZeroEndOfBlock() Configuration configuration = Configuration.Default; Av1SymbolEncoder encoder = new(configuration, 100 / 8, BaseQIndex); Span coefficientsBuffer = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + Span expected = new int[16]; Span actuals = new int[16]; // Act @@ -51,7 +52,7 @@ public void RoundTripZeroEndOfBlock() decoder.ReadCoefficients(modeInfo, new Point(0, 0), aboveContexts, leftContexts, 0, 0, 0, 1, 1, transformBlockContext, transformSize, false, true, transformInfo, 0, 0, actuals); // Assert - Assert.Equal(coefficientsBuffer, actuals); + Assert.Equal(expected, actuals); } }