@@ -27,7 +27,7 @@ namespace FlyPhotos.Display.Animators;
2727/// <para>
2828/// <b>Why PNG re-assembly per frame?</b>
2929/// The Windows WIC PNG codec decodes complete PNG files, not raw chunk sequences.
30- /// APNG frame data is stored as sequences of raw IDAT/fdAT chunks — not standalone files.
30+ /// APNG frame data is stored as sequences of raw IDAT/fdAT chunks � not standalone files.
3131/// To decode each frame, a minimal valid PNG is assembled in memory from the frame's
3232/// chunks plus the shared global chunks (PLTE, tRNS, etc.) and the IHDR with updated
3333/// dimensions, then handed to <c>CanvasBitmap.LoadAsync</c> for decoding.
@@ -195,7 +195,7 @@ private class ApngFrameMetadata
195195
196196 /// <summary>
197197 /// Reusable 13-byte IHDR data buffer. IHDR is always exactly 13 bytes per the PNG spec.
198- /// The width and height fields (bytes 0– 3 and 4– 7) are overwritten in-place for each
198+ /// The width and height fields (bytes 0� 3 and 4� 7) are overwritten in-place for each
199199 /// frame with the current patch dimensions, avoiding a new array allocation per frame.
200200 /// </summary>
201201 private readonly byte [ ] _ihdrBuffer = new byte [ 13 ] ;
@@ -295,7 +295,7 @@ public static async Task<PngAnimator> CreateAsync(byte[] apngData, ICanvasResour
295295 {
296296 var parsedData = await Parser . ParseApngStreamAsync ( randomAccessStream ) ;
297297
298- // APNG spec (§ 4.4): delay = DelayNum / (DelayDen == 0 ? 100 : DelayDen) seconds.
298+ // APNG spec (� 4.4): delay = DelayNum / (DelayDen == 0 ? 100 : DelayDen) seconds.
299299 // DelayNum == 0 means zero delay, which browsers treat as 100 ms.
300300 var metadata = parsedData . FrameControls . Select ( fc => new ApngFrameMetadata
301301 {
@@ -361,7 +361,7 @@ public async Task UpdateAsync(TimeSpan totalElapsedTime)
361361 {
362362 // Loop wrap-around: instead of clearing _compositedSurface here, we set the previous-frame
363363 // disposal state to cover the full canvas. RenderFrameAsync(0) will then apply the clear
364- // atomically inside its drawing session — immediately before drawing frame 0's patch —
364+ // atomically inside its drawing session � immediately before drawing frame 0's patch �
365365 // eliminating the async gap between clear and draw that caused a visible flash on loop restart.
366366
367367 _currentFrameIndex = - 1 ;
@@ -389,7 +389,7 @@ private async Task RenderFrameAsync(int frameIndex)
389389 var metadata = _frameMetadata [ frameIndex ] ;
390390
391391 // Reconstruct a minimal valid PNG from the frame's raw chunk data and decode it.
392- // CanvasBitmap.LoadAsync allocates a new GPU texture per frame — unavoidable without
392+ // CanvasBitmap.LoadAsync allocates a new GPU texture per frame � unavoidable without
393393 // a decoded frame cache. _reusableStream and _reusableWriter avoid the per-frame
394394 // stream and writer allocations that would otherwise occur here.
395395 using var patchBitmap = await Parser . ReconstructAndLoadCanvasBitmapAsync (
@@ -398,7 +398,7 @@ private async Task RenderFrameAsync(int frameIndex)
398398 _canvas . Device ) ;
399399
400400 // If this frame specifies dispose-to-previous, snapshot the full compositor surface
401- // now — before we draw — so it can be restored on the next iteration's disposal step.
401+ // now � before we draw � so it can be restored on the next iteration's disposal step.
402402 if ( metadata . DisposeOp == APNG_DISPOSE_OP_PREVIOUS )
403403 {
404404 using var backupDs = _previousFrameBackup . CreateDrawingSession ( ) ;
@@ -407,7 +407,7 @@ private async Task RenderFrameAsync(int frameIndex)
407407
408408 using ( var ds = _compositedSurface . CreateDrawingSession ( ) )
409409 {
410- // Step 1 — Apply the PREVIOUS frame's dispose operation.
410+ // Step 1 � Apply the PREVIOUS frame's dispose operation.
411411 if ( _previousFrameDisposal == APNG_DISPOSE_OP_BACKGROUND )
412412 {
413413 // Clear the previous frame's region to transparent.
@@ -432,7 +432,7 @@ private async Task RenderFrameAsync(int frameIndex)
432432 CanvasComposite . Copy ) ;
433433 }
434434
435- // Step 2 — Draw the current frame patch using its specified blend operation.
435+ // Step 2 � Draw the current frame patch using its specified blend operation.
436436 var patchSourceRect = new Rect ( 0 , 0 , patchBitmap . SizeInPixels . Width , patchBitmap . SizeInPixels . Height ) ;
437437
438438 if ( metadata . BlendOp == APNG_BLEND_OP_SOURCE )
@@ -549,7 +549,7 @@ public class ApngData
549549 public bool IsDefaultImageFirstFrame ;
550550 }
551551
552- /// <summary>Standard 8-byte PNG file signature, per PNG spec § 5.2.</summary>
552+ /// <summary>Standard 8-byte PNG file signature, per PNG spec � 5.2.</summary>
553553 private static readonly byte [ ] PngSig = [ 0x89 , 0x50 , 0x4E , 0x47 , 0x0D , 0x0A , 0x1A , 0x0A ] ;
554554
555555 /// <summary>
@@ -596,10 +596,12 @@ public static async Task<ApngData> ParseApngStreamAsync(IRandomAccessStream stre
596596 else if ( c . Type != "IEND" && c . Type != "acTL" )
597597 {
598598 // Collect global ancillary chunks (PLTE, tRNS, gAMA, cHRM, etc.).
599- // acTL is intentionally skipped — it merely signals "this is an APNG".
599+ // acTL is intentionally skipped � it merely signals "this is an APNG".
600600 global . Add ( c ) ;
601601 }
602602
603+ if ( ihdr == null ) throw new ArgumentException ( "PNG missing IHDR chunk" ) ;
604+
603605 var ordered = fcs . OrderBy ( f => f . SequenceNumber ) . ToList ( ) ;
604606 bool defaultIsFirst = false ;
605607
@@ -672,7 +674,7 @@ private static List<PngChunk> ReadAllChunks(BinaryReader r)
672674 var len = ReadU32BE ( r ) ;
673675 var type = Encoding . ASCII . GetString ( r . ReadBytes ( 4 ) ) ;
674676 var data = r . ReadBytes ( ( int ) len ) ;
675- r . ReadBytes ( 4 ) ; // CRC — verified by the PNG decoder; skipped here for speed.
677+ r . ReadBytes ( 4 ) ; // CRC � verified by the PNG decoder; skipped here for speed.
676678 res . Add ( new PngChunk { Type = type , Data = data } ) ;
677679 if ( type == "IEND" ) break ;
678680 }
@@ -713,14 +715,14 @@ private static PngChunk ConvertFdat(PngChunk f)
713715 /// <summary>
714716 /// Writes a complete PNG chunk to <paramref name="w" />:
715717 /// 4-byte big-endian length, 4-byte type, data bytes, 4-byte CRC32.
716- /// CRC covers the type and data fields, per PNG spec § 5.3.
718+ /// CRC covers the type and data fields, per PNG spec � 5.3.
717719 /// <para>
718720 /// <paramref name="crcBuf" /> is a caller-owned scratch buffer grown in place
719721 /// via <see cref="Array.Resize{T}" /> only when the chunk (4 type bytes + data)
720722 /// exceeds its current capacity. It is never shrunk, so it converges to the size
721723 /// of the largest chunk seen and causes no further allocations at steady state.
722724 /// The CRC is computed over a <see cref="ReadOnlySpan{T}" /> slice of
723- /// <paramref name="crcBuf" /> so only the relevant bytes are hashed — no trimmed
725+ /// <paramref name="crcBuf" /> so only the relevant bytes are hashed � no trimmed
724726 /// array copy is allocated regardless of whether the buffer is larger than needed.
725727 /// </para>
726728 /// </summary>
@@ -731,7 +733,7 @@ private static void WriteChunk(BinaryWriter w, string t, byte[] d, ref byte[] cr
731733 if ( crcBuf . Length < needed )
732734 Array . Resize ( ref crcBuf , needed ) ;
733735
734- // Encode the 4-byte chunk type directly into the scratch buffer — no temp array.
736+ // Encode the 4-byte chunk type directly into the scratch buffer � no temp array.
735737 Encoding . ASCII . GetBytes ( t , 0 , 4 , crcBuf , 0 ) ;
736738 if ( d . Length > 0 ) Buffer . BlockCopy ( d , 0 , crcBuf , 4 , d . Length ) ;
737739
0 commit comments