diff --git a/Codecs/Image/Jpeg/Classes/FrameComponent.cs b/Codecs/Image/Jpeg/Classes/FrameComponent.cs index 5a29010d..30270b0f 100644 --- a/Codecs/Image/Jpeg/Classes/FrameComponent.cs +++ b/Codecs/Image/Jpeg/Classes/FrameComponent.cs @@ -2,7 +2,7 @@ namespace Codec.Jpeg.Classes; -public class FrameComponent : MemorySegment +public sealed class FrameComponent : MemorySegment { /// /// The length of a in bytes. @@ -25,7 +25,7 @@ public int HorizontalSamplingFactor { get { - var bitOffset= Binary.BytesToBits(Offset + 1); + var bitOffset = Binary.BytesToBits(Offset + 1); return (int)this.ReadBits(ref bitOffset, Binary.Four, Binary.BitOrder.MostSignificant); } set diff --git a/Codecs/Image/Jpeg/Classes/JpegState.cs b/Codecs/Image/Jpeg/Classes/JpegState.cs new file mode 100644 index 00000000..4810f37f --- /dev/null +++ b/Codecs/Image/Jpeg/Classes/JpegState.cs @@ -0,0 +1,98 @@ +using Codec.Jpeg.Markers; +using System; + +namespace Codec.Jpeg.Classes; + +/// +/// Contains useful information about the current state of the Jpeg image. +/// +internal sealed class JpegState : IEquatable +{ + /// + /// The function code which corresponds to the StartOfScan marker. + /// + public byte StartOfFrameFunctionCode; + + /// + /// Start of spectral or predictor selection + /// + public byte Ss; + + /// + /// End of spectral selection + /// + public byte Se; + + /// + /// Successive approximation bit position high + /// + public byte Ah; + + /// + /// Successive approximation bit position low or point transform + /// + public byte Al; + + /// + /// Constructors a + /// + /// + /// + /// + /// + /// + public JpegState(byte startOfScanFunctionCode, byte ss, byte se, byte ah, byte al) + { + StartOfFrameFunctionCode = startOfScanFunctionCode; + Ss = ss; + Se = se; + Ah = ah; + Al = al; + } + + /// + /// Constructs a + /// + /// + public JpegState(StartOfScan sos) + { + StartOfFrameFunctionCode = sos.FunctionCode; + Ss = (byte)sos.Ss; + Se = (byte)sos.Se; + Ah = (byte)sos.Ah; + Al = (byte)sos.Al; + } + + + /// + /// Copy constructor + /// + /// + public JpegState (JpegState other) + { + StartOfFrameFunctionCode = other.StartOfFrameFunctionCode; + Ss = other.Ss; + Se = other.Se; + Ah = other.Ah; + Al = other.Al; + } + + public override bool Equals(object obj) + => obj is JpegState jpegState && Equals(jpegState); + + public bool Equals(JpegState other) + => StartOfFrameFunctionCode == other.StartOfFrameFunctionCode && + Ss == other.Ss && + Se == other.Se && + Ah == other.Ah && + Al == other.Al; + + public override int GetHashCode() + => HashCode.Combine(StartOfFrameFunctionCode, Ss, Se, Ah, Al); + + public static bool operator ==(JpegState a, JpegState b) + => a.Equals(b); + + public static bool operator !=(JpegState a, JpegState b) + => false == a.Equals(b); +} diff --git a/Codecs/Image/Jpeg/Classes/ScanComponentSelector.cs b/Codecs/Image/Jpeg/Classes/ScanComponentSelector.cs index a0a56c36..4d3dec45 100644 --- a/Codecs/Image/Jpeg/Classes/ScanComponentSelector.cs +++ b/Codecs/Image/Jpeg/Classes/ScanComponentSelector.cs @@ -25,15 +25,13 @@ public byte Tdj { get { - using var slice = RawData; var bitOffset = Binary.BytesToBits(Offset + 1); - return (byte)slice.ReadBits(bitOffset, Binary.Four, Binary.BitOrder.MostSignificant); + return (byte)this.ReadBits(bitOffset, Binary.Four, Binary.BitOrder.MostSignificant); } set { - using var slice = RawData; var bitOffset = Binary.BytesToBits(Offset + 1); - slice.WriteBits(bitOffset, Binary.Four, value, Binary.BitOrder.MostSignificant); + this.WriteBits(bitOffset, Binary.Four, value, Binary.BitOrder.MostSignificant); } } @@ -44,15 +42,13 @@ public byte Taj { get { - using var slice = RawData; var bitOffset = Binary.BytesToBits(Offset + 1) + Binary.Four; - return (byte)slice.ReadBits(bitOffset, Binary.Four, Binary.BitOrder.MostSignificant); + return (byte)this.ReadBits(bitOffset, Binary.Four, Binary.BitOrder.MostSignificant); } set { - using var slice = RawData; var bitOffset = Binary.BytesToBits(Offset + 1) + Binary.Four; - slice.WriteBits(bitOffset, Binary.Four, value, Binary.BitOrder.MostSignificant); + this.WriteBits(bitOffset, Binary.Four, value, Binary.BitOrder.MostSignificant); } } diff --git a/Codecs/Image/Jpeg/JpegCodec.cs b/Codecs/Image/Jpeg/JpegCodec.cs index affaca25..17654bea 100644 --- a/Codecs/Image/Jpeg/JpegCodec.cs +++ b/Codecs/Image/Jpeg/JpegCodec.cs @@ -10,6 +10,19 @@ public class JpegCodec : ImageCodec, IEncoder, IDecoder { const int ComponentCount = 3; + public static ImageFormat DefaultImageFormat + { + get => new + ( + Binary.ByteOrder.Big, + DataLayout.Packed, + new JpegComponent(0, 1, 8), + new JpegComponent(0, 2, 8), + new JpegComponent(0, 3, 8), + new JpegComponent(0, 4, 8) + ); + } + public JpegCodec() : base("JPEG", Binary.ByteOrder.Little, ComponentCount, Binary.BitsPerByte) { diff --git a/Codecs/Image/Jpeg/JpegComponent.cs b/Codecs/Image/Jpeg/JpegComponent.cs index f4fef905..8efb3557 100644 --- a/Codecs/Image/Jpeg/JpegComponent.cs +++ b/Codecs/Image/Jpeg/JpegComponent.cs @@ -1,6 +1,4 @@ -using Media.Codec; - -namespace Codec.Jpeg; +namespace Media.Codec.Jpeg; public class JpegComponent : MediaComponent { diff --git a/Codecs/Image/Jpeg/JpegImage.cs b/Codecs/Image/Jpeg/JpegImage.cs index f0924729..cd96dde3 100644 --- a/Codecs/Image/Jpeg/JpegImage.cs +++ b/Codecs/Image/Jpeg/JpegImage.cs @@ -5,7 +5,6 @@ using Media.Common; using System.Linq; using Media.Common.Collections.Generic; -using Codec.Jpeg; using Codec.Jpeg.Markers; using Codec.Jpeg.Classes; @@ -13,18 +12,19 @@ namespace Media.Codec.Jpeg; public class JpegImage : Image { - public readonly bool Progressive; + internal readonly JpegState JpegState; public readonly ConcurrentThesaurus Markers; public JpegImage(ImageFormat imageFormat, int width, int height) : base(imageFormat, width, height, new JpegCodec()) { + JpegState = new JpegState(Jpeg.Markers.StartOfBaselineFrame, 0, 63, 0, 0); } - private JpegImage(ImageFormat imageFormat, int width, int height, MemorySegment data, bool progressive, ConcurrentThesaurus markers) + private JpegImage(ImageFormat imageFormat, int width, int height, MemorySegment data, JpegState jpegState, ConcurrentThesaurus markers) : base(imageFormat, width, height, data, new JpegCodec()) { - Progressive = progressive; + JpegState = jpegState; Markers = markers; } @@ -34,7 +34,7 @@ public static JpegImage FromStream(Stream stream) ImageFormat imageFormat = default; MemorySegment dataSegment = default; MemorySegment thumbnailData = default; - bool progressive = false; + JpegState jpegState = new(Jpeg.Markers.Prefix, 0, 63, 0, 0); ConcurrentThesaurus markers = new ConcurrentThesaurus(); foreach (var marker in JpegCodec.ReadMarkers(stream)) { @@ -57,16 +57,8 @@ public static JpegImage FromStream(Stream stream) case Jpeg.Markers.StartOfProgressiveArithmeticFrame: case Jpeg.Markers.StartOfProgressiveHuffmanFrame: case Jpeg.Markers.HeirarchicalProgression: - switch (marker.FunctionCode) - { - case Jpeg.Markers.StartOfDifferentialProgressiveHuffmanFrame: - case Jpeg.Markers.StartOfDifferentialProgressiveArithmeticFrame: - case Jpeg.Markers.StartOfProgressiveArithmeticFrame: - case Jpeg.Markers.StartOfProgressiveHuffmanFrame: - case Jpeg.Markers.HeirarchicalProgression: - progressive = true; - break; - } + + jpegState.StartOfFrameFunctionCode = marker.FunctionCode; StartOfFrame tag; @@ -77,9 +69,9 @@ public static JpegImage FromStream(Stream stream) else { tag = new StartOfFrame(marker); - } + } - int bitDepth = Binary.Clamp(Binary.BitsPerByte, Binary.BitsPerInteger, tag.P); + int bitDepth = Binary.Clamp(tag.P, Binary.BitsPerByte, Binary.BitsPerInteger); height = tag.Y; width = tag.X; @@ -115,7 +107,7 @@ public static JpegImage FromStream(Stream stream) } // Create the image format based on the SOF0 data - imageFormat = new ImageFormat(Binary.ByteOrder.Little, DataLayout.Planar, mediaComponents); + imageFormat = new ImageFormat(Binary.ByteOrder.Big, DataLayout.Planar, mediaComponents); imageFormat.HorizontalSamplingFactors = widths; imageFormat.VerticalSamplingFactors = heights; tag.Dispose(); @@ -123,9 +115,25 @@ public static JpegImage FromStream(Stream stream) continue; case Jpeg.Markers.StartOfScan: { - using var sos = new StartOfScan(marker); + using var sos = new StartOfScan(marker); + + jpegState.Ss = (byte)sos.Ss; + + if (sos.Se > 0) + jpegState.Se = (byte)sos.Se; + + jpegState.Ah = (byte)sos.Ah; + jpegState.Al = (byte)sos.Al; + + if (jpegState.StartOfFrameFunctionCode == Jpeg.Markers.StartOfProgressiveHuffmanFrame) + jpegState.Al = 1; - for(int ns = sos.Ns, i = 0; i < ns; ++i) + if (imageFormat == null) + imageFormat = JpegCodec.DefaultImageFormat; + + ///If Ns > 1, the following restriction shall be placed on the image components contained in the scan: + ///(The summation of all components products of thier respective values correspond to 10. + for (int ns = Binary.Min(4, sos.Ns), i = 0; i < ns; ++i) { using var scanComponentSelector = sos[i]; var jpegComponent = imageFormat.GetComponentById(scanComponentSelector.Csj) as JpegComponent ?? imageFormat.Components[i] as JpegComponent; @@ -138,6 +146,7 @@ public static JpegImage FromStream(Stream stream) var read = stream.Read(dataSegment.Array, dataSegment.Offset, dataSegment.Count); if (read < dataSegment.Count) dataSegment = dataSegment.Slice(0, read); + break; } case Jpeg.Markers.AppFirst: @@ -171,8 +180,8 @@ public static JpegImage FromStream(Stream stream) if (imageFormat == null || dataSegment == null && thumbnailData == null) throw new InvalidDataException("The provided stream does not contain valid JPEG image data."); - // Create and return the JpegImage - return new JpegImage(imageFormat, width, height, dataSegment ?? thumbnailData, progressive, markers); + // Create and return the JpegImage (Could use a Default Jpeg State?) + return new JpegImage(imageFormat, width, height, dataSegment ?? thumbnailData, jpegState, markers); } public void Save(Stream stream) @@ -206,9 +215,8 @@ public void Save(Stream stream) } } - // TODO revise to write correct start of scan header per coding. // Write the SOF marker - WriteStartOfFrame(Progressive ? Jpeg.Markers.StartOfProgressiveHuffmanFrame : Jpeg.Markers.StartOfBaselineFrame, stream); + WriteStartOfFrame(JpegState.StartOfFrameFunctionCode, stream); if (markerBuffer != null) { @@ -268,15 +276,16 @@ private void WriteStartOfFrame(byte functionCode, Stream stream) } private void WriteStartOfScan(Stream stream) - { + { var numberOfComponents = ImageFormat.Components.Length; + using var sos = new StartOfScan(numberOfComponents); - // Set the Ss, Se, Ah, and Al fields - // These values are typical for a baseline or progessive JPEG but not lossless. (1-7) - sos.Ss = 0; // Start of spectral selection - sos.Se = 63; // End of spectral selection - sos.Ah = 0; // Successive approximation high - sos.Al = 0; // Successive approximation low + + sos.Ss = JpegState.Ss; + sos.Se = JpegState.Se; + sos.Ah = JpegState.Ah; + sos.Al = JpegState.Al; + for (var i = 0; i < numberOfComponents; ++i) { var imageComponent = ImageFormat.Components[i]; diff --git a/Codecs/Image/Jpeg/JpegUnitTests.cs b/Codecs/Image/Jpeg/JpegUnitTests.cs index 8f0fdd6d..4d7815e5 100644 --- a/Codecs/Image/Jpeg/JpegUnitTests.cs +++ b/Codecs/Image/Jpeg/JpegUnitTests.cs @@ -250,25 +250,27 @@ public static void TestLoad() jpgImage.Save(outputNew); } - using var inputNew = new FileStream(saveFileName, FileMode.OpenOrCreate, FileAccess.Read); - - var destinationMarkers = new List(); - - foreach (var marker in JpegCodec.ReadMarkers(inputNew)) + using (var inputNew = new FileStream(saveFileName, FileMode.OpenOrCreate, FileAccess.Read)) { - DumpMarker(marker); - } - - inputNew.Seek(0, SeekOrigin.Begin); + var destinationMarkers = new List(); + + foreach (var marker in JpegCodec.ReadMarkers(inputNew)) + { + DumpMarker(marker); + } - using var newJpgImage = JpegImage.FromStream(inputNew); + inputNew.Seek(0, SeekOrigin.Begin); - if (newJpgImage.Width != jpgImage.Width || - newJpgImage.Height != jpgImage.Height || - newJpgImage.Progressive != jpgImage.Progressive || - newJpgImage.ImageFormat.Components.Length != jpgImage.ImageFormat.Components.Length || - newJpgImage.ImageFormat.Size != jpgImage.ImageFormat.Size) - throw new InvalidDataException(); + using (var newJpgImage = JpegImage.FromStream(inputNew)) + { + if (newJpgImage.Width != jpgImage.Width || + newJpgImage.Height != jpgImage.Height || + newJpgImage.JpegState != jpgImage.JpegState || + newJpgImage.ImageFormat.Components.Length != jpgImage.ImageFormat.Components.Length || + newJpgImage.ImageFormat.Size != jpgImage.ImageFormat.Size) + throw new InvalidDataException(); + } + } } } } \ No newline at end of file diff --git a/Codecs/Image/Jpeg/Marker.cs b/Codecs/Image/Jpeg/Marker.cs index b7429818..8aca77cd 100644 --- a/Codecs/Image/Jpeg/Marker.cs +++ b/Codecs/Image/Jpeg/Marker.cs @@ -37,7 +37,7 @@ public int Length public int MarkerLength => DataLength + 2; - public int DataOffset => Offset + PrefixBytes + LengthBytes; + public int DataOffset => DataLength > 0 ? Offset + PrefixBytes + LengthBytes : Count - 1; public MemorySegment Data => Count > PrefixBytes + LengthBytes ? this.Slice(PrefixBytes + LengthBytes) : Empty; diff --git a/Codecs/Image/Jpeg/Markers/StartOfFrame.cs b/Codecs/Image/Jpeg/Markers/StartOfFrame.cs index 31ca1712..ae9655ff 100644 --- a/Codecs/Image/Jpeg/Markers/StartOfFrame.cs +++ b/Codecs/Image/Jpeg/Markers/StartOfFrame.cs @@ -57,13 +57,14 @@ public int Nf { get { - var offset = Length + index * FrameComponent.Length; - return new FrameComponent(this.Slice(DataOffset + offset, FrameComponent.Length)); + var offset = DataOffset + Length + index * FrameComponent.Length; + using var slice = this.Slice(offset, FrameComponent.Length); + return new FrameComponent(slice); } set { - var offset = Length + index * FrameComponent.Length; - using var slice = this.Slice(DataOffset + offset, FrameComponent.Length); + var offset = DataOffset + Length + index * FrameComponent.Length; + using var slice = this.Slice(offset, FrameComponent.Length); value.CopyTo(slice); } } diff --git a/Codecs/Image/Jpeg/Markers/StartOfScan.cs b/Codecs/Image/Jpeg/Markers/StartOfScan.cs index 4d00242a..bf66c06e 100644 --- a/Codecs/Image/Jpeg/Markers/StartOfScan.cs +++ b/Codecs/Image/Jpeg/Markers/StartOfScan.cs @@ -5,7 +5,7 @@ namespace Codec.Jpeg.Markers; -public class StartOfScan : Marker +public sealed class StartOfScan : Marker { /// /// The number of bytes in the segment of this marker when is 0. @@ -56,15 +56,11 @@ public IEnumerable Components { get { - var offset = 1 + index * ScanComponentSelector.Length; - using var slice = this.Slice(DataOffset + offset, ScanComponentSelector.Length); + var offset = index * ScanComponentSelector.Length; + using var slice = this.Slice(DataOffset + 1 + offset, ScanComponentSelector.Length); return new ScanComponentSelector(slice); } - set - { - var offset = DataOffset + 1 + index * ScanComponentSelector.Length; - value.CopyTo(Array, offset); - } + set => value.CopyTo(Array, DataOffset + 1 + (index * ScanComponentSelector.Length)); } /// @@ -91,12 +87,12 @@ public int Se { get { - var offset = Ns * ScanComponentSelector.Length + 1; + var offset = 1 + Ns * ScanComponentSelector.Length + 1; return Array[DataOffset + offset]; } set { - var offset = Ns * ScanComponentSelector.Length + 1; + var offset = 1 + Ns * ScanComponentSelector.Length + 1; Array[DataOffset + offset] = (byte)value; } }