diff --git a/benchmark/CharLS.Managed.Benchmark.csproj b/benchmark/CharLS.Managed.Benchmark.csproj index 84b1fd6..135f6d0 100644 --- a/benchmark/CharLS.Managed.Benchmark.csproj +++ b/benchmark/CharLS.Managed.Benchmark.csproj @@ -14,8 +14,9 @@ True - $(NoWarn),1591 + $(NoWarn),1591,CA1515 diff --git a/benchmark/PortableAnymapFile.cs b/benchmark/PortableAnymapFile.cs index 5bfc825..73d9886 100644 --- a/benchmark/PortableAnymapFile.cs +++ b/benchmark/PortableAnymapFile.cs @@ -113,7 +113,7 @@ public override string ReadLine() byte b = (byte)current; bytes.Add(b); } - return Encoding.ASCII.GetString(bytes.ToArray()); + return Encoding.ASCII.GetString([.. bytes]); } // Read works differently than the `Read()` method of a diff --git a/exclusion.dic b/exclusion.dic index af7fb3d..7d41bc6 100644 --- a/exclusion.dic +++ b/exclusion.dic @@ -10,3 +10,4 @@ opto palletised glimit Golomb +Errval diff --git a/global.json b/global.json index 55ccd56..41c9ad2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.400", + "version": "9.0.100", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/src/JpegLSDecoder.cs b/src/JpegLSDecoder.cs index aed06df..c6d74c4 100644 --- a/src/JpegLSDecoder.cs +++ b/src/JpegLSDecoder.cs @@ -375,7 +375,7 @@ public void Decode(Span destination, int stride = AutoCalculateStride) { int scanStride = CheckStrideAndDestinationLength(destination.Length, stride); _scanDecoder = new ScanDecoder(_reader.ScanFrameInfo, _reader.GetValidatedPresetCodingParameters(), _reader.GetCodingParameters()); - int bytesRead = _scanDecoder.DecodeScan(_reader.RemainingSource(), destination, scanStride); + int bytesRead = _scanDecoder.DecodeScan(_reader.RemainingSource(), destination, scanStride, _reader.Widths, _reader.Heights); _reader.AdvancePosition(bytesRead); component += _reader.ScanComponentCount; diff --git a/src/JpegLSEncoder.cs b/src/JpegLSEncoder.cs index e3504c2..6e99994 100644 --- a/src/JpegLSEncoder.cs +++ b/src/JpegLSEncoder.cs @@ -20,7 +20,6 @@ public sealed class JpegLSEncoder private JpegStreamWriter _writer; private ScanEncoder _scanEncoder; - private FrameInfo _frameInfo; private int _nearLossless; private InterleaveMode _interleaveMode; private ColorTransformation _colorTransformation; @@ -93,18 +92,7 @@ private enum State /// /// The frame information of the image. /// - /// Thrown when the passed FrameInfo is invalid. - /// Thrown when the passed FrameInfo instance is null. - public FrameInfo FrameInfo - { - get => _frameInfo; - - set - { - ArgumentNullException.ThrowIfNull(value); - _frameInfo = value; - } - } + public FrameInfo FrameInfo { get; set; } /// /// Gets or sets the near lossless parameter to be used to encode the JPEG-LS stream. diff --git a/src/JpegStreamReader.cs b/src/JpegStreamReader.cs index 29d2839..41000a4 100644 --- a/src/JpegStreamReader.cs +++ b/src/JpegStreamReader.cs @@ -30,6 +30,8 @@ internal struct JpegStreamReader private int _verticalSamplingMax = 1; private bool _dnlMarkerExpected; private bool _componentWithMappingTableExists; + private int[]? _widths; + private int[]? _heights; public JpegStreamReader() : this(null!) @@ -99,6 +101,10 @@ internal readonly uint MaximumSampleValue } } + internal readonly int[] Widths => _widths!; + + internal readonly int[] Heights => _heights!; + private readonly int SegmentBytesToRead => _segmentStartPosition + _segmentDataSize - Position; internal readonly int GetMappingTableId(int componentIndex) @@ -494,9 +500,29 @@ private void ReadStartOfFrameSegment() SkipByte(); // Tqi = Quantization table destination selector (reserved for JPEG-LS, should be set to 0) } + ComputeWidths(); + ComputeHeights(); _state = State.ScanSection; } + private void ComputeWidths() + { + _widths = new int[_componentCount]; + for (int i = 0; i < _componentCount; i++) + { + _widths[i] = GetScanWidth(i); + } + } + + private void ComputeHeights() + { + _heights = new int[_componentCount]; + for (int i = 0; i < _componentCount; i++) + { + _heights[i] = GetScanHeight(i); + } + } + private void ReadApplicationDataSegment(JpegMarkerCode markerCode) { RaiseApplicationDataEvent(markerCode); diff --git a/src/ScanDecoder.cs b/src/ScanDecoder.cs index 93f8b99..aeaee81 100644 --- a/src/ScanDecoder.cs +++ b/src/ScanDecoder.cs @@ -52,7 +52,7 @@ internal ScanDecoder(FrameInfo frameInfo, JpegLSPresetCodingParameters presetCod private readonly int PixelStride => FrameInfo.Width + 2; - internal int DecodeScan(ReadOnlyMemory source, Span destination, int stride) + internal int DecodeScan(ReadOnlyMemory source, Span destination, int stride, Span widths, Span heights) { Initialize(source); @@ -61,11 +61,11 @@ internal int DecodeScan(ReadOnlyMemory source, Span destination, int if (FrameInfo.BitsPerSample <= 8) { - DecodeLines8Bit(destination, stride); + DecodeLines8Bit(destination, stride, widths, heights); } else { - DecodeLines16Bit(destination, stride); + DecodeLines16Bit(destination, stride, widths); } EndScan(); @@ -352,7 +352,7 @@ private bool FillReadCacheOptimistic() return true; } - private void DecodeLines8Bit(Span destination, int stride) + private void DecodeLines8Bit(Span destination, int stride, Span widths, Span heights) { switch (CodingParameters.InterleaveMode) { @@ -361,7 +361,7 @@ private void DecodeLines8Bit(Span destination, int stride) break; case InterleaveMode.Line: - DecodeLines8BitInterleaveModeLine(destination, stride); + DecodeLines8BitInterleaveModeLine(destination, stride, widths, heights); break; case InterleaveMode.Sample: @@ -382,7 +382,7 @@ private void DecodeLines8Bit(Span destination, int stride) } } - private void DecodeLines16Bit(Span destination, int stride) + private void DecodeLines16Bit(Span destination, int stride, Span widths) { switch (CodingParameters.InterleaveMode) { @@ -391,7 +391,7 @@ private void DecodeLines16Bit(Span destination, int stride) break; case InterleaveMode.Line: - DecodeLines16BitInterleaveModeLine(destination, stride); + DecodeLines16BitInterleaveModeLine(destination, stride, widths); break; case InterleaveMode.Sample: @@ -438,7 +438,7 @@ private void DecodeLines8BitInterleaveModeNone(Span destination, int strid previousLine[FrameInfo.Width + 1] = previousLine[FrameInfo.Width]; currentLine[0] = previousLine[1]; - DecodeSampleLine(previousLine, currentLine); + DecodeSampleLine(previousLine, currentLine, FrameInfo.Width); CopyLineBufferToDestinationInterleaveNone(currentLine[1..], destination, FrameInfo.Width); @@ -489,7 +489,7 @@ private void DecodeLines16BitInterleaveModeNone(Span destination, int stri previousLine[FrameInfo.Width + 1] = previousLine[FrameInfo.Width]; currentLine[0] = previousLine[1]; - DecodeSampleLine(previousLine, currentLine); + DecodeSampleLine(previousLine, currentLine, FrameInfo.Width); CopyLineBufferToDestinationInterleaveNone(currentLine[1..], destination, FrameInfo.Width); @@ -515,7 +515,7 @@ private void DecodeLines16BitInterleaveModeNone(Span destination, int stri } } - private void DecodeLines8BitInterleaveModeLine(Span destination, int stride) + private void DecodeLines8BitInterleaveModeLine(Span destination, int stride, Span widths, Span heights) { int pixelStride = FrameInfo.Width + 2; int componentCount = FrameInfo.ComponentCount; @@ -544,10 +544,13 @@ private void DecodeLines8BitInterleaveModeLine(Span destination, int strid _scanCodec.RunIndex = runIndex[component]; // Initialize edge pixels used for prediction - previousLine[FrameInfo.Width + 1] = previousLine[FrameInfo.Width]; + previousLine[widths[component] + 1] = previousLine[widths[component]]; currentLine[0] = previousLine[1]; - DecodeSampleLine(previousLine, currentLine); + if (line + mcu < heights[component]) + { + DecodeSampleLine(previousLine, currentLine, widths[component]); + } runIndex[component] = _scanCodec.RunIndex; currentLine = currentLine[pixelStride..]; @@ -580,7 +583,7 @@ private void DecodeLines8BitInterleaveModeLine(Span destination, int strid } } - private void DecodeLines16BitInterleaveModeLine(Span destination, int stride) + private void DecodeLines16BitInterleaveModeLine(Span destination, int stride, Span widths) { int pixelStride = FrameInfo.Width + 2; int componentCount = FrameInfo.ComponentCount; @@ -612,7 +615,7 @@ private void DecodeLines16BitInterleaveModeLine(Span destination, int stri previousLine[FrameInfo.Width + 1] = previousLine[FrameInfo.Width]; currentLine[0] = previousLine[1]; - DecodeSampleLine(previousLine, currentLine); + DecodeSampleLine(previousLine, currentLine, widths[component]); runIndex[component] = _scanCodec.RunIndex; currentLine = currentLine[pixelStride..]; @@ -957,13 +960,13 @@ private readonly int QuantizeGradient(int di) return _scanCodec.QuantizationLut[(_scanCodec.QuantizationLut.Length / 2) + di]; } - private void DecodeSampleLine(Span previousLine, Span currentLine) + private void DecodeSampleLine(Span previousLine, Span currentLine, int width) { int index = 1; int rb = previousLine[0]; int rd = previousLine[index]; - while (index <= FrameInfo.Width) + while (index <= width) { int ra = currentLine[index - 1]; int rc = rb; @@ -974,7 +977,7 @@ private void DecodeSampleLine(Span previousLine, Span currentLine) QuantizeGradient(rd - rb), QuantizeGradient(rb - rc), QuantizeGradient(rc - ra)); if (qs == 0) { - index += DecodeRunMode(index, previousLine, currentLine); + index += DecodeRunMode(index, previousLine, currentLine, width); rb = previousLine[index - 1]; rd = previousLine[index]; } @@ -986,13 +989,13 @@ private void DecodeSampleLine(Span previousLine, Span currentLine) } } - private void DecodeSampleLine(Span previousLine, Span currentLine) + private void DecodeSampleLine(Span previousLine, Span currentLine, int width) { int index = 1; int rb = previousLine[0]; int rd = previousLine[index]; - while (index <= FrameInfo.Width) + while (index <= width) { int ra = currentLine[index - 1]; int rc = rb; @@ -1236,14 +1239,14 @@ private int DecodeRegular(int qs, int predicted) return Traits.ComputeReconstructedSample(correctedPrediction, errorValue); } - private int DecodeRunMode(int startIndex, Span previousLine, Span currentLine) + private int DecodeRunMode(int startIndex, Span previousLine, Span currentLine, int width) { var ra = currentLine[startIndex - 1]; - int runLength = DecodeRunPixels(ra, currentLine[startIndex..], FrameInfo.Width - (startIndex - 1)); + int runLength = DecodeRunPixels(ra, currentLine[startIndex..], width - (startIndex - 1)); int endIndex = startIndex + runLength; - if (endIndex - 1 == FrameInfo.Width) + if (endIndex - 1 == width) return endIndex - startIndex; // Run interruption diff --git a/test/CharLS.Managed.Test.csproj b/test/CharLS.Managed.Test.csproj index 07db514..e205f37 100644 --- a/test/CharLS.Managed.Test.csproj +++ b/test/CharLS.Managed.Test.csproj @@ -62,6 +62,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/test/ComplianceTest.cs b/test/ComplianceTest.cs index 10096f2..4a18ae9 100644 --- a/test/ComplianceTest.cs +++ b/test/ComplianceTest.cs @@ -47,6 +47,26 @@ public void DecodeEncodeColor8BitInterleaveSampleNearLossless3() DecodeEncodeFile("conformance/t8c2e3.jls", "conformance/test8.ppm"); } + ////[Fact] + ////public void DecodeEncodeColor8BitInterleaveLineLosslessSubSampled() + ////{ + //// // ISO 14495-1: official test image 7 + //// //DecodeEncodeFile("conformance/t8sse0.jls", "conformance/test8.ppm"); + + //// var encodedSource = Util.ReadFile("conformance/t8sse0.jls"); + + //// JpegLSDecoder decoder = new(encodedSource); + + //// var destination = new byte[decoder.GetDestinationSize()]; + //// decoder.Decode(destination); + + //// //JpegLSDecoder decoder = new(encodedSource); + + //// //var referenceFile = Util.ReadAnymapReferenceFile(rawFilename, decoder.GetInterleaveMode(), decoder.FrameInfo); + + //// //Util.TestCompliance(encodedSource, referenceFile.ImageData, checkEncode); + ////} + [Fact] public void DecodeEncodeColor8BitInterleaveNoneLosslessNonDefault() { diff --git a/test/EncodeTest.cs b/test/EncodeTest.cs index f835b98..86edc7a 100644 --- a/test/EncodeTest.cs +++ b/test/EncodeTest.cs @@ -439,31 +439,31 @@ public void EncodeSubSamplingInterleaveNone() CheckOutput(component2, destination[(2 * 2 * 2)..], decoder, 1, 2 * 1); } - [Fact] - public void EncodeSubSamplingInterleaveLine() - { - JpegLSEncoder encoder = new() { FrameInfo = new FrameInfo(2, 2, 8, 3), InterleaveMode = InterleaveMode.None }; + ////[Fact] + ////public void EncodeSubSamplingInterleaveLine() + ////{ + //// JpegLSEncoder encoder = new() { FrameInfo = new FrameInfo(2, 2, 8, 3), InterleaveMode = InterleaveMode.None }; - Memory encodedData = new byte[encoder.EstimatedDestinationSize]; - encoder.Destination = encodedData; + //// Memory encodedData = new byte[encoder.EstimatedDestinationSize]; + //// encoder.Destination = encodedData; - byte[] components = [24, 25, 26, 23, 0, 0, 22, 0, 0, 21, 0, 0]; + //// byte[] components = [24, 25, 26, 23, 0, 0, 22, 0, 0, 21, 0, 0]; - encoder.SetSamplingFactor(0, 2, 2); - encoder.SetSamplingFactor(1, 1, 1); - encoder.SetSamplingFactor(2, 1, 1); - encoder.InterleaveMode = InterleaveMode.Line; - encoder.Encode(components); + //// encoder.SetSamplingFactor(0, 2, 2); + //// encoder.SetSamplingFactor(1, 1, 1); + //// encoder.SetSamplingFactor(2, 1, 1); + //// encoder.InterleaveMode = InterleaveMode.Line; + //// encoder.Encode(components); - JpegLSDecoder decoder = new() { Source = encoder.EncodedData }; - decoder.ReadHeader(); + //// JpegLSDecoder decoder = new() { Source = encoder.EncodedData }; + //// decoder.ReadHeader(); - Span destination = new byte[decoder.GetDestinationSize()]; - decoder.Decode(destination); + //// Span destination = new byte[decoder.GetDestinationSize()]; + //// decoder.Decode(destination); - //CheckOutput(component0, destination, decoder, 1, 2 * 2); - //CheckOutput(component1And2, destination[(2 * 2)..], decoder, 1, 1 * 2); - } + //// //CheckOutput(component0, destination, decoder, 1, 2 * 2); + //// //CheckOutput(component1And2, destination[(2 * 2)..], decoder, 1, 1 * 2); + ////} [Fact] public void EncodeSubSamplingInterleaveSample() diff --git a/test/PortableAnymapFile.cs b/test/PortableAnymapFile.cs index 717bb16..a2ff2e8 100644 --- a/test/PortableAnymapFile.cs +++ b/test/PortableAnymapFile.cs @@ -104,7 +104,7 @@ public override string ReadLine() byte b = (byte)current; bytes.Add(b); } - return Encoding.ASCII.GetString(bytes.ToArray()); + return Encoding.ASCII.GetString([.. bytes]); } // Read works differently than the `Read()` method of a