• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

team-charls / charls-dotnet / 21352226215

26 Jan 2026 09:13AM UTC coverage: 94.401% (-0.1%) from 94.536%
21352226215

Pull #61

github

web-flow
Merge 96c923b5d into 2dbe1f389
Pull Request #61: Add support for subsampling

1415 of 1556 branches covered (90.94%)

Branch coverage included in aggregate %.

133 of 138 new or added lines in 7 files covered. (96.38%)

1 existing line in 1 file now uncovered.

3441 of 3588 relevant lines covered (95.9%)

780436.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.44
/src/JpegStreamReader.cs
1
// Copyright (c) Team CharLS.
2
// SPDX-License-Identifier: BSD-3-Clause
3

4
using System.Buffers.Binary;
5

6
using static CharLS.Managed.Algorithm;
7

8
namespace CharLS.Managed;
9

10
internal struct JpegStreamReader
11
{
12
    private const int JpegRestartMarkerBase = 0xD0; // RSTm: Marks the next restart interval (range is D0 - D7)
13
    private const int JpegRestartMarkerRange = 8;
14
    private const int PcTableIdEntrySizeBytes = 3;
15

16
    private readonly object _eventSender;
17
    private readonly List<ComponentInfo> _componentInfos;
18
    private readonly List<MappingTableEntry> _mappingTables = [];
813✔
19

20
    private State _state;
21
    private int _nearLossless;
22
    private int _segmentDataSize;
23
    private int _segmentStartPosition;
24
    private int _width;
25
    private int _height;
26
    private int _bitsPerSample;
27
    private int _componentCount;
28
    private int _readComponentCount;
29
    private int _horizontalSamplingMax = 1;
813✔
30
    private int _verticalSamplingMax = 1;
813✔
31
    private bool _dnlMarkerExpected;
32
    private bool _componentWithMappingTableExists;
33
    private int[]? _widths;
34
    private int[]? _heights;
35

36
    public JpegStreamReader()
37
        : this(null!)
240✔
38
    {
39
    }
240✔
40

41
    internal JpegStreamReader(object eventSender)
42
    {
43
        _eventSender = eventSender;
813✔
44
        _componentInfos = [];
813✔
45
    }
813✔
46

47
    public event EventHandler<CommentEventArgs>? Comment;
48

49
    public event EventHandler<ApplicationDataEventArgs>? ApplicationData;
50

51
    private enum State
52
    {
53
        BeforeStartOfImage,
54
        HeaderSection,
55
        SpiffHeaderSection,
56
        FrameSection,
57
        ScanSection,
58
        BitStreamSection,
59
        AfterEndOfImage
60
    }
61

62
    internal readonly int ComponentCount => _componentInfos.Count;
885✔
63

64
    internal readonly bool EndOfImage => _state == State.AfterEndOfImage;
507✔
65

66
    internal readonly FrameInfo FrameInfo => new(_width, _height, _bitsPerSample, _componentCount);
4,179✔
67

68
    internal ReadOnlyMemory<byte> Source { get; set; }
53,313✔
69

70
    internal int Position { get; private set; }
83,787✔
71

72
    internal JpegLSPresetCodingParameters? JpegLSPresetCodingParameters { get; private set; }
2,544✔
73

74
    internal SpiffHeader? SpiffHeader { get; private set; }
807✔
75

76
    internal InterleaveMode ScanInterleaveMode { get; private set; }
4,437✔
77

78
    internal int ScanComponentCount { get; private set; }
3,423✔
79

80
    internal readonly FrameInfo ScanFrameInfo =>
81
        new(GetScanWidth(_readComponentCount - ScanComponentCount), GetScanHeight(_readComponentCount - ScanComponentCount), _bitsPerSample, ScanComponentCount);
522✔
82

83
    internal CompressedDataFormat CompressedDataFormat { get; private set; }
717✔
84

85
    internal ColorTransformation ColorTransformation { get; private set; }
1,374✔
86

87
    internal readonly int MappingTableCount => _mappingTables.Count;
33✔
88

89
    internal int RestartInterval { get; private set; }
591✔
90

91
    internal readonly uint MaximumSampleValue
92
    {
93
        get
94
        {
95
            if (JpegLSPresetCodingParameters != null && JpegLSPresetCodingParameters.MaximumSampleValue != 0)
816✔
96
            {
97
                return (uint)JpegLSPresetCodingParameters.MaximumSampleValue;
48✔
98
            }
99

100
            return (uint)CalculateMaximumSampleValue(_bitsPerSample);
768✔
101
        }
102
    }
103

104
    internal readonly int[] Widths => _widths!;
522✔
105

106
    internal readonly int[] Heights => _heights!;
522✔
107

108
    private readonly int SegmentBytesToRead => _segmentStartPosition + _segmentDataSize - Position;
276✔
109

110
    internal readonly int GetMappingTableId(int componentIndex)
111
    {
112
        return _componentInfos[componentIndex].MappingTableId;
24✔
113
    }
114

115
    internal readonly int FindMappingTableIndex(int mappingTableId)
116
    {
117
        return _mappingTables.FindIndex(entry => entry.MappingTableId == mappingTableId);
42✔
118
    }
119

120
    internal readonly MappingTableInfo GetMappingTableInfo(int mappingTableIndex)
121
    {
122
        var entry = _mappingTables[mappingTableIndex];
9✔
123
        return new MappingTableInfo { EntrySize = entry.EntrySize, TableId = entry.MappingTableId, DataSize = entry.DataSize };
9✔
124
    }
125

126
    internal readonly ReadOnlyMemory<byte> GetMappingTableData(int mappingTableIndex)
127
    {
128
        var entry = _mappingTables[mappingTableIndex];
9✔
129
        return entry.GetData();
9✔
130
    }
131

132
    internal void AdvancePosition(int count)
133
    {
134
        Debug.Assert(count >= 0);
135
        Debug.Assert(Position + count <= Source.Length);
136
        Position += count;
504✔
137
    }
504✔
138

139
    internal JpegLSPresetCodingParameters GetValidatedPresetCodingParameters()
140
    {
141
        JpegLSPresetCodingParameters ??= new JpegLSPresetCodingParameters();
522✔
142

143
        if (!JpegLSPresetCodingParameters.TryMakeExplicit(CalculateMaximumSampleValue(FrameInfo.BitsPerSample), _nearLossless, out var validatedCodingParameters))
522!
144
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterJpegLSPresetParameters);
×
145

146
        return validatedCodingParameters;
522✔
147
    }
148

149
    internal readonly int GetNearLossless(int componentIndex)
150
    {
151
        return _componentInfos[componentIndex].NearLossless;
354✔
152
    }
153

154
    internal readonly InterleaveMode GetInterleaveMode(int componentIndex)
155
    {
156
        return _componentInfos[componentIndex].InterleaveMode;
351✔
157
    }
158

159
    internal CodingParameters GetCodingParameters()
160
    {
161
        return new CodingParameters
522✔
162
        {
522✔
163
            NearLossless = _nearLossless,
522✔
164
            InterleaveMode = ScanInterleaveMode,
522✔
165
            RestartInterval = RestartInterval,
522✔
166
            ColorTransformation = ColorTransformation
522✔
167
        };
522✔
168
    }
169

170
    internal readonly ReadOnlyMemory<byte> RemainingSource()
171
    {
172
        Debug.Assert(_state == State.BitStreamSection);
173
        return Source[Position..];
522✔
174
    }
175

176
    internal void ReadHeader(bool readSpiffHeader)
177
    {
178
        Debug.Assert(_state != State.ScanSection);
179

180
        if (_state == State.BeforeStartOfImage)
801✔
181
        {
182
            if (ReadNextMarkerCode() != JpegMarkerCode.StartOfImage)
789!
183
                ThrowHelper.ThrowInvalidDataException(ErrorCode.StartOfImageMarkerNotFound);
×
184

185
            _state = State.HeaderSection;
774✔
186
        }
187

188
        for (; ; )
189
        {
190
            var markerCode = ReadNextMarkerCode();
1,758✔
191
            if (markerCode == JpegMarkerCode.EndOfImage)
1,758✔
192
            {
193
                if (IsAbbreviatedFormatForTableSpecificationData())
12✔
194
                {
195
                    _state = State.AfterEndOfImage;
6✔
196
                    CompressedDataFormat = CompressedDataFormat.AbbreviatedTableSpecification;
6✔
197
                    return;
6✔
198
                }
199

200
                ThrowHelper.ThrowInvalidDataException(ErrorCode.UnexpectedEndOfImageMarker);
3✔
201
            }
202

203
            ValidateMarkerCode(markerCode);
1,746✔
204

205
            ReadSegmentSize();
1,728✔
206

207
            switch (_state)
1,716✔
208
            {
209
                case State.SpiffHeaderSection:
210
                    ReadSpiffDirectoryEntry(markerCode);
12✔
211
                    break;
9✔
212

213
                default:
214
                    ReadMarkerSegment(markerCode, readSpiffHeader);
1,704✔
215
                    break;
216
            }
217

218
            Debug.Assert(SegmentBytesToRead == 0);
219

220
            if (_state == State.HeaderSection && SpiffHeader != null)
1,605✔
221
            {
222
                _state = State.SpiffHeaderSection;
18✔
223
                return;
18✔
224
            }
225

226
            if (_state == State.BitStreamSection)
1,587✔
227
            {
228
                if (_height == 0)
615✔
229
                {
230
                    FindAndReadDefineNumberOfLinesSegment();
24✔
231
                }
232

233
                CheckWidth();
603✔
234
                CheckCodingParameters();
600✔
235
                return;
594✔
236
            }
237
        }
238
    }
239

240
    internal void ReadNextStartOfScan()
241
    {
242
        Debug.Assert(_state == State.BitStreamSection);
243
        _state = State.ScanSection;
192✔
244

245
        do
246
        {
247
            var markerCode = ReadNextMarkerCode();
219✔
248
            ValidateMarkerCode(markerCode);
219✔
249
            ReadSegmentSize();
216✔
250
            ReadMarkerSegment(markerCode, false);
216✔
251

252
            Debug.Assert(SegmentBytesToRead == 0); // All segment data should be processed.
253
        }
254
        while (_state == State.ScanSection);
216✔
255
    }
189✔
256

257
    internal void ReadEndOfImage()
258
    {
259
        Debug.Assert(_state == State.BitStreamSection);
260

261
        var markerCode = ReadNextMarkerCode();
324✔
262
        if (markerCode != JpegMarkerCode.EndOfImage)
324!
263
            ThrowHelper.ThrowInvalidDataException(ErrorCode.EndOfImageMarkerNotFound);
×
264

265
        Debug.Assert(CompressedDataFormat == CompressedDataFormat.Unknown);
266
        CompressedDataFormat = _componentWithMappingTableExists && HasExternalMappingTableIds() ?
324✔
267
            CompressedDataFormat.AbbreviatedImageData : CompressedDataFormat.Interchange;
324✔
268

269
        _state = State.AfterEndOfImage;
324✔
270
    }
324✔
271

272
    private byte ReadByte()
273
    {
274
        if (Position == Source.Span.Length)
24,117✔
275
            ThrowHelper.ThrowInvalidDataException(ErrorCode.NeedMoreData);
6✔
276

277
        return Source.Span[Position++];
24,111✔
278
    }
279

280
    private void SkipByte()
281
    {
282
        if (Position == Source.Span.Length)
1,539!
283
            ThrowHelper.ThrowInvalidDataException(ErrorCode.NeedMoreData);
×
284

285
        Position++;
1,539✔
286
    }
1,539✔
287

288
    private int ReadUint16()
289
    {
290
        int value = ReadByte() * 256;
3,678✔
291
        return value + ReadByte();
3,678✔
292
    }
293

294
    private int ReadUint24()
295
    {
296
        uint value = (uint)ReadByte() << 16;
21✔
297
        return (int)(value + (uint)ReadUint16());
21✔
298
    }
299

300
    private int ReadUint32()
301
    {
302
        uint value = BinaryPrimitives.ReadUInt32BigEndian(Source[Position..].Span);
108✔
303
        Position += 4;
108✔
304

305
        if (value > int.MaxValue)
108✔
306
            ThrowHelper.ThrowInvalidDataException(ErrorCode.ParameterValueNotSupported);
9✔
307

308
        return (int)value;
99✔
309
    }
310

311
    private JpegMarkerCode ReadNextMarkerCode()
312
    {
313
        byte value = ReadByte();
3,090✔
314
        if (value != Constants.JpegMarkerStartByte)
3,087✔
315
            ThrowHelper.ThrowInvalidDataException(ErrorCode.JpegMarkerStartByteNotFound);
12✔
316

317
        // Read all preceding 0xFF fill values until a non 0xFF value has been found. (see T.81, B.1.1.2)
318
        do
319
        {
320
            value = ReadByte();
3,087✔
321
        }
322
        while (value == Constants.JpegMarkerStartByte);
3,087✔
323

324
        return (JpegMarkerCode)value;
3,075✔
325
    }
326

327
    private void ReadSegmentSize()
328
    {
329
        const int segmentLength = 2; // The segment size also includes the length of the segment length bytes.
330
        int segmentSize = ReadUint16();
1,962✔
331
        _segmentDataSize = segmentSize - segmentLength;
1,959✔
332
        if (segmentSize < segmentLength || Position + _segmentDataSize > Source.Length)
1,959✔
333
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidMarkerSegmentSize);
9✔
334

335
        _segmentStartPosition = Position;
1,950✔
336
    }
1,950✔
337

338
    private void ReadMarkerSegment(JpegMarkerCode markerCode, bool readSpiffHeader)
339
    {
340
        switch (markerCode)
341
        {
342
            case JpegMarkerCode.StartOfFrameJpegLS:
343
                ReadStartOfFrameSegment();
672!
344
                break;
651✔
345

346
            case JpegMarkerCode.StartOfScan:
347
                ReadStartOfScanSegment();
825✔
348
                break;
804✔
349

350
            case JpegMarkerCode.Comment:
351
                ReadCommentSegment();
21✔
352
                break;
18✔
353

354
            case JpegMarkerCode.JpegLSPresetParameters:
355
                ReadPresetParametersSegment();
162✔
356
                break;
105✔
357

358
            case JpegMarkerCode.DefineRestartInterval:
359
                ReadDefineRestartIntervalSegment();
63✔
360
                break;
57✔
361

362
            case JpegMarkerCode.DefineNumberOfLines:
363
                _ = ReadDefineNumberOfLinesSegment();
12✔
364
                _dnlMarkerExpected = false;
12✔
365
                break;
12✔
366

367
            case JpegMarkerCode.ApplicationData0:
368
            case JpegMarkerCode.ApplicationData1:
369
            case JpegMarkerCode.ApplicationData2:
370
            case JpegMarkerCode.ApplicationData3:
371
            case JpegMarkerCode.ApplicationData4:
372
            case JpegMarkerCode.ApplicationData5:
373
            case JpegMarkerCode.ApplicationData6:
374
            case JpegMarkerCode.ApplicationData7:
375
            case JpegMarkerCode.ApplicationData9:
376
            case JpegMarkerCode.ApplicationData10:
377
            case JpegMarkerCode.ApplicationData11:
378
            case JpegMarkerCode.ApplicationData12:
379
            case JpegMarkerCode.ApplicationData13:
380
            case JpegMarkerCode.ApplicationData14:
381
            case JpegMarkerCode.ApplicationData15:
382
                ReadApplicationDataSegment(markerCode);
90✔
383
                break;
90✔
384

385
            case JpegMarkerCode.ApplicationData8:
386
                ReadApplicationData8Segment(readSpiffHeader);
75✔
387
                break;
388
        }
389
    }
75✔
390

391
    private readonly void ValidateMarkerCode(JpegMarkerCode markerCode)
392
    {
393
        // ISO/IEC 14495-1, C.1.1. defines the following markers as valid for a JPEG-LS byte stream:
394
        // SOF55, LSE, SOI, EOI, SOS, DNL, DRI, RSTm, APPn and COM.
395
        // All other markers shall not be present.
396
        switch (markerCode)
397
        {
398
            case JpegMarkerCode.StartOfScan:
399
                if (_state != State.ScanSection)
828✔
400
                    ThrowHelper.ThrowInvalidDataException(ErrorCode.UnexpectedStartOfScanMarker);
3✔
401

402
                return;
825✔
403

404
            case JpegMarkerCode.StartOfFrameJpegLS:
405
                if (_state == State.ScanSection)
681✔
406
                    ThrowHelper.ThrowInvalidDataException(ErrorCode.DuplicateStartOfFrameMarker);
3✔
407

408
                return;
678✔
409

410
            case JpegMarkerCode.DefineRestartInterval:
411
            case JpegMarkerCode.JpegLSPresetParameters:
412
            case JpegMarkerCode.Comment:
413
            case JpegMarkerCode.ApplicationData0:
414
            case JpegMarkerCode.ApplicationData1:
415
            case JpegMarkerCode.ApplicationData2:
416
            case JpegMarkerCode.ApplicationData3:
417
            case JpegMarkerCode.ApplicationData4:
418
            case JpegMarkerCode.ApplicationData5:
419
            case JpegMarkerCode.ApplicationData6:
420
            case JpegMarkerCode.ApplicationData7:
421
            case JpegMarkerCode.ApplicationData8:
422
            case JpegMarkerCode.ApplicationData9:
423
            case JpegMarkerCode.ApplicationData10:
424
            case JpegMarkerCode.ApplicationData11:
425
            case JpegMarkerCode.ApplicationData12:
426
            case JpegMarkerCode.ApplicationData13:
427
            case JpegMarkerCode.ApplicationData14:
428
            case JpegMarkerCode.ApplicationData15:
429
                return;
429✔
430

431
            case JpegMarkerCode.DefineNumberOfLines:
432
                if (!_dnlMarkerExpected)
18✔
433
                    ThrowHelper.ThrowInvalidDataException(ErrorCode.UnexpectedDefineNumberOfLinesMarker);
6✔
434
                return;
12✔
435

436
            case JpegMarkerCode.StartOfImage:
437
                ThrowHelper.ThrowInvalidDataException(ErrorCode.DuplicateStartOfImageMarker);
3✔
438
                break;
439
        }
440

441
        // Check explicit for one of the other common JPEG encodings.
442
        if (IsKnownJpegSofMarker(markerCode))
6✔
443
            ThrowHelper.ThrowInvalidDataException(ErrorCode.EncodingNotSupported);
3✔
444

445
        if (IsRestartMarkerCode(markerCode))
3!
446
            ThrowHelper.ThrowInvalidDataException(ErrorCode.UnexpectedRestartMarker);
3✔
447

448
        ThrowHelper.ThrowInvalidDataException(ErrorCode.UnknownJpegMarkerFound);
×
449
    }
×
450

451
    private static bool IsRestartMarkerCode(JpegMarkerCode markerCode)
452
    {
453
        return (int)markerCode is >= JpegRestartMarkerBase and
3!
454
               < JpegRestartMarkerBase + JpegRestartMarkerRange;
3✔
455
    }
456

457
    private void ReadSpiffDirectoryEntry(JpegMarkerCode markerCode)
458
    {
459
        if (markerCode != JpegMarkerCode.ApplicationData8)
12✔
460
            ThrowHelper.ThrowInvalidDataException(ErrorCode.MissingEndOfSpiffDirectory);
3✔
461

462
        CheckMinimalSegmentSize(4);
9✔
463
        int spiffDirectoryType = ReadUint32();
9✔
464
        if (spiffDirectoryType == Constants.SpiffEndOfDirectoryEntryType)
9✔
465
        {
466
            CheckSegmentSize(6); // 4 + 2 for dummy SOI.
9✔
467
            _state = State.FrameSection;
9✔
468
        }
469

470
        SkipRemainingSegmentData();
9✔
471
    }
9✔
472

473
    private void ReadStartOfFrameSegment()
474
    {
475
        // A JPEG-LS Start of Frame (SOF) segment is documented in ISO/IEC 14495-1, C.2.2
476
        // This section references ISO/IEC 10918-1, B.2.2, which defines the normal JPEG SOF,
477
        // with some modifications.
478
        CheckMinimalSegmentSize(6);
672✔
479

480
        _bitsPerSample = ReadByte();
669✔
481
        if (!Validation.IsBitsPerSampleValid(_bitsPerSample))
669!
482
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterBitsPerSample);
×
483

484
        SetHeight(ReadUint16(), false);
669✔
485
        SetWidth(ReadUint16());
666✔
486

487
        _componentCount = ReadByte();
666✔
488
        if (_componentCount == 0)
666!
489
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterComponentCount);
×
490

491
        CheckSegmentSize(6 + (_componentCount * 3));
666✔
492

493
        for (int i = 0; i != _componentCount; i++)
4,350✔
494
        {
495
            // Component specification parameters
496
            byte componentIdentifier = ReadByte(); // Ci = Component identifier
1,524✔
497
            byte horizontalVerticalSamplingFactor = ReadByte(); // Hi + Vi = Horizontal sampling factor + Vertical sampling factor
1,524✔
498
            AddComponent(componentIdentifier, horizontalVerticalSamplingFactor);
1,524✔
499

500
            SkipByte(); // Tqi = Quantization table destination selector (reserved for JPEG-LS, should be set to 0)
1,521✔
501
        }
502

503
        ComputeWidths();
651✔
504
        ComputeHeights();
651✔
505
        _state = State.ScanSection;
651✔
506
    }
651✔
507

508
    private void ComputeWidths()
509
    {
510
        _widths = new int[_componentCount];
651✔
511
        for (int i = 0; i < _componentCount; i++)
4,338✔
512
        {
513
            _widths[i] = GetScanWidth(i);
1,518✔
514
        }
515
    }
651✔
516

517
    private void ComputeHeights()
518
    {
519
        _heights = new int[_componentCount];
651✔
520
        for (int i = 0; i < _componentCount; i++)
4,338✔
521
        {
522
            _heights[i] = GetScanHeight(i);
1,518✔
523
        }
524
    }
651✔
525

526
    private void ReadApplicationDataSegment(JpegMarkerCode markerCode)
527
    {
528
        RaiseApplicationDataEvent(markerCode);
90✔
529
        SkipRemainingSegmentData();
90✔
530
    }
90✔
531

532
    private void RaiseApplicationDataEvent(JpegMarkerCode markerCode)
533
    {
534
        try
535
        {
536
            ApplicationData?.Invoke(
165✔
537
                _eventSender, new ApplicationDataEventArgs(markerCode - JpegMarkerCode.ApplicationData0, Source.Slice(Position, _segmentDataSize)));
165✔
538
        }
165✔
539
        catch (Exception e)
×
540
        {
541
            throw ThrowHelper.CreateInvalidDataException(ErrorCode.CallbackFailed, e);
×
542
        }
543
    }
165✔
544

545
    private void ReadCommentSegment()
546
    {
547
        try
548
        {
549
            Comment?.Invoke(_eventSender, new CommentEventArgs(Source.Slice(Position, _segmentDataSize)));
21✔
550
        }
18✔
551
        catch (Exception e)
3✔
552
        {
553
            throw ThrowHelper.CreateInvalidDataException(ErrorCode.CallbackFailed, e);
3✔
554
        }
555

556
        SkipRemainingSegmentData();
18✔
557
    }
18✔
558

559
    private void ReadPresetParametersSegment()
560
    {
561
        CheckMinimalSegmentSize(1);
162✔
562

563
        byte type = ReadByte();
159✔
564
        switch ((JpegLSPresetParametersType)type)
159✔
565
        {
566
            case JpegLSPresetParametersType.PresetCodingParameters:
567
                ReadPresetCodingParameters();
57✔
568
                return;
57✔
569

570
            case JpegLSPresetParametersType.MappingTableSpecification:
571
                ReadMappingTableSpecification();
39✔
572
                return;
33✔
573

574
            case JpegLSPresetParametersType.MappingTableContinuation:
575
                ReadMappingTableContinuation();
9✔
576
                return;
3✔
577

578
            case JpegLSPresetParametersType.OversizeImageDimension:
579
                ReadOversizeImageDimension();
30✔
580
                return;
12✔
581
        }
582

583
        const byte jpegLSExtendedPresetParameterLast = 0xD; // defined in JPEG-LS Extended (ISO/IEC 14495-2) (first = 0x5)
584
        ThrowHelper.ThrowInvalidDataException(type <= jpegLSExtendedPresetParameterLast
24!
585
            ? ErrorCode.JpegLSPresetExtendedParameterTypeNotSupported
24✔
586
            : ErrorCode.InvalidJpegLSPresetParameterType);
24✔
587
    }
×
588

589
    private void ReadPresetCodingParameters()
590
    {
591
        CheckSegmentSize(1 + (5 * sizeof(short)));
57✔
592

593
        // Note: validation will be done, just before decoding as more info is needed for validation.
594
        JpegLSPresetCodingParameters = new JpegLSPresetCodingParameters
57✔
595
        {
57✔
596
            MaximumSampleValue = ReadUint16(),
57✔
597
            Threshold1 = ReadUint16(),
57✔
598
            Threshold2 = ReadUint16(),
57✔
599
            Threshold3 = ReadUint16(),
57✔
600
            ResetValue = ReadUint16()
57✔
601
        };
57✔
602
    }
57✔
603

604
    private void ReadMappingTableSpecification()
605
    {
606
        CheckMinimalSegmentSize(PcTableIdEntrySizeBytes);
39✔
607

608
        byte tableId = ReadByte();
39✔
609
        byte entrySize = ReadByte();
39✔
610

611
        AddMappingTable(tableId, entrySize, Source.Slice(Position, SegmentBytesToRead));
39✔
612
        SkipRemainingSegmentData();
33✔
613
    }
33✔
614

615
    private void ReadMappingTableContinuation()
616
    {
617
        CheckMinimalSegmentSize(PcTableIdEntrySizeBytes);
9✔
618

619
        byte tableId = ReadByte();
9✔
620
        byte entrySize = ReadByte();
9✔
621

622
        ExtendMappingTable(tableId, entrySize, Source.Slice(Position, SegmentBytesToRead));
9✔
623
        SkipRemainingSegmentData();
3✔
624
    }
3✔
625

626
    private void ReadOversizeImageDimension()
627
    {
628
        // Note: The JPEG-LS standard supports a 2,3 or 4 bytes for the size.
629
        const int pcAndDimensionBytes = 2;
630
        CheckMinimalSegmentSize(PcTableIdEntrySizeBytes);
30✔
631
        byte dimensionSize = ReadByte();
30✔
632

633
        int height;
634
        int width;
635
        switch (dimensionSize)
636
        {
637
            case 2:
638
                CheckSegmentSize(pcAndDimensionBytes + (sizeof(ushort) * 2));
12✔
639
                height = ReadUint16();
9✔
640
                width = ReadUint16();
9✔
641
                break;
9✔
642

643
            case 3:
644
                CheckSegmentSize(pcAndDimensionBytes + ((sizeof(ushort) + 1) * 2));
6✔
645
                height = ReadUint24();
3✔
646
                width = ReadUint24();
3✔
647
                break;
3✔
648

649
            case 4:
650
                CheckSegmentSize(pcAndDimensionBytes + (sizeof(uint) * 2));
9✔
651
                height = ReadUint32();
6✔
652
                width = ReadUint32();
3✔
653
                break;
3✔
654

655
            default:
656
                throw ThrowHelper.CreateInvalidDataException(ErrorCode.InvalidParameterJpegLSPresetParameters);
3✔
657
        }
658

659
        SetHeight(height, false);
15✔
660
        SetWidth(width);
15✔
661
    }
12✔
662

663
    private readonly void AddMappingTable(byte tableId, byte entrySize, ReadOnlyMemory<byte> tableData)
664
    {
665
        if (tableId == 0 || _mappingTables.FindIndex(entry => entry.MappingTableId == tableId) != -1)
45✔
666
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterMappingTableId);
6✔
667

668
        _mappingTables.Add(new MappingTableEntry(tableId, entrySize, tableData));
33✔
669
    }
33✔
670

671
    private readonly void ExtendMappingTable(byte tableId, byte entrySize, ReadOnlyMemory<byte> tableData)
672
    {
673
        int index = _mappingTables.FindIndex(entry => entry.MappingTableId == tableId);
15✔
674

675
        if (index == -1 || _mappingTables[index].EntrySize != entrySize)
9✔
676
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterMappingTableContinuation);
6✔
677

678
        MappingTableEntry tableEntry = _mappingTables[index];
3✔
679
        tableEntry.AddFragment(tableData);
3✔
680
        _mappingTables[index] = tableEntry;
3✔
681
    }
3✔
682

683
    private void ReadDefineRestartIntervalSegment()
684
    {
685
        // Note: The JPEG-LS standard supports a 2, 3 or 4 byte restart interval (see ISO/IEC 14495-1, C.2.5)
686
        //       The original JPEG standard only supports 2 bytes (16 bit big endian).
687
        RestartInterval = _segmentDataSize switch
63✔
688
        {
63✔
689
            2 => ReadUint16(),
42✔
690
            3 => ReadUint24(),
9✔
691
            4 => ReadUint32(),
9✔
692
            _ => throw ThrowHelper.CreateInvalidDataException(ErrorCode.InvalidMarkerSegmentSize)
3✔
693
        };
63✔
694
    }
57✔
695

696
    private int ReadDefineNumberOfLinesSegment()
697
    {
698
        // Note: The JPEG-LS standard supports a 2, 3 or 4 byte DNL segment (see ISO/IEC 14495-1, C.2.6)
699
        //       The original JPEG standard only supports 2 bytes (16 bit big endian).
700
        return _segmentDataSize switch
30!
701
        {
30✔
702
            2 => ReadUint16(),
15✔
703
            3 => ReadUint24(),
6✔
704
            4 => ReadUint32(),
9✔
705
            _ => throw ThrowHelper.CreateInvalidDataException(ErrorCode.InvalidMarkerSegmentSize)
×
706
        };
30✔
707
    }
708

709
    private void ReadStartOfScanSegment()
710
    {
711
        CheckMinimalSegmentSize(1);
825✔
712

713
        // ISO 10918-1, B2.3. defines the limits for the number of image components parameter in an SOS.
714
        int scanComponentCount = ReadByte();
825✔
715
        if (scanComponentCount < 1 || scanComponentCount > Constants.MaximumComponentCountInScan ||
825✔
716
            scanComponentCount > _componentCount - _readComponentCount)
825✔
717
        {
718
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterComponentCount);
9✔
719
        }
720

721
        ScanComponentCount = scanComponentCount;
816✔
722
        _readComponentCount += scanComponentCount;
816✔
723

724
        Span<byte> componentIds = stackalloc byte[scanComponentCount];
816✔
725
        Span<byte> mappingTableIds = stackalloc byte[scanComponentCount];
816✔
726

727
        CheckSegmentSize(4 + (2 * scanComponentCount));
816✔
728

729
        for (int i = 0; i != scanComponentCount; ++i)
4,098✔
730
        {
731
            componentIds[i] = ReadByte();
1,233✔
732
            mappingTableIds[i] = ReadByte();
1,233✔
733
        }
734

735
        _nearLossless = ReadByte(); // Read NEAR parameter
816✔
736
        if (_nearLossless > ComputeMaximumNearLossless((int)MaximumSampleValue))
816✔
737
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterNearLossless);
6✔
738

739
        ScanInterleaveMode = (InterleaveMode)ReadByte(); // Read ILV parameter
810✔
740
        CheckInterleaveMode(ScanInterleaveMode, scanComponentCount);
810✔
741

742
        for (int i = 0; i != scanComponentCount; ++i)
4,050✔
743
        {
744
            StoreComponentInfo(componentIds[i], mappingTableIds[i], (byte)_nearLossless, ScanInterleaveMode);
1,221✔
745
        }
746

747
        if ((ReadByte() & 0xFU) != 0) // Read Ah (no meaning) and Al (point transform).
804!
748
            ThrowHelper.ThrowInvalidDataException(ErrorCode.ParameterValueNotSupported);
×
749

750
        _state = State.BitStreamSection;
804✔
751
    }
804✔
752

753
    private void ReadApplicationData8Segment(bool readSpiffHeader)
754
    {
755
        RaiseApplicationDataEvent(JpegMarkerCode.ApplicationData8);
75✔
756

757
        if (_segmentDataSize == 5)
75✔
758
        {
759
            TryReadHPColorTransformSegment();
48✔
760
        }
761
        else if (readSpiffHeader && _segmentDataSize >= 30)
27✔
762
        {
763
            TryReadSpiffHeaderSegment();
18✔
764
        }
765

766
        SkipRemainingSegmentData();
75✔
767
    }
75✔
768

769
    private void TryReadHPColorTransformSegment()
770
    {
771
        Debug.Assert(SegmentBytesToRead == 5);
772

773
        Span<byte> mrfxTag = [(byte)'m', (byte)'r', (byte)'f', (byte)'x'];
48✔
774
        var tagBytes = ReadBytes(mrfxTag.Length);
48✔
775
        if (!mrfxTag.SequenceEqual(tagBytes.Span))
48!
776
            return;
×
777

778
        byte transformation = ReadByte();
48!
779
        switch (transformation)
780
        {
781
            case (byte)ColorTransformation.None:
782
            case (byte)ColorTransformation.HP1:
783
            case (byte)ColorTransformation.HP2:
784
            case (byte)ColorTransformation.HP3:
785
                ColorTransformation = (ColorTransformation)transformation;
48✔
786
                return;
48✔
787

788
            case 4: // RgbAsYuvLossy: the standard lossy RGB to YCbCr transform used in JPEG.
789
            case 5: // Matrix: transformation is controlled using a matrix that is also stored in the segment.
790
                ThrowHelper.ThrowInvalidDataException(ErrorCode.ColorTransformNotSupported);
×
791
                break;
×
792

793
            default:
794
                ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterColorTransformation);
×
795
                break;
796
        }
797
    }
×
798

799
    private void TryReadSpiffHeaderSegment()
800
    {
801
        Debug.Assert(_segmentDataSize >= 30);
802

803
        Span<byte> spiffTag = [(byte)'S', (byte)'P', (byte)'I', (byte)'F', (byte)'F', 0];
18✔
804
        var tagBytes = ReadBytes(spiffTag.Length);
18✔
805
        if (!spiffTag.SequenceEqual(tagBytes.Span))
18!
806
            return;
×
807

808
        byte highVersion = ReadByte();
18✔
809
        if (highVersion > Constants.SpiffMajorRevisionNumber)
18!
810
            return;  // Treat unknown versions as if the SPIFF header doesn't exist.
×
811
        SkipByte();  // low version
18✔
812

813
        SpiffHeader = new SpiffHeader
18✔
814
        {
18✔
815
            ProfileId = (SpiffProfileId)ReadByte(),
18✔
816
            ComponentCount = ReadByte(),
18✔
817
            Height = ReadUint32(),
18✔
818
            Width = ReadUint32(),
18✔
819
            ColorSpace = (SpiffColorSpace)ReadByte(),
18✔
820
            BitsPerSample = ReadByte(),
18✔
821
            CompressionType = (SpiffCompressionType)ReadByte(),
18✔
822
            ResolutionUnit = (SpiffResolutionUnit)ReadByte(),
18✔
823
            VerticalResolution = ReadUint32(),
18✔
824
            HorizontalResolution = ReadUint32()
18✔
825
        };
18✔
826
    }
18✔
827

828
    private ReadOnlyMemory<byte> ReadBytes(int byteCount)
829
    {
830
        var bytes = Source.Slice(Position, byteCount);
66✔
831
        Position += byteCount;
66✔
832
        return bytes;
66✔
833
    }
834

835
    private void SkipRemainingSegmentData()
836
    {
837
        Position += SegmentBytesToRead;
228✔
838
    }
228✔
839

840
    private readonly void CheckMinimalSegmentSize(int minimumSize)
841
    {
842
        if (minimumSize > _segmentDataSize)
1,746✔
843
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidMarkerSegmentSize);
6✔
844
    }
1,740✔
845

846
    private readonly void CheckSegmentSize(int expectedSize)
847
    {
848
        if (expectedSize != _segmentDataSize)
1,575✔
849
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidMarkerSegmentSize);
21✔
850
    }
1,554✔
851

852
    private void AddComponent(byte componentId, byte horizontalVerticalSamplingFactor)
853
    {
854
        if (_componentInfos.Exists(scan => scan.Id == componentId))
2,883✔
855
            ThrowHelper.ThrowInvalidDataException(ErrorCode.DuplicateComponentIdInStartOfFrameSegment);
3✔
856

857
        byte horizontalSamplingFactor = (byte)(horizontalVerticalSamplingFactor >> 4);
1,521✔
858
        byte verticalSamplingFactor = (byte)(horizontalVerticalSamplingFactor & 0xF);
1,521✔
859
        if (horizontalSamplingFactor < 1 || horizontalSamplingFactor > 4 || verticalSamplingFactor < 1 || verticalSamplingFactor > 4)
1,521!
NEW
860
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterSamplingFactor);
×
861

862
        _componentInfos.Add(new ComponentInfo(componentId, horizontalSamplingFactor, verticalSamplingFactor));
1,521✔
863

864
        if (horizontalSamplingFactor > _horizontalSamplingMax)
1,521✔
865
        {
866
            _horizontalSamplingMax = horizontalSamplingFactor;
6✔
867
        }
868

869
        if (verticalSamplingFactor > _verticalSamplingMax)
1,521✔
870
        {
871
            _verticalSamplingMax = verticalSamplingFactor;
6✔
872
        }
873
    }
1,521✔
874

875
    private readonly void CheckWidth()
876
    {
877
        if (_width < 1)
603✔
878
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterWidth);
3✔
879
    }
600✔
880

881
    private readonly void CheckCodingParameters()
882
    {
883
        if (ColorTransformation != ColorTransformation.None && !ColorTransformations.IsPossible(FrameInfo))
600✔
884
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterColorTransformation);
6✔
885
    }
594✔
886

887
    private void SetWidth(int value)
888
    {
889
        if (value == 0)
681✔
890
            return;
15✔
891

892
        if (_width != 0)
666✔
893
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterWidth);
3✔
894

895
        _width = value;
663✔
896
    }
663✔
897

898
    private void SetHeight(int value, bool finalUpdate)
899
    {
900
        if (value == 0 && !finalUpdate)
699✔
901
            return;
39✔
902

903
        if (_height != 0 || value == 0)
660✔
904
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterHeight);
6✔
905

906
        _height = value;
654✔
907
    }
654✔
908

909
    private readonly int GetScanWidth(int componentIndex)
910
    {
911
        int horizontalSamplingFactor = _componentInfos[componentIndex].HorizontalSamplingFactor;
2,040✔
912
        if (_horizontalSamplingMax == horizontalSamplingFactor)
2,040✔
913
            return _width;
2,025✔
914

915
        return _width * horizontalSamplingFactor / _horizontalSamplingMax;
15✔
916
    }
917

918
    private readonly int GetScanHeight(int componentIndex)
919
    {
920
        int verticalSamplingFactor = _componentInfos[componentIndex].VerticalSamplingFactor;
2,040✔
921
        if (_verticalSamplingMax == verticalSamplingFactor)
2,040✔
922
            return _height;
2,019✔
923

924
        return _height * verticalSamplingFactor / _verticalSamplingMax;
21✔
925
    }
926

927
    private void StoreComponentInfo(byte componentId, byte tableId, int nearLossless, InterleaveMode interleaveMode)
928
    {
929
        // Ignore when info is default, prevent search and ID mismatch issues.
930
        if (tableId == 0 && nearLossless == 0 && interleaveMode == InterleaveMode.None)
1,221✔
931
            return;
498✔
932

933
        int index = _componentInfos.FindIndex(scanInfo => scanInfo.Id == componentId);
2,136✔
934
        if (index == -1)
723!
935
            ThrowHelper.ThrowInvalidDataException(ErrorCode.UnknownComponentId);
×
936

937
        if (tableId != 0)
723✔
938
        {
939
            _componentWithMappingTableExists = true;
15✔
940
        }
941

942
        _componentInfos[index] = new ComponentInfo(_componentInfos[index], tableId, nearLossless, interleaveMode);
723✔
943
    }
723✔
944

945
    private static void CheckInterleaveMode(InterleaveMode mode, int componentCountInScan)
946
    {
947
        if (!mode.IsValid() || (mode != InterleaveMode.None && componentCountInScan == 1))
810✔
948
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterInterleaveMode);
6✔
949
    }
804✔
950

951
    /// <summary>
952
    /// ISO/IEC 14495-1, Annex C defines 3 data formats.
953
    /// Annex C.4 defines the format that only contains mapping tables.
954
    /// </summary>
955
    private readonly bool IsAbbreviatedFormatForTableSpecificationData()
956
    {
957
        if (MappingTableCount == 0)
12✔
958
            return false;
3✔
959

960
        if (_state == State.FrameSection)
9✔
961
            ThrowHelper.ThrowInvalidDataException(ErrorCode.AbbreviatedFormatAndSpiffHeaderMismatch);
3✔
962

963
        return _state == State.HeaderSection;
6✔
964
    }
965

966
    private readonly bool HasExternalMappingTableIds()
967
    {
968
        foreach (var scanInfo in _componentInfos)
39✔
969
        {
970
            if (scanInfo.MappingTableId != 0 && FindMappingTableIndex(scanInfo.MappingTableId) == -1)
12✔
971
                return true;
3✔
972
        }
973

974
        return false;
6✔
975
    }
3✔
976

977
    private void FindAndReadDefineNumberOfLinesSegment()
978
    {
979
        var source = Source.Span;
24✔
980
        for (int i = Position; i < source.Length - 1; ++i)
72✔
981
        {
982
            if (source[i] != 0xFF)
33✔
983
                continue;
984

985
            byte optionalMarkerCode = source[i + 1];
24✔
986
            if (optionalMarkerCode is < 128 or 0xFF)
24✔
987
                continue;
988

989
            // Found a marker, ISO/IEC 10918-1 B.2.5 requires that if DNL is used it must be at the end of the first scan.
990
            if ((JpegMarkerCode)optionalMarkerCode != JpegMarkerCode.DefineNumberOfLines)
21✔
991
                break;
992

993
            int currentPosition = Position;
18✔
994
            Position = i + 2;
18✔
995
            ReadSegmentSize();
18✔
996
            SetHeight(ReadDefineNumberOfLinesSegment(), true);
18✔
997
            _dnlMarkerExpected = true;
12✔
998
            Position = currentPosition;
12✔
999
            return;
12✔
1000
        }
1001

1002
        ThrowHelper.ThrowInvalidDataException(ErrorCode.DefineNumberOfLinesMarkerNotFound);
6✔
1003
    }
×
1004

1005
    private static bool IsKnownJpegSofMarker(JpegMarkerCode markerCode)
1006
    {
1007
        // The following start of frame (SOF) markers are defined in ISO/IEC 10918-1 | ITU T.81 (general JPEG standard).
1008
        const byte sofBaselineJpeg = 0xC0;            // SOF_0: Baseline jpeg encoded frame.
1009
        const byte sofExtendedSequential = 0xC1;      // SOF_1: Extended sequential Huffman encoded frame.
1010
        const byte sofProgressive = 0xC2;             // SOF_2: Progressive Huffman encoded frame.
1011
        const byte sofLossless = 0xC3;                // SOF_3: Lossless Huffman encoded frame.
1012
        const byte sofDifferentialSequential = 0xC5;  // SOF_5: Differential sequential Huffman encoded frame.
1013
        const byte sofDifferentialProgressive = 0xC6; // SOF_6: Differential progressive Huffman encoded frame.
1014
        const byte sofDifferentialLossless = 0xC7;    // SOF_7: Differential lossless Huffman encoded frame.
1015
        const byte sofExtendedArithmetic = 0xC9;      // SOF_9: Extended sequential arithmetic encoded frame.
1016
        const byte sofProgressiveArithmetic = 0xCA;   // SOF_10: Progressive arithmetic encoded frame.
1017
        const byte sofLosslessArithmetic = 0xCB;      // SOF_11: Lossless arithmetic encoded frame.
1018
        const byte sofJpegLSExtended = 0xF9;          // SOF_57: JPEG-LS extended (ISO/IEC 14495-2) encoded frame.
1019

1020
        return (byte)markerCode switch
6✔
1021
        {
6✔
1022
            sofBaselineJpeg or sofExtendedSequential or sofProgressive or sofLossless or sofDifferentialSequential
6✔
1023
                or sofDifferentialProgressive or sofDifferentialLossless or sofExtendedArithmetic
6✔
1024
                or sofProgressiveArithmetic or sofLosslessArithmetic or sofJpegLSExtended => true,
3✔
1025
            _ => false
3✔
1026
        };
6✔
1027
    }
1028

1029
    private readonly struct ComponentInfo
1030
    {
1031
        internal readonly byte Id;
1032
        internal readonly byte HorizontalSamplingFactor;
1033
        internal readonly byte VerticalSamplingFactor;
1034
        internal readonly byte MappingTableId;
1035
        internal readonly int NearLossless;
1036
        internal readonly InterleaveMode InterleaveMode;
1037

1038
        internal ComponentInfo(byte id, byte horizontalSamplingFactor, byte verticalSamplingFactor)
1039
        {
1040
            Id = id;
1,521✔
1041
            HorizontalSamplingFactor = horizontalSamplingFactor;
1,521✔
1042
            VerticalSamplingFactor = verticalSamplingFactor;
1,521✔
1043
        }
1,521✔
1044

1045
        internal ComponentInfo(ComponentInfo componentInfo, byte mappingTableId, int nearLossless, InterleaveMode interleaveMode)
1046
        {
1047
            Id = componentInfo.Id;
723✔
1048
            HorizontalSamplingFactor = componentInfo.HorizontalSamplingFactor;
723✔
1049
            VerticalSamplingFactor = componentInfo.VerticalSamplingFactor;
723✔
1050
            MappingTableId = mappingTableId;
723✔
1051
            NearLossless = nearLossless;
723✔
1052
            InterleaveMode = interleaveMode;
723✔
1053
        }
723✔
1054
    }
1055

1056
    private readonly struct MappingTableEntry
1057
    {
1058
        private readonly List<ReadOnlyMemory<byte>> _dataFragments = [];
33✔
1059

1060
        internal MappingTableEntry(byte mappingTableId, byte entrySize, ReadOnlyMemory<byte> tableData)
1061
        {
1062
            MappingTableId = mappingTableId;
33✔
1063
            EntrySize = entrySize;
33✔
1064
            _dataFragments.Add(tableData);
33✔
1065
        }
33✔
1066

1067
        internal byte MappingTableId { get; }
42✔
1068

1069
        internal byte EntrySize { get; }
15✔
1070

1071
        internal int DataSize => _dataFragments.Sum(fragment => fragment.Length);
30✔
1072

1073
        internal void AddFragment(ReadOnlyMemory<byte> tableData)
1074
        {
1075
            _dataFragments.Add(tableData);
3✔
1076
        }
3✔
1077

1078
        internal ReadOnlyMemory<byte> GetData()
1079
        {
1080
            if (_dataFragments.Count == 1)
9✔
1081
            {
1082
                return _dataFragments[0];
6✔
1083
            }
1084

1085
            byte[] buffer = new byte[DataSize];
3✔
1086
            CopyFragmentsIntoBuffer(buffer);
3✔
1087
            return buffer;
3✔
1088
        }
1089

1090
        private void CopyFragmentsIntoBuffer(Span<byte> buffer)
1091
        {
1092
            foreach (var fragment in _dataFragments)
18✔
1093
            {
1094
                fragment.Span.CopyTo(buffer);
6✔
1095
                buffer = buffer[fragment.Length..];
6✔
1096
            }
1097
        }
3✔
1098
    }
1099
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc