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

team-charls / charls-dotnet / 18995468200

01 Nov 2025 10:32AM UTC coverage: 94.488% (-0.1%) from 94.625%
18995468200

Pull #61

github

web-flow
Merge e89e76295 into 01de85364
Pull Request #61: Add support for subsampling

1411 of 1546 branches covered (91.27%)

Branch coverage included in aggregate %.

132 of 138 new or added lines in 7 files covered. (95.65%)

2 existing lines in 1 file now uncovered.

3440 of 3588 relevant lines covered (95.88%)

520290.67 hits per line

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

94.13
/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 = [];
542✔
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;
542✔
30
    private int _verticalSamplingMax = 1;
542✔
31
    private bool _dnlMarkerExpected;
32
    private bool _componentWithMappingTableExists;
33
    private int[]? _widths;
34
    private int[]? _heights;
35

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

41
    internal JpegStreamReader(object eventSender)
42
    {
43
        _eventSender = eventSender;
542✔
44
        _componentInfos = [];
542✔
45
    }
542✔
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;
590✔
63

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

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

68
    internal ReadOnlyMemory<byte> Source { get; set; }
35,542✔
69

70
    internal int Position { get; private set; }
55,858✔
71

72
    internal JpegLSPresetCodingParameters? JpegLSPresetCodingParameters { get; private set; }
1,696✔
73

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

76
    internal InterleaveMode ScanInterleaveMode { get; private set; }
2,958✔
77

78
    internal int ScanComponentCount { get; private set; }
2,282✔
79

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

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

85
    internal ColorTransformation ColorTransformation { get; private set; }
916✔
86

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

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

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

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

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

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

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

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

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

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

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

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

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

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

146
        return validatedCodingParameters;
348✔
147
    }
148

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

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

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

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

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

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

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

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

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

203
            ValidateMarkerCode(markerCode);
1,164✔
204

205
            ReadSegmentSize();
1,152✔
206

207
            switch (_state)
1,144✔
208
            {
209
                case State.SpiffHeaderSection:
210
                    ReadSpiffDirectoryEntry(markerCode);
8✔
211
                    break;
6✔
212

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

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

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

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

233
                CheckWidth();
402✔
234
                CheckCodingParameters();
400✔
235
                return;
396✔
236
            }
237
        }
238
    }
239

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

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

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

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

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

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

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

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

277
        return Source.Span[Position++];
16,074✔
278
    }
279

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

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

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

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

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

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

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

311
    private JpegMarkerCode ReadNextMarkerCode()
312
    {
313
        byte value = ReadByte();
2,060✔
314
        if (value != Constants.JpegMarkerStartByte)
2,058✔
315
            ThrowHelper.ThrowInvalidDataException(ErrorCode.JpegMarkerStartByteNotFound);
8✔
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();
2,058✔
321
        }
322
        while (value == Constants.JpegMarkerStartByte);
2,058✔
323

324
        return (JpegMarkerCode)value;
2,050✔
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,308✔
331
        _segmentDataSize = segmentSize - segmentLength;
1,306✔
332
        if (segmentSize < segmentLength || Position + _segmentDataSize > Source.Length)
1,306✔
333
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidMarkerSegmentSize);
6✔
334

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

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

346
            case JpegMarkerCode.StartOfScan:
347
                ReadStartOfScanSegment();
550✔
348
                break;
536✔
349

350
            case JpegMarkerCode.Comment:
351
                ReadCommentSegment();
14✔
352
                break;
12✔
353

354
            case JpegMarkerCode.JpegLSPresetParameters:
355
                ReadPresetParametersSegment();
108✔
356
                break;
70✔
357

358
            case JpegMarkerCode.DefineRestartInterval:
359
                ReadDefineRestartIntervalSegment();
42✔
360
                break;
38✔
361

362
            case JpegMarkerCode.DefineNumberOfLines:
363
                _ = ReadDefineNumberOfLinesSegment();
8✔
364
                _dnlMarkerExpected = false;
8✔
365
                break;
8✔
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);
60✔
383
                break;
60✔
384

385
            case JpegMarkerCode.ApplicationData8:
386
                ReadApplicationData8Segment(readSpiffHeader);
50✔
387
                break;
388
        }
389
    }
50✔
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)
552✔
400
                    ThrowHelper.ThrowInvalidDataException(ErrorCode.UnexpectedStartOfScanMarker);
2✔
401

402
                return;
550✔
403

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

408
                return;
452✔
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;
286✔
430

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

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

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

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

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

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

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

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

470
        SkipRemainingSegmentData();
6✔
471
    }
6✔
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);
448✔
479

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

484
        SetHeight(ReadUint16(), false);
446✔
485
        SetWidth(ReadUint16());
444✔
486

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

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

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

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

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

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

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

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

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

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

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

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

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

570
            case JpegLSPresetParametersType.MappingTableSpecification:
571
                ReadMappingTableSpecification();
26✔
572
                return;
22✔
573

574
            case JpegLSPresetParametersType.MappingTableContinuation:
575
                ReadMappingTableContinuation();
6✔
576
                return;
2✔
577

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

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

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

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

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

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

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

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

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

622
        ExtendMappingTable(tableId, entrySize, Source.Slice(Position, SegmentBytesToRead));
6✔
623
        SkipRemainingSegmentData();
2✔
624
    }
2✔
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);
20✔
631
        byte dimensionSize = ReadByte();
20✔
632

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

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

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

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

659
        SetHeight(height, false);
10✔
660
        SetWidth(width);
10✔
661
    }
8✔
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)
30✔
666
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterMappingTableId);
4✔
667

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

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

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

678
        MappingTableEntry tableEntry = _mappingTables[index];
2✔
679
        tableEntry.AddFragment(tableData);
2✔
680
        _mappingTables[index] = tableEntry;
2✔
681
    }
2✔
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
42✔
688
        {
42✔
689
            2 => ReadUint16(),
28✔
690
            3 => ReadUint24(),
6✔
691
            4 => ReadUint32(),
6✔
692
            _ => throw ThrowHelper.CreateInvalidDataException(ErrorCode.InvalidMarkerSegmentSize)
2✔
693
        };
42✔
694
    }
38✔
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
20!
701
        {
20✔
702
            2 => ReadUint16(),
10✔
703
            3 => ReadUint24(),
4✔
704
            4 => ReadUint32(),
6✔
705
            _ => throw ThrowHelper.CreateInvalidDataException(ErrorCode.InvalidMarkerSegmentSize)
×
706
        };
20✔
707
    }
708

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

778
        byte transformation = ReadByte();
32!
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;
32✔
786
                return;
32✔
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];
12✔
804
        var tagBytes = ReadBytes(spiffTag.Length);
12✔
805
        if (!spiffTag.SequenceEqual(tagBytes.Span))
12!
806
            return;
×
807

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

924
        return _height * verticalSamplingFactor / _verticalSamplingMax;
14✔
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)
814✔
931
            return;
332✔
932

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

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

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

945
    private static void CheckInterleaveMode(InterleaveMode mode, int componentCountInScan)
946
    {
947
        if (!mode.IsValid() || (mode != InterleaveMode.None && componentCountInScan == 1))
540✔
948
            ThrowHelper.ThrowInvalidDataException(ErrorCode.InvalidParameterInterleaveMode);
4✔
949
    }
536✔
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)
8✔
958
            return false;
2✔
959

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

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

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

974
        return false;
4✔
975
    }
2✔
976

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

985
            byte optionalMarkerCode = source[i + 1];
16✔
986
            if (optionalMarkerCode is < 128 or 0xFF)
16✔
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)
14✔
991
                break;
992

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

1002
        ThrowHelper.ThrowInvalidDataException(ErrorCode.DefineNumberOfLinesMarkerNotFound);
4✔
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
4✔
1021
        {
4✔
1022
            sofBaselineJpeg or sofExtendedSequential or sofProgressive or sofLossless or sofDifferentialSequential
4✔
1023
                or sofDifferentialProgressive or sofDifferentialLossless or sofExtendedArithmetic
4✔
1024
                or sofProgressiveArithmetic or sofLosslessArithmetic or sofJpegLSExtended => true,
2✔
1025
            _ => false
2✔
1026
        };
4✔
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,014✔
1041
            HorizontalSamplingFactor = horizontalSamplingFactor;
1,014✔
1042
            VerticalSamplingFactor = verticalSamplingFactor;
1,014✔
1043
        }
1,014✔
1044

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

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

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

1067
        internal byte MappingTableId { get; }
28✔
1068

1069
        internal byte EntrySize { get; }
10✔
1070

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

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

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

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

1090
        private void CopyFragmentsIntoBuffer(Span<byte> buffer)
1091
        {
1092
            foreach (var fragment in _dataFragments)
12✔
1093
            {
1094
                fragment.Span.CopyTo(buffer);
4✔
1095
                buffer = buffer[fragment.Length..];
4✔
1096
            }
1097
        }
2✔
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