22// Licensed under the Apache License, Version 2.0.
33
44// ReSharper disable InconsistentNaming
5+ using System ;
6+ using System . Buffers . Binary ;
57using System . IO ;
68using System . Linq ;
79
@@ -18,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
1820{
1921 public class PngEncoderTests
2022 {
23+ private static PngEncoder PngEncoder => new PngEncoder ( ) ;
24+
2125 public static readonly TheoryData < string , PngBitDepth > PngBitDepthFiles =
2226 new TheoryData < string , PngBitDepth >
2327 {
@@ -234,8 +238,7 @@ public void InfersColorTypeAndBitDepth<TPixel>(TestImageProvider<TPixel> provide
234238 {
235239 using ( Stream stream = new MemoryStream ( ) )
236240 {
237- var encoder = new PngEncoder ( ) ;
238- encoder . Encode ( provider . GetImage ( ) , stream ) ;
241+ PngEncoder . Encode ( provider . GetImage ( ) , stream ) ;
239242
240243 stream . Seek ( 0 , SeekOrigin . Begin ) ;
241244
@@ -281,7 +284,7 @@ public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider)
281284 using ( Image < TPixel > image = provider . GetImage ( ) )
282285 using ( var ms = new MemoryStream ( ) )
283286 {
284- image . Save ( ms , new PngEncoder ( ) ) ;
287+ image . Save ( ms , PngEncoder ) ;
285288
286289 byte [ ] data = ms . ToArray ( ) . Take ( 8 ) . ToArray ( ) ;
287290 byte [ ] expected =
@@ -304,14 +307,12 @@ public void WritesFileMarker<TPixel>(TestImageProvider<TPixel> provider)
304307 [ MemberData ( nameof ( RatioFiles ) ) ]
305308 public void Encode_PreserveRatio ( string imagePath , int xResolution , int yResolution , PixelResolutionUnit resolutionUnit )
306309 {
307- var options = new PngEncoder ( ) ;
308-
309310 var testFile = TestFile . Create ( imagePath ) ;
310311 using ( Image < Rgba32 > input = testFile . CreateRgba32Image ( ) )
311312 {
312313 using ( var memStream = new MemoryStream ( ) )
313314 {
314- input . Save ( memStream , options ) ;
315+ input . Save ( memStream , PngEncoder ) ;
315316
316317 memStream . Position = 0 ;
317318 using ( var output = Image . Load < Rgba32 > ( memStream ) )
@@ -329,14 +330,12 @@ public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolut
329330 [ MemberData ( nameof ( PngBitDepthFiles ) ) ]
330331 public void Encode_PreserveBits ( string imagePath , PngBitDepth pngBitDepth )
331332 {
332- var options = new PngEncoder ( ) ;
333-
334333 var testFile = TestFile . Create ( imagePath ) ;
335334 using ( Image < Rgba32 > input = testFile . CreateRgba32Image ( ) )
336335 {
337336 using ( var memStream = new MemoryStream ( ) )
338337 {
339- input . Save ( memStream , options ) ;
338+ input . Save ( memStream , PngEncoder ) ;
340339
341340 memStream . Position = 0 ;
342341 using ( var output = Image . Load < Rgba32 > ( memStream ) )
@@ -353,8 +352,6 @@ public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
353352 [ MemberData ( nameof ( PngTrnsFiles ) ) ]
354353 public void Encode_PreserveTrns ( string imagePath , PngBitDepth pngBitDepth , PngColorType pngColorType )
355354 {
356- var options = new PngEncoder ( ) ;
357-
358355 var testFile = TestFile . Create ( imagePath ) ;
359356 using ( Image < Rgba32 > input = testFile . CreateRgba32Image ( ) )
360357 {
@@ -363,7 +360,7 @@ public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngCo
363360
364361 using ( var memStream = new MemoryStream ( ) )
365362 {
366- input . Save ( memStream , options ) ;
363+ input . Save ( memStream , PngEncoder ) ;
367364 memStream . Position = 0 ;
368365 using ( var output = Image . Load < Rgba32 > ( memStream ) )
369366 {
@@ -404,6 +401,126 @@ public void Encode_PreserveTrns(string imagePath, PngBitDepth pngBitDepth, PngCo
404401 }
405402 }
406403
404+ [ Fact ]
405+ public void HeaderChunk_ComesFirst ( )
406+ {
407+ var testFile = TestFile . Create ( TestImages . Png . PngWithMetadata ) ;
408+ using Image < Rgba32 > input = testFile . CreateRgba32Image ( ) ;
409+ using var memStream = new MemoryStream ( ) ;
410+ input . Save ( memStream , PngEncoder ) ;
411+ memStream . Position = 0 ;
412+
413+ // Skip header.
414+ Span < byte > bytesSpan = memStream . ToArray ( ) . AsSpan ( 8 ) ;
415+ BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 0 , 4 ) ) ;
416+ var type = ( PngChunkType ) BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 4 , 4 ) ) ;
417+ Assert . Equal ( PngChunkType . Header , type ) ;
418+ }
419+
420+ [ Fact ]
421+ public void EndChunk_IsLast ( )
422+ {
423+ var testFile = TestFile . Create ( TestImages . Png . PngWithMetadata ) ;
424+ using Image < Rgba32 > input = testFile . CreateRgba32Image ( ) ;
425+ using var memStream = new MemoryStream ( ) ;
426+ input . Save ( memStream , PngEncoder ) ;
427+ memStream . Position = 0 ;
428+
429+ // Skip header.
430+ Span < byte > bytesSpan = memStream . ToArray ( ) . AsSpan ( 8 ) ;
431+
432+ bool endChunkFound = false ;
433+ while ( bytesSpan . Length > 0 )
434+ {
435+ int length = BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 0 , 4 ) ) ;
436+ var type = ( PngChunkType ) BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 4 , 4 ) ) ;
437+ Assert . False ( endChunkFound ) ;
438+ if ( type == PngChunkType . End )
439+ {
440+ endChunkFound = true ;
441+ }
442+
443+ bytesSpan = bytesSpan . Slice ( 4 + 4 + length + 4 ) ;
444+ }
445+ }
446+
447+ [ Theory ]
448+ [ InlineData ( PngChunkType . Gamma ) ]
449+ [ InlineData ( PngChunkType . Chroma ) ]
450+ [ InlineData ( PngChunkType . EmbeddedColorProfile ) ]
451+ [ InlineData ( PngChunkType . SignificantBits ) ]
452+ [ InlineData ( PngChunkType . StandardRgbColourSpace ) ]
453+ public void Chunk_ComesBeforePlteAndIDat ( object chunkTypeObj )
454+ {
455+ var chunkType = ( PngChunkType ) chunkTypeObj ;
456+ var testFile = TestFile . Create ( TestImages . Png . PngWithMetadata ) ;
457+ using Image < Rgba32 > input = testFile . CreateRgba32Image ( ) ;
458+ using var memStream = new MemoryStream ( ) ;
459+ input . Save ( memStream , PngEncoder ) ;
460+ memStream . Position = 0 ;
461+
462+ // Skip header.
463+ Span < byte > bytesSpan = memStream . ToArray ( ) . AsSpan ( 8 ) ;
464+
465+ bool palFound = false ;
466+ bool dataFound = false ;
467+ while ( bytesSpan . Length > 0 )
468+ {
469+ int length = BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 0 , 4 ) ) ;
470+ var type = ( PngChunkType ) BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 4 , 4 ) ) ;
471+ if ( chunkType == type )
472+ {
473+ Assert . False ( palFound || dataFound , $ "{ chunkType } chunk should come before data and palette chunk") ;
474+ }
475+
476+ switch ( type )
477+ {
478+ case PngChunkType . Data :
479+ dataFound = true ;
480+ break ;
481+ case PngChunkType . Palette :
482+ palFound = true ;
483+ break ;
484+ }
485+
486+ bytesSpan = bytesSpan . Slice ( 4 + 4 + length + 4 ) ;
487+ }
488+ }
489+
490+ [ Theory ]
491+ [ InlineData ( PngChunkType . Physical ) ]
492+ [ InlineData ( PngChunkType . SuggestedPalette ) ]
493+ public void Chunk_ComesBeforeIDat ( object chunkTypeObj )
494+ {
495+ var chunkType = ( PngChunkType ) chunkTypeObj ;
496+ var testFile = TestFile . Create ( TestImages . Png . PngWithMetadata ) ;
497+ using Image < Rgba32 > input = testFile . CreateRgba32Image ( ) ;
498+ using var memStream = new MemoryStream ( ) ;
499+ input . Save ( memStream , PngEncoder ) ;
500+ memStream . Position = 0 ;
501+
502+ // Skip header.
503+ Span < byte > bytesSpan = memStream . ToArray ( ) . AsSpan ( 8 ) ;
504+
505+ bool dataFound = false ;
506+ while ( bytesSpan . Length > 0 )
507+ {
508+ int length = BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 0 , 4 ) ) ;
509+ var type = ( PngChunkType ) BinaryPrimitives . ReadInt32BigEndian ( bytesSpan . Slice ( 4 , 4 ) ) ;
510+ if ( chunkType == type )
511+ {
512+ Assert . False ( dataFound , $ "{ chunkType } chunk should come before data chunk") ;
513+ }
514+
515+ if ( type == PngChunkType . Data )
516+ {
517+ dataFound = true ;
518+ }
519+
520+ bytesSpan = bytesSpan . Slice ( 4 + 4 + length + 4 ) ;
521+ }
522+ }
523+
407524 [ Theory ]
408525 [ WithTestPatternImages ( 587 , 821 , PixelTypes . Rgba32 ) ]
409526 [ WithTestPatternImages ( 677 , 683 , PixelTypes . Rgba32 ) ]
0 commit comments