@@ -27,6 +27,9 @@ public sealed class JpegLSEncoder
2727 private JpegLSPresetCodingParameters ? _userPresetCodingParameters = new ( ) ;
2828 private State _state = State . Initial ;
2929 private int _encodedComponentCount ;
30+ private byte [ ] ? _samplingFactors ;
31+ private int _horizontalSamplingMax ;
32+ private int _verticalSamplingMax ;
3033
3134 /// <summary>
3235 /// Initializes a new instance of the <see cref="JpegLSEncoder"/> class.
@@ -247,6 +250,23 @@ public Memory<byte> Destination
247250
248251 private bool IsFrameInfoConfigured => FrameInfo . Height != 0 ;
249252
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 ) ;
262+ ThrowHelper . ThrowIfOutsideRange ( 0 , 4 , horizontalFactor ) ;
263+ ThrowHelper . ThrowIfOutsideRange ( 0 , 4 , verticalFactor ) ;
264+
265+ // Usage of sampling factors is rare: use lazy initialization.
266+ _samplingFactors ??= new byte [ Constants . MaximumComponentCount ] ;
267+ _samplingFactors [ componentIndex ] = ( byte ) ( ( horizontalFactor << 4 ) | verticalFactor ) ;
268+ }
269+
250270 /// <summary>
251271 /// Configures the mapping table ID the encoder should reference when encoding a component.
252272 /// The referenced mapping table can be included in the stream or provided in another JPEG-LS abbreviated format stream.
@@ -298,14 +318,14 @@ public void EncodeComponents(ReadOnlySpan<byte> source, int sourceComponentCount
298318 ThrowHelper . ThrowInvalidOperationIfFalse ( IsFrameInfoConfigured ) ;
299319 ThrowHelper . ThrowArgumentExceptionIfFalse ( sourceComponentCount <= FrameInfo . ComponentCount - _encodedComponentCount , nameof ( sourceComponentCount ) ) ;
300320 CheckInterleaveModeAgainstComponentCount ( sourceComponentCount ) ;
301- int scanStride = CheckStrideAndSourceLength ( source . Length , stride , sourceComponentCount ) ;
302321
303322 int maximumSampleValue = CalculateMaximumSampleValue ( FrameInfo . BitsPerSample ) ;
304323 if ( ! _userPresetCodingParameters ! . TryMakeExplicit ( maximumSampleValue , NearLossless , out var explicitCodingParameters ) )
305324 throw ThrowHelper . CreateArgumentException ( ErrorCode . InvalidArgumentPresetCodingParameters ) ;
306325
307326 if ( _encodedComponentCount == 0 )
308327 {
328+ DetermineMaxSamplingFactors ( ) ;
309329 TransitionToTablesAndMiscellaneousState ( ) ;
310330 WriteColorTransformSegment ( ) ;
311331 WriteStartOfFrameSegment ( ) ;
@@ -315,24 +335,28 @@ public void EncodeComponents(ReadOnlySpan<byte> source, int sourceComponentCount
315335
316336 if ( InterleaveMode == InterleaveMode . None )
317337 {
318- int byteCountComponent = scanStride * FrameInfo . Height ;
319338 for ( int component = 0 ; ; )
320339 {
340+ int scanWidth = GetScanWidth ( _encodedComponentCount + component ) ;
341+ int scanHeight = GetScanHeight ( _encodedComponentCount + component ) ;
342+ int scanStride = CheckStrideAndSourceLengthInterleaveModeNone ( source . Length , stride , scanWidth , scanHeight ) ;
321343 _writer . WriteStartOfScanSegment ( 1 , NearLossless , InterleaveMode ) ;
322- EncodeScan ( source , scanStride , 1 , explicitCodingParameters ) ;
344+ EncodeScan ( source , scanStride , scanWidth , scanHeight , 1 , explicitCodingParameters ) ;
323345
324346 ++ component ;
325347 if ( component == sourceComponentCount )
326348 break ;
327349
328350 // Synchronize the source stream (EncodeScan works on a local copy)
351+ int byteCountComponent = scanStride * scanHeight ;
329352 source = source [ byteCountComponent ..] ;
330353 }
331354 }
332355 else
333356 {
357+ int scanStride = CheckStrideAndSourceLength ( source . Length , stride , sourceComponentCount ) ;
334358 _writer . WriteStartOfScanSegment ( sourceComponentCount , NearLossless , InterleaveMode ) ;
335- EncodeScan ( source , scanStride , sourceComponentCount , explicitCodingParameters ) ;
359+ EncodeScan ( source , scanStride , FrameInfo . Width , FrameInfo . Height , sourceComponentCount , explicitCodingParameters ) ;
336360 }
337361
338362 _encodedComponentCount += sourceComponentCount ;
@@ -526,10 +550,10 @@ public static Memory<byte> Encode(
526550 return encoder . EncodedData ;
527551 }
528552
529- private void EncodeScan ( ReadOnlySpan < byte > source , int stride , int componentCount , JpegLSPresetCodingParameters codingParameters )
553+ private void EncodeScan ( ReadOnlySpan < byte > source , int stride , int scanWidth , int scanHeight , int componentCount , JpegLSPresetCodingParameters codingParameters )
530554 {
531555 _scanEncoder = new ScanEncoder (
532- new FrameInfo ( FrameInfo . Width , FrameInfo . Height , FrameInfo . BitsPerSample , componentCount ) ,
556+ new FrameInfo ( scanWidth , scanHeight , FrameInfo . BitsPerSample , componentCount ) ,
533557 codingParameters ,
534558 new CodingParameters
535559 {
@@ -592,7 +616,7 @@ private void WriteColorTransformSegment()
592616
593617 private void WriteStartOfFrameSegment ( )
594618 {
595- if ( _writer . WriteStartOfFrameSegment ( FrameInfo ) )
619+ if ( _writer . WriteStartOfFrameSegment ( FrameInfo , _samplingFactors ) )
596620 {
597621 // Image dimensions are oversized and need to be written to a JPEG-LS preset parameters (LSE) segment.
598622 _writer . WriteJpegLSPresetParametersSegment ( FrameInfo . Height , FrameInfo . Width ) ;
@@ -652,6 +676,28 @@ private int CheckStrideAndSourceLength(int sourceLength, int stride, int sourceC
652676 return stride ;
653677 }
654678
679+ private int CheckStrideAndSourceLengthInterleaveModeNone ( int sourceLength , int stride , int scanWidth , int scanHeight )
680+ {
681+ int minimumStride = scanWidth * BitToByteCount ( FrameInfo . BitsPerSample ) ;
682+ if ( stride == AutoCalculateStride )
683+ {
684+ stride = minimumStride ;
685+ }
686+ else
687+ {
688+ if ( stride < minimumStride )
689+ ThrowHelper . ThrowArgumentException ( ErrorCode . InvalidArgumentStride ) ;
690+ }
691+
692+ int notUsedBytesAtEnd = stride - minimumStride ;
693+ int minimumSourceLength = ( stride * scanHeight ) - notUsedBytesAtEnd ;
694+
695+ if ( sourceLength < minimumSourceLength )
696+ ThrowHelper . ThrowArgumentException ( ErrorCode . InvalidArgumentSize ) ;
697+
698+ return stride ;
699+ }
700+
655701 private int CalculateMinimumStride ( int sourceComponentCount )
656702 {
657703 int stride = FrameInfo . Width * BitToByteCount ( FrameInfo . BitsPerSample ) ;
@@ -661,6 +707,54 @@ private int CalculateMinimumStride(int sourceComponentCount)
661707 return stride * sourceComponentCount ;
662708 }
663709
710+ private void DetermineMaxSamplingFactors ( )
711+ {
712+ if ( _samplingFactors == null )
713+ return ;
714+
715+ _horizontalSamplingMax = 1 ;
716+ _verticalSamplingMax = 1 ;
717+ for ( int i = 0 ; i < FrameInfo . ComponentCount ; ++ i )
718+ {
719+ _horizontalSamplingMax = Math . Max ( _horizontalSamplingMax , GetHorizontalSamplingFactor ( i ) ) ;
720+ _verticalSamplingMax = Math . Max ( _verticalSamplingMax , GetVerticalSamplingFactor ( i ) ) ;
721+ }
722+ }
723+
724+ private int GetScanWidth ( int componentIndex )
725+ {
726+ if ( _samplingFactors == null )
727+ return FrameInfo . Width ;
728+
729+ return FrameInfo . Width * GetHorizontalSamplingFactor ( componentIndex ) / _horizontalSamplingMax ;
730+ }
731+
732+ private int GetScanHeight ( int componentIndex )
733+ {
734+ if ( _samplingFactors == null )
735+ return FrameInfo . Height ;
736+
737+ return FrameInfo . Height * GetVerticalSamplingFactor ( componentIndex ) / _verticalSamplingMax ;
738+ }
739+
740+ private int GetHorizontalSamplingFactor ( int componentIndex )
741+ {
742+ byte samplingFactor = _samplingFactors ! [ componentIndex ] ;
743+ if ( samplingFactor == 0 )
744+ return 1 ;
745+
746+ return samplingFactor >> 4 ;
747+ }
748+
749+ private int GetVerticalSamplingFactor ( int componentIndex )
750+ {
751+ byte samplingFactor = _samplingFactors ! [ componentIndex ] ;
752+ if ( samplingFactor == 0 )
753+ return 1 ;
754+
755+ return samplingFactor & 0xF ;
756+ }
757+
664758 private static ReadOnlySpan < byte > ToUtf8 ( string text )
665759 {
666760 if ( string . IsNullOrEmpty ( text ) )
0 commit comments