From 23bf5dede079719a1567ca6614a590696527de0f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 4 Aug 2024 16:42:03 +0200 Subject: [PATCH] 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 @@ internal class ObuReader 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 @@ internal class ObuReader 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 @@ internal class ObuReader 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 @@ internal class ObuReader /// 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 @@ internal class ObuReader { 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter } } - 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter 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 @@ internal class ObuWriter _ = 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 @@ internal class ObuWriter 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 @@ internal class Av1TileReader : IAv1TileReader 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 class ObuFrameHeaderTests /* [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 class ObuFrameHeaderTests obuWriter.WriteAll(encoded, obuReader.SequenceHeader, obuReader.FrameHeader, tileStub); // Assert - Assert.Equal(span, encoded.ToArray()); + byte[] encodedArray = encoded.ToArray(); + Assert.Equal(span, encodedArray); } */