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

team-charls / charls-dotnet / 18775459846

24 Oct 2025 09:21AM UTC coverage: 94.488% (-0.1%) from 94.625%
18775459846

Pull #61

github

web-flow
Merge 6d689c757 into cc5d89c30
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

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

4
using System.Reflection;
5
using System.Text;
6

7
using static CharLS.Managed.Algorithm;
8

9
namespace CharLS.Managed;
10

11
/// <summary>
12
/// JPEG-LS encoder that provided the functionality to encode JPEG-LS images.
13
/// </summary>
14
public sealed class JpegLSEncoder
15
{
16
    /// <summary>
17
    /// Special value to indicate that encoder needs to calculate the required stride.
18
    /// </summary>
19
    public const int AutoCalculateStride = Constants.AutoCalculateStride;
20

21
    private JpegStreamWriter _writer;
22
    private ScanEncoder _scanEncoder;
23
    private int _nearLossless;
24
    private InterleaveMode _interleaveMode;
25
    private ColorTransformation _colorTransformation;
26
    private EncodingOptions _encodingOptions;
27
    private JpegLSPresetCodingParameters? _userPresetCodingParameters = new();
336✔
28
    private State _state = State.Initial;
29
    private int _encodedComponentCount;
30
    private byte[]? _samplingFactors;
31
    private int _horizontalSamplingMax;
32
    private int _verticalSamplingMax;
33

34
    /// <summary>
35
    /// Initializes a new instance of the <see cref="JpegLSEncoder"/> class.
36
    /// </summary>
37
    public JpegLSEncoder()
254✔
38
    {
39
    }
254✔
40

41
    /// <summary>
42
    /// Initializes a new instance of the <see cref="JpegLSEncoder"/> class.
43
    /// </summary>
44
    /// <param name="width">The width of the image to encode.</param>
45
    /// <param name="height">The height of the image to encode.</param>
46
    /// <param name="bitsPerSample">The bits per sample of the image to encode.</param>
47
    /// <param name="componentCount">The component count of the image to encode.</param>
48
    /// <param name="interleaveMode">The interleave mode of the image to encode (default None).</param>
49
    /// <param name="allocateDestination">Flag to control if destination buffer should be allocated or not (default true).</param>
50
    /// <param name="extraBytes">Number of extra destination bytes. Comments and tables are not included in the standard estimate (default 0).</param>
51
    /// <exception cref="ArgumentOutOfRangeException">Thrown when one of the arguments is invalid.</exception>
52
    /// <exception cref="OutOfMemoryException">Thrown when memory allocation for the destination buffer fails.</exception>
53
    public JpegLSEncoder(int width, int height, int bitsPerSample, int componentCount, InterleaveMode interleaveMode = InterleaveMode.None, bool allocateDestination = true, int extraBytes = 0)
54
        : this(new FrameInfo(width, height, bitsPerSample, componentCount), interleaveMode, allocateDestination, extraBytes)
86✔
55
    {
56
    }
74✔
57

58
    /// <summary>
59
    /// Initializes a new instance of the <see cref="JpegLSEncoder"/> class.
60
    /// </summary>
61
    /// <param name="frameInfo">The frameInfo of the image to encode.</param>
62
    /// <param name="interleaveMode">The interleave mode of the image to encode (default None).</param>
63
    /// <param name="allocateDestination">Flag to control if destination buffer should be allocated or not (default true).</param>
64
    /// <param name="extraBytes">Number of extra destination bytes. Comments and tables are not included in the standard estimate (default 0).</param>
65
    /// <exception cref="ArgumentOutOfRangeException">Thrown when one of the arguments is invalid.</exception>
66
    /// <exception cref="OutOfMemoryException">Thrown when memory allocation for the destination buffer fails.</exception>
67
    public JpegLSEncoder(FrameInfo frameInfo, InterleaveMode interleaveMode = InterleaveMode.None, bool allocateDestination = true, int extraBytes = 0)
82✔
68
    {
69
        ArgumentOutOfRangeException.ThrowIfNegative(extraBytes);
82✔
70

71
        FrameInfo = frameInfo;
82✔
72
        InterleaveMode = interleaveMode;
82✔
73

74
        if (allocateDestination)
82✔
75
        {
76
            Destination = new byte[EstimatedDestinationSize + extraBytes];
80✔
77
        }
78
    }
82✔
79

80
    private enum State
81
    {
82
        Initial,
83
        DestinationSet,
84
        SpiffHeader,
85
        TablesAndMiscellaneous,
86
        Completed
87
    }
88

89
    /// <summary>
90
    /// Gets or sets the frame information of the image.
91
    /// </summary>
92
    /// <value>
93
    /// The frame information of the image.
94
    /// </value>
95
    /// <exception cref="ArgumentException">Thrown when the passed FrameInfo is invalid.</exception>
96
    /// <exception cref="ArgumentNullException">Thrown when the passed FrameInfo instance is null.</exception>
97
    public FrameInfo FrameInfo { get; set; }
4,112✔
98

99
    /// <summary>
100
    /// Gets or sets the near lossless parameter to be used to encode the JPEG-LS stream.
101
    /// </summary>
102
    /// <value>
103
    /// The near lossless parameter value, 0 means lossless.
104
    /// </value>
105
    /// <exception cref="ArgumentException">Thrown when the passed value is invalid.</exception>
106
    public int NearLossless
107
    {
108
        get => _nearLossless;
988✔
109

110
        set
111
        {
112
            ThrowHelper.ThrowIfOutsideRange(
34✔
113
                Constants.MinimumNearLossless, Constants.MaximumNearLossless, value, ErrorCode.InvalidArgumentNearLossless);
34✔
114
            _nearLossless = value;
30✔
115
        }
30✔
116
    }
117

118
    /// <summary>
119
    /// Gets or sets the interleave mode.
120
    /// </summary>
121
    /// <value>
122
    /// The interleave mode that should be used to encode the image. Default is None.
123
    /// </value>
124
    /// <exception cref="ArgumentException">Thrown when the passed value is invalid for the defined image.</exception>
125
    public InterleaveMode InterleaveMode
126
    {
127
        get => _interleaveMode;
1,076✔
128

129
        set
130
        {
131
            ThrowHelper.ThrowArgumentOutOfRangeExceptionIfFalse(value.IsValid(), ErrorCode.InvalidArgumentInterleaveMode, nameof(value));
242✔
132
            _interleaveMode = value;
238✔
133
        }
238✔
134
    }
135

136
    /// <summary>
137
    /// Configures the encoding options the encoder should use.
138
    /// </summary>
139
    /// <value>
140
    /// Options to use. Options can be combined. Default is None.
141
    /// </value>
142
    /// <exception cref="ArgumentOutOfRangeException">Thrown when the passed enum value is invalid.</exception>
143
    public EncodingOptions EncodingOptions
144
    {
145
        get => _encodingOptions;
640✔
146

147
        set
148
        {
149
            ThrowHelper.ThrowArgumentOutOfRangeExceptionIfFalse(value.IsValid(), ErrorCode.InvalidArgumentEncodingOptions, nameof(value));
14✔
150
            _encodingOptions = value;
12✔
151
        }
12✔
152
    }
153

154
    /// <summary>
155
    /// Gets or sets the JPEG-LS preset coding parameters.
156
    /// </summary>
157
    /// <value>
158
    /// The JPEG-LS preset coding parameters that should be used to encode the image.
159
    /// </value>
160
    /// <exception cref="ArgumentNullException">value.</exception>
161
    public JpegLSPresetCodingParameters? PresetCodingParameters
162
    {
163
        get => _userPresetCodingParameters;
×
164

165
        set
166
        {
167
            ArgumentNullException.ThrowIfNull(value);
48✔
168

169
            _userPresetCodingParameters = value;
48✔
170
        }
48✔
171
    }
172

173
    /// <summary>
174
    /// Gets or sets the HP color transformation the encoder should use
175
    /// If not set the encoder will use no color transformation.
176
    /// Color transformations are an HP extension and not defined by the JPEG-LS standard and can only be set for 3 component.
177
    /// </summary>
178
    /// <value>
179
    /// The color transformation that should be used to encode the image.
180
    /// </value>
181
    /// <exception cref="ArgumentOutOfRangeException">value.</exception>
182
    public ColorTransformation ColorTransformation
183
    {
184
        get => _colorTransformation;
502✔
185

186
        set
187
        {
188
            ThrowHelper.ThrowArgumentOutOfRangeExceptionIfFalse(
80✔
189
                value.IsValid(), ErrorCode.InvalidArgumentColorTransformation);
80✔
190

191
            _colorTransformation = value;
78✔
192
        }
78✔
193
    }
194

195
    /// <summary>
196
    /// Gets the estimated size in bytes of the memory buffer that should be used as output destination.
197
    /// </summary>
198
    /// <value>
199
    /// The size in bytes of the memory buffer.
200
    /// </value>
201
    /// <exception cref="OverflowException">When the required size doesn't fit in an int.</exception>
202
    public int EstimatedDestinationSize
203
    {
204
        get
205
        {
206
            ThrowHelper.ThrowInvalidOperationIfFalse(IsFrameInfoConfigured);
226✔
207

208
            checked
209
            {
210
                return (FrameInfo.Width * FrameInfo.Height * FrameInfo.ComponentCount *
224✔
211
                           BitToByteCount(FrameInfo.BitsPerSample)) + 1024 + Constants.SpiffHeaderSizeInBytes;
224✔
212
            }
213
        }
214
    }
215

216
    /// <summary>
217
    /// Gets or sets the memory region that will be the destination for the encoded JPEG-LS data.
218
    /// </summary>
219
    /// <value>
220
    /// The memory buffer to be used as the destination.
221
    /// </value>
222
    /// <exception cref="ArgumentException">Thrown when the passed value is an empty buffer.</exception>
223
    public Memory<byte> Destination
224
    {
225
        get => _writer.Destination;
110✔
226

227
        set
228
        {
229
            ThrowHelper.ThrowInvalidOperationIfFalse(_state == State.Initial);
298✔
230
            _writer.Destination = value;
296✔
231
            _state = State.DestinationSet;
296✔
232
        }
296✔
233
    }
234

235
    /// <summary>
236
    /// Gets the memory region with the encoded JPEG-LS data.
237
    /// </summary>
238
    /// <value>
239
    /// The memory region with the encoded data.
240
    /// </value>
241
    public Memory<byte> EncodedData => Destination[..BytesWritten];
106✔
242

243
    /// <summary>
244
    /// Gets the bytes written to the destination buffer.
245
    /// </summary>
246
    /// <value>
247
    /// The bytes written to the destination buffer.
248
    /// </value>
249
    public int BytesWritten => _writer.BytesWritten;
324✔
250

251
    private bool IsFrameInfoConfigured => FrameInfo.Height != 0;
494✔
252

253
    /// <summary>
254
    /// Configures the sampling factor when encoding a component.
255
    /// </summary>
256
    /// <param name="componentIndex">The index of the component to set the mapping table ID for.</param>
257
    /// <param name="horizontalFactor">The horizontal subsampling factor.</param>
258
    /// <param name="verticalFactor">The vertical subsampling factor.</param>
259
    public void SetSamplingFactor(int componentIndex, int horizontalFactor, int verticalFactor)
260
    {
261
        ThrowHelper.ThrowIfOutsideRange(Constants.MinimumComponentIndex, Constants.MaximumComponentIndex, componentIndex);
12✔
262
        ThrowHelper.ThrowIfOutsideRange(0, 4, horizontalFactor);
12✔
263
        ThrowHelper.ThrowIfOutsideRange(0, 4, verticalFactor);
12✔
264

265
        // Usage of sampling factors is rare: use lazy initialization.
266
        _samplingFactors ??= new byte[Constants.MaximumComponentCount];
12✔
267
        _samplingFactors[componentIndex] = (byte)((horizontalFactor << 4) | verticalFactor);
12✔
268
    }
12✔
269

270
    /// <summary>
271
    /// Configures the mapping table ID the encoder should reference when encoding a component.
272
    /// The referenced mapping table can be included in the stream or provided in another JPEG-LS abbreviated format stream.
273
    /// </summary>
274
    /// <param name="componentIndex">The index of the component to set the mapping table ID for.</param>
275
    /// <param name="mappingTableId">The table ID the component should reference.</param>
276
    public void SetMappingTableId(int componentIndex, int mappingTableId)
277
    {
278
        ThrowHelper.ThrowIfOutsideRange(Constants.MinimumComponentIndex, Constants.MaximumComponentIndex, componentIndex);
10✔
279
        ThrowHelper.ThrowIfOutsideRange(0, Constants.MaximumMappingTableId, mappingTableId);
8✔
280

281
        _writer.SetTableId(componentIndex, mappingTableId);
6✔
282
    }
6✔
283

284
    /// <summary>
285
    /// Resets the write position of the destination buffer to the beginning.
286
    /// </summary>
287
    public void Rewind()
288
    {
289
        if (_state == State.Initial)
4✔
290
            return; // Nothing to do, stay in the same state.
2✔
291

292
        _writer.Rewind();
2✔
293
        _state = State.DestinationSet;
2✔
294
        _encodedComponentCount = 0;
2✔
295
    }
2✔
296

297
    /// <summary>
298
    /// Encodes the passed image data into encoded JPEG-LS data.
299
    /// </summary>
300
    /// <param name="source">The memory region that is the source input to the encoding process.</param>
301
    /// <param name="stride">The stride of the image pixel of the source input.</param>
302
    public void Encode(ReadOnlySpan<byte> source, int stride = Constants.AutoCalculateStride)
303
    {
304
        EncodeComponents(source, FrameInfo.ComponentCount, stride);
212✔
305
    }
186✔
306

307
    /// <summary>
308
    /// Encodes the passed image data into encoded JPEG-LS data.
309
    /// This is an advanced method that provides more control how image data is encoded in JPEG-LS scans.
310
    /// It should be called until all components are encoded.
311
    /// </summary>
312
    /// <param name="source">The memory region that is the source input to the encoding process.</param>
313
    /// <param name="sourceComponentCount">The number of components present in the input source.</param>
314
    /// <param name="stride">The stride of the image pixel of the source input.</param>
315
    public void EncodeComponents(ReadOnlySpan<byte> source, int sourceComponentCount, int stride = Constants.AutoCalculateStride)
316
    {
317
        CheckStateCanWrite();
244✔
318
        ThrowHelper.ThrowInvalidOperationIfFalse(IsFrameInfoConfigured);
242✔
319
        ThrowHelper.ThrowArgumentExceptionIfFalse(sourceComponentCount <= FrameInfo.ComponentCount - _encodedComponentCount, nameof(sourceComponentCount));
240✔
320
        CheckInterleaveModeAgainstComponentCount(sourceComponentCount);
238✔
321

322
        int maximumSampleValue = CalculateMaximumSampleValue(FrameInfo.BitsPerSample);
232✔
323
        if (!_userPresetCodingParameters!.TryMakeExplicit(maximumSampleValue, NearLossless, out var explicitCodingParameters))
232✔
324
            throw ThrowHelper.CreateArgumentException(ErrorCode.InvalidArgumentPresetCodingParameters);
2✔
325

326
        if (_encodedComponentCount == 0)
230✔
327
        {
328
            DetermineMaxSamplingFactors();
212✔
329
            TransitionToTablesAndMiscellaneousState();
212✔
330
            WriteColorTransformSegment();
212✔
331
            WriteStartOfFrameSegment();
206✔
332
        }
333

334
        WriteJpegLSPresetParametersSegment(maximumSampleValue, explicitCodingParameters);
224✔
335

336
        if (InterleaveMode == InterleaveMode.None)
224✔
337
        {
338
            for (int component = 0; ;)
138✔
339
            {
340
                int scanWidth = GetScanWidth(_encodedComponentCount + component);
186✔
341
                int scanHeight = GetScanHeight(_encodedComponentCount + component);
186✔
342
                int scanStride = CheckStrideAndSourceLengthInterleaveModeNone(source.Length, stride, scanWidth, scanHeight);
186✔
343
                _writer.WriteStartOfScanSegment(1, NearLossless, InterleaveMode);
182✔
344
                EncodeScan(source, scanStride, scanWidth, scanHeight, 1, explicitCodingParameters);
182✔
345

346
                ++component;
182✔
347
                if (component == sourceComponentCount)
182✔
348
                    break;
349

350
                // Synchronize the source stream (EncodeScan works on a local copy)
351
                int byteCountComponent = scanStride * scanHeight;
48✔
352
                source = source[byteCountComponent..];
48✔
353
            }
354
        }
355
        else
356
        {
357
            int scanWidth = GetScanWidth(_encodedComponentCount);
86✔
358
            int scanHeight = GetScanHeight(_encodedComponentCount);
86✔
359
            int scanStride = CheckStrideAndSourceLength(source.Length, stride, scanWidth, scanHeight, sourceComponentCount);
86✔
360
            _writer.WriteStartOfScanSegment(sourceComponentCount, NearLossless, InterleaveMode);
82✔
361
            EncodeScan(source, scanStride, scanWidth, scanHeight, sourceComponentCount, explicitCodingParameters);
82✔
362
        }
363

364
        _encodedComponentCount += sourceComponentCount;
216✔
365
        if (_encodedComponentCount == FrameInfo.ComponentCount)
216✔
366
        {
367
            WriteEndOfImage();
198✔
368
        }
369
    }
216✔
370

371
    /// <summary>
372
    /// Writes a SPIFF header to the destination memory buffer.
373
    /// A SPIFF header is optional, but recommended for standalone JPEG-LS files.
374
    /// It should not be used when embedding a JPEG-LS image in a DICOM file.
375
    /// </summary>
376
    /// <param name="spiffHeader">Reference to a SPIFF header that will be written to the destination buffer.</param>
377
    public void WriteSpiffHeader(SpiffHeader spiffHeader)
378
    {
379
        ArgumentNullException.ThrowIfNull(spiffHeader);
32✔
380

381
        ThrowHelper.ThrowIfOutsideRange(1, int.MaxValue, spiffHeader.Height, ErrorCode.InvalidArgumentHeight);
30✔
382
        ThrowHelper.ThrowIfOutsideRange(1, int.MaxValue, spiffHeader.Width, ErrorCode.InvalidArgumentWidth);
28✔
383
        ThrowHelper.ThrowInvalidOperationIfFalse(_state == State.DestinationSet);
26✔
384

385
        _writer.WriteStartOfImage();
22✔
386
        _writer.WriteSpiffHeaderSegment(spiffHeader);
22✔
387
        _state = State.SpiffHeader;
22✔
388
    }
22✔
389

390
    /// <summary>
391
    /// Writes a standard SPIFF header to the destination. The additional values are computed from the current encoder settings.
392
    /// A SPIFF header is optional, but recommended for standalone JPEG-LS files.
393
    /// It should not be used when embedding a JPEG-LS image in a DICOM file.
394
    /// </summary>
395
    /// <param name="colorSpace">The color space of the image.</param>
396
    /// <param name="resolutionUnit">The resolution units of the next 2 parameters.</param>
397
    /// <param name="verticalResolution">The vertical resolution.</param>
398
    /// <param name="horizontalResolution">The horizontal resolution.</param>
399
    public void WriteStandardSpiffHeader(
400
        SpiffColorSpace colorSpace,
401
        SpiffResolutionUnit resolutionUnit = SpiffResolutionUnit.AspectRatio,
402
        int verticalResolution = 1,
403
        int horizontalResolution = 1)
404
    {
405
        ThrowHelper.ThrowInvalidOperationIfFalse(IsFrameInfoConfigured);
26✔
406

407
        var spiffHeader = new SpiffHeader
24✔
408
        {
24✔
409
            ColorSpace = colorSpace,
24✔
410
            Height = FrameInfo.Height,
24✔
411
            Width = FrameInfo.Width,
24✔
412
            BitsPerSample = FrameInfo.BitsPerSample,
24✔
413
            ComponentCount = FrameInfo.ComponentCount,
24✔
414
            ResolutionUnit = resolutionUnit,
24✔
415
            VerticalResolution = verticalResolution,
24✔
416
            HorizontalResolution = horizontalResolution
24✔
417
        };
24✔
418
        WriteSpiffHeader(spiffHeader);
24✔
419
    }
20✔
420

421
    /// <summary>
422
    /// Writes a SPIFF directory entry to the destination.
423
    /// </summary>
424
    /// <param name="entryTag">The entry tag of the directory entry.</param>
425
    /// <param name="entryData">The data of the directory entry.</param>
426
    public void WriteSpiffEntry(SpiffEntryTag entryTag, ReadOnlySpan<byte> entryData)
427
    {
428
        WriteSpiffEntry((int)entryTag, entryData);
12✔
429
    }
8✔
430

431
    /// <summary>
432
    /// Writes a SPIFF directory entry to the destination.
433
    /// </summary>
434
    /// <param name="entryTag">The entry tag of the directory entry.</param>
435
    /// <param name="entryData">The data of the directory entry.</param>
436
    public void WriteSpiffEntry(int entryTag, ReadOnlySpan<byte> entryData)
437
    {
438
        ThrowHelper.ThrowArgumentExceptionIfFalse(entryTag != Constants.SpiffEndOfDirectoryEntryType, nameof(entryTag));
14✔
439
        ThrowHelper.ThrowArgumentExceptionIfFalse(entryData.Length <= 65528, nameof(entryData), ErrorCode.InvalidArgumentSize);
12✔
440
        ThrowHelper.ThrowInvalidOperationIfFalse(_state == State.SpiffHeader);
10✔
441

442
        _writer.WriteSpiffDirectoryEntry(entryTag, entryData);
8✔
443
    }
8✔
444

445
    /// <summary>
446
    /// Writes a SPIFF end of directory entry to the destination.
447
    /// The encoder will normally do this automatically. It is made available
448
    /// for the scenario to create SPIFF headers in front of existing JPEG-LS streams.
449
    /// </summary>
450
    /// <remarks>
451
    /// The end of directory also includes a SOI marker. This marker should be skipped from the JPEG-LS stream.
452
    /// </remarks>
453
    public void WriteSpiffEndOfDirectoryEntry()
454
    {
455
        ThrowHelper.ThrowInvalidOperationIfFalse(_state == State.SpiffHeader);
8✔
456
        TransitionToTablesAndMiscellaneousState();
4✔
457
    }
4✔
458

459
    /// <summary>
460
    /// Writes a comment (COM) segment to the destination.
461
    /// </summary>
462
    /// <remarks>
463
    /// Function should be called before encoding the image data.
464
    /// </remarks>
465
    /// <param name="comment">The 'comment' bytes. Application specific, usually human-readable UTF-8 string.</param>
466
    public void WriteComment(ReadOnlySpan<byte> comment)
467
    {
468
        ThrowHelper.ThrowArgumentExceptionIfFalse(comment.Length <= Constants.SegmentMaxDataSize, nameof(comment));
22✔
469
        CheckStateCanWrite();
20✔
470

471
        TransitionToTablesAndMiscellaneousState();
16✔
472
        _writer.WriteCommentSegment(comment);
16✔
473
    }
16✔
474

475
    /// <summary>
476
    /// Writes a comment (COM) segment to the destination.
477
    /// </summary>
478
    /// <remarks>
479
    /// Function should be called before encoding the image data.
480
    /// </remarks>
481
    /// <param name="comment">Application specific value, usually human-readable UTF-8 string.</param>
482
    public void WriteComment(string comment)
483
    {
484
        WriteComment(ToUtf8(comment));
16✔
485
    }
12✔
486

487
    /// <summary>
488
    /// Writes an application data (APPn) segment to the destination.
489
    /// </summary>
490
    /// <remarks>
491
    /// Function should be called before encoding the image data.
492
    /// </remarks>
493
    /// <param name="applicationDataId">The ID of the application data segment in the range [0..15].</param>
494
    /// <param name="applicationData">The 'application data' bytes. Application specific.</param>
495
    public void WriteApplicationData(int applicationDataId, ReadOnlySpan<byte> applicationData)
496
    {
497
        ThrowHelper.ThrowIfOutsideRange(Constants.MinimumApplicationDataId, Constants.MaximumApplicationDataId, applicationDataId);
22✔
498
        ThrowHelper.ThrowArgumentExceptionIfFalse(applicationData.Length <= Constants.SegmentMaxDataSize, nameof(applicationData));
18✔
499
        CheckStateCanWrite();
16✔
500

501
        TransitionToTablesAndMiscellaneousState();
14✔
502
        _writer.WriteApplicationDataSegment(applicationDataId, applicationData);
14✔
503
    }
14✔
504

505
    /// <summary>
506
    /// Writes a mapping table segment to the destination.
507
    /// </summary>
508
    /// <remarks>
509
    /// No validation is performed if the table ID is unique and if the table size matches the required size.
510
    /// </remarks>
511
    /// <param name="tableId">Table ID. Unique identifier of the mapping table in the range [1..255].</param>
512
    /// <param name="entrySize">Size in bytes of a single table entry.</param>
513
    /// <param name="tableData">Buffer that holds the mapping table.</param>
514
    public void WriteMappingTable(int tableId, int entrySize, ReadOnlySpan<byte> tableData)
515
    {
516
        ThrowHelper.ThrowIfOutsideRange(Constants.MinimumMappingTableId, Constants.MaximumMappingTableId, tableId);
18✔
517
        ThrowHelper.ThrowIfOutsideRange(Constants.MinimumMappingEntrySize, Constants.MaximumMappingEntrySize, entrySize);
14✔
518
        ThrowHelper.ThrowArgumentExceptionIfFalse(tableData.Length >= entrySize, nameof(tableData), ErrorCode.InvalidArgumentSize);
10✔
519
        CheckStateCanWrite();
8✔
520

521
        TransitionToTablesAndMiscellaneousState();
6✔
522
        _writer.WriteJpegLSPresetParametersSegment(tableId, entrySize, tableData);
6✔
523
    }
6✔
524

525
    /// <summary>
526
    /// Creates a JPEG-LS stream in abbreviated format that only contain mapping tables (See JPEG-LS standard, C.4).
527
    /// These tables should have been written to the stream first with the method write_mapping_table.
528
    /// </summary>
529
    public void CreateAbbreviatedFormat()
530
    {
531
        ThrowHelper.ThrowInvalidOperationIfFalse(_state == State.TablesAndMiscellaneous);
4✔
532
        WriteEndOfImage();
2✔
533
    }
2✔
534

535
    /// <summary>
536
    /// Encodes the passed image data into encoded JPEG-LS data.
537
    /// </summary>
538
    /// <param name="source">Source pixel data that needs to be encoded.</param>
539
    /// <param name="frameInfo">Frame info object that describes the pixel data.</param>
540
    /// <param name="interleaveMode">Defines how the pixel data should be encoded.</param>
541
    /// <param name="encodingOptions">Defines several options how to encode the pixel data.</param>
542
    /// <param name="stride">The stride to use; byte count to the next pixel row. Pass 0 (AutoCalculateStride) for the default.</param>
543
    public static Memory<byte> Encode(
544
        ReadOnlySpan<byte> source,
545
        FrameInfo frameInfo,
546
        InterleaveMode interleaveMode = InterleaveMode.None,
547
        EncodingOptions encodingOptions = EncodingOptions.None,
548
        int stride = AutoCalculateStride)
549
    {
550
        JpegLSEncoder encoder = new(frameInfo) { InterleaveMode = interleaveMode, EncodingOptions = encodingOptions };
6✔
551
        encoder.Encode(source, stride);
6✔
552
        return encoder.EncodedData;
2✔
553
    }
554

555
    private void EncodeScan(ReadOnlySpan<byte> source, int stride, int scanWidth, int scanHeight, int componentCount, JpegLSPresetCodingParameters codingParameters)
556
    {
557
        _scanEncoder = new ScanEncoder(
264✔
558
            new FrameInfo(scanWidth, scanHeight, FrameInfo.BitsPerSample, componentCount),
264✔
559
            codingParameters,
264✔
560
            new CodingParameters
264✔
561
            {
264✔
562
                InterleaveMode = InterleaveMode,
264✔
563
                NearLossless = NearLossless,
264✔
564
                RestartInterval = 0,
264✔
565
                ColorTransformation = ColorTransformation
264✔
566
            });
264✔
567

568
        int bytesWritten = _scanEncoder.EncodeScan(source, _writer.GetRemainingDestination(), stride);
264✔
569

570
        // Synchronize the destination encapsulated in the writer (encode_scan works on a local copy)
571
        _writer.AdvancePosition(bytesWritten);
264✔
572
    }
264✔
573

574
    private void TransitionToTablesAndMiscellaneousState()
575
    {
576
        switch (_state)
252✔
577
        {
578
            case State.TablesAndMiscellaneous:
579
                return;
12✔
580
            case State.SpiffHeader:
581
                _writer.WriteSpiffEndOfDirectoryEntry();
6✔
582
                break;
6✔
583
            default:
584
                Debug.Assert(_state == State.DestinationSet);
585
                _writer.WriteStartOfImage();
234✔
586
                break;
587
        }
588

589
        if (EncodingOptions.HasFlag(EncodingOptions.IncludeVersionNumber))
240✔
590
        {
591
            string informationVersion = RemoveGitHash(Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion!);
2!
592

593
            byte[] utfBytes = Encoding.UTF8.GetBytes(informationVersion);
2✔
594

595
            var versionNumber = "charls-dotnet "u8.ToArray().Concat(utfBytes).ToArray();
2✔
596
            _writer.WriteCommentSegment(versionNumber);
2✔
597
        }
598

599
        _state = State.TablesAndMiscellaneous;
240✔
600
        return;
240✔
601

602
        static string RemoveGitHash(string version)
603
        {
604
            int plusIndex = version.IndexOf('+', StringComparison.InvariantCulture);
2✔
605
            return plusIndex != -1 ? version[..plusIndex] : version;
2!
606
        }
607
    }
608

609
    private void WriteColorTransformSegment()
610
    {
611
        if (ColorTransformation == ColorTransformation.None)
212✔
612
            return;
180✔
613

614
        ThrowHelper.ThrowArgumentExceptionIfFalse(ColorTransformations.IsPossible(FrameInfo), null, ErrorCode.InvalidArgumentColorTransformation);
32✔
615

616
        _writer.WriteColorTransformSegment(ColorTransformation);
26✔
617
    }
26✔
618

619
    private void WriteStartOfFrameSegment()
620
    {
621
        if (_writer.WriteStartOfFrameSegment(FrameInfo, _samplingFactors))
206✔
622
        {
623
            // Image dimensions are oversized and need to be written to a JPEG-LS preset parameters (LSE) segment.
624
            _writer.WriteJpegLSPresetParametersSegment(FrameInfo.Height, FrameInfo.Width);
4✔
625
        }
626
    }
206✔
627

628
    private void WriteJpegLSPresetParametersSegment(int maximumSampleValue, JpegLSPresetCodingParameters presetCodingParameters)
629
    {
630
        if (!_userPresetCodingParameters!.IsDefault(maximumSampleValue, NearLossless) ||
224✔
631
            (EncodingOptions.HasFlag(EncodingOptions.IncludePCParametersJai) && FrameInfo.BitsPerSample > 12))
224✔
632
        {
633
            // Write the actual used values to the stream, not zero's.
634
            // Explicit values reduces the risk for decoding by other implementations.
635
            _writer.WriteJpegLSPresetParametersSegment(presetCodingParameters);
26✔
636
        }
637
    }
224✔
638

639
    private void WriteEndOfImage()
640
    {
641
        _writer.WriteEndOfImage(EncodingOptions.HasFlag(EncodingOptions.EvenDestinationSize));
200✔
642
        _state = State.Completed;
200✔
643
    }
200✔
644

645
    private void CheckStateCanWrite()
646
    {
647
        ThrowHelper.ThrowInvalidOperationIfFalse(_state is >= State.DestinationSet and < State.Completed);
288✔
648
    }
278✔
649

650
    private void CheckInterleaveModeAgainstComponentCount(int componentCount)
651
    {
652
        if (InterleaveMode != InterleaveMode.None && componentCount is 1 or > Constants.MaximumComponentCountInScan)
238✔
653
            ThrowHelper.ThrowArgumentException(ErrorCode.InvalidArgumentInterleaveMode);
6✔
654
    }
232✔
655

656
    private int CheckStrideAndSourceLength(int sourceLength, int stride, int scanWidth, int scanHeight, int sourceComponentCount)
657
    {
658
        int minimumStride = CalculateMinimumStride(scanWidth, sourceComponentCount);
86✔
659

660
        if (stride == AutoCalculateStride)
86✔
661
        {
662
            stride = minimumStride;
78✔
663
        }
664
        else
665
        {
666
            if (stride < minimumStride)
8✔
667
                ThrowHelper.ThrowArgumentException(ErrorCode.InvalidArgumentStride);
4✔
668
        }
669

670
        int notUsedBytesAtEnd = stride - minimumStride;
82✔
671
        int minimumSourceLength = InterleaveMode == InterleaveMode.None
82!
672
            ? (stride * sourceComponentCount * scanHeight) - notUsedBytesAtEnd
82✔
673
            : (stride * scanHeight) - notUsedBytesAtEnd;
82✔
674

675
        if (sourceLength < minimumSourceLength)
82!
NEW
676
            ThrowHelper.ThrowArgumentException(ErrorCode.InvalidArgumentSize);
×
677

678
        return stride;
82✔
679
    }
680

681
    private int CheckStrideAndSourceLengthInterleaveModeNone(int sourceLength, int stride, int scanWidth, int scanHeight)
682
    {
683
        int minimumStride = scanWidth * BitToByteCount(FrameInfo.BitsPerSample);
186✔
684
        if (stride == AutoCalculateStride)
186✔
685
        {
686
            stride = minimumStride;
164✔
687
        }
688
        else
689
        {
690
            if (stride < minimumStride)
22✔
691
                ThrowHelper.ThrowArgumentException(ErrorCode.InvalidArgumentStride);
2✔
692
        }
693

694
        int notUsedBytesAtEnd = stride - minimumStride;
184✔
695
        int minimumSourceLength = (stride * scanHeight) - notUsedBytesAtEnd;
184✔
696

697
        if (sourceLength < minimumSourceLength)
184✔
698
            ThrowHelper.ThrowArgumentException(ErrorCode.InvalidArgumentSize);
2✔
699

700
        return stride;
182✔
701
    }
702

703
    private int CalculateMinimumStride(int scanWidth, int sourceComponentCount)
704
    {
705
        switch (_interleaveMode)
86!
706
        {
707
            case InterleaveMode.None:
NEW
708
                return FrameInfo.Width * BitToByteCount(FrameInfo.BitsPerSample);
×
709

710
            case InterleaveMode.Line:
711
                return FrameInfo.Width * BitToByteCount(FrameInfo.BitsPerSample) * sourceComponentCount;
36✔
712

713
            default:
714
                Debug.Assert(_interleaveMode == InterleaveMode.Sample);
715
                return scanWidth * BitToByteCount(FrameInfo.BitsPerSample) * sourceComponentCount;
50✔
716
        }
717
    }
718

719
    private void DetermineMaxSamplingFactors()
720
    {
721
        if (_samplingFactors == null)
212✔
722
            return;
208✔
723

724
        _horizontalSamplingMax = 1;
4✔
725
        _verticalSamplingMax = 1;
4✔
726
        for (int i = 0; i < FrameInfo.ComponentCount; ++i)
32✔
727
        {
728
            _horizontalSamplingMax = Math.Max(_horizontalSamplingMax, GetHorizontalSamplingFactor(i));
12✔
729
            _verticalSamplingMax = Math.Max(_verticalSamplingMax, GetVerticalSamplingFactor(i));
12✔
730
        }
731
    }
4✔
732

733
    private int GetScanWidth(int componentIndex)
734
    {
735
        if (_samplingFactors == null)
272✔
736
            return FrameInfo.Width;
262✔
737

738
        return FrameInfo.Width * GetHorizontalSamplingFactor(componentIndex) / _horizontalSamplingMax;
10✔
739
    }
740

741
    private int GetScanHeight(int componentIndex)
742
    {
743
        if (_samplingFactors == null)
272✔
744
            return FrameInfo.Height;
262✔
745

746
        return FrameInfo.Height * GetVerticalSamplingFactor(componentIndex) / _verticalSamplingMax;
10✔
747
    }
748

749
    private int GetHorizontalSamplingFactor(int componentIndex)
750
    {
751
        byte samplingFactor = _samplingFactors![componentIndex];
22✔
752
        if (samplingFactor == 0)
22!
NEW
UNCOV
753
            return 1;
×
754

755
        return samplingFactor >> 4;
22✔
756
    }
757

758
    private int GetVerticalSamplingFactor(int componentIndex)
759
    {
760
        byte samplingFactor = _samplingFactors![componentIndex];
22✔
761
        if (samplingFactor == 0)
22!
NEW
UNCOV
762
            return 1;
×
763

764
        return samplingFactor & 0xF;
22✔
765
    }
766

767
    private static ReadOnlySpan<byte> ToUtf8(string text)
768
    {
769
        if (string.IsNullOrEmpty(text))
16✔
770
            return default;
6✔
771

772
        var utf8Encoded = new byte[Encoding.UTF8.GetMaxByteCount(text.Length) + 1];
10✔
773
        int bytesWritten = Encoding.UTF8.GetBytes(text, 0, text.Length, utf8Encoded, 0);
10✔
774
        utf8Encoded[bytesWritten] = 0;
10✔
775

776
        return new ReadOnlySpan<byte>(utf8Encoded, 0, bytesWritten + 1);
10✔
777
    }
778
}
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