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 ref struct Av1BitStreamWriter(Stream stream) 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 @@ internal class ObuReader 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 @@ internal class ObuReader // 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 @@ internal class ObuReader 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 @@ internal class ObuReader 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 @@ internal class ObuReader int deltaQ = 0; if (reader.ReadBoolean()) { - deltaQ = (int)reader.ReadLiteral(7); + deltaQ = reader.ReadSignedFromUnsigned(6); } return deltaQ; @@ -1042,7 +1038,7 @@ internal class ObuReader 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 @@ internal class ObuReader 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 @@ internal class ObuReader } } + /// + /// See section 5.9.20. + /// private static void ReadLoopRestorationParameters(ref Av1BitStreamReader reader, ObuSequenceHeader sequenceHeader, ObuFrameHeader frameInfo, int planesCount) { _ = planesCount; @@ -1153,37 +1148,86 @@ internal class ObuReader 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 @@ internal class ObuReader } 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 + } +}