Skip to content

Commit 8e02f5c

Browse files
committed
Fix static analyzer warnings
- Remove dead field, async-without-await, GC.SuppressFinalize without finalizer, unused volatile compound op, off-by-one in mip selection - XML doc: missing params, ambiguous crefs, unescaped angle brackets, stale symbol names - field keyword, auto-property, local function, partial for CsWinRT1028 - Null guard for missing IHDR chunk in PNG parser
1 parent 0ec1915 commit 8e02f5c

20 files changed

Lines changed: 54 additions & 64 deletions

Src/FlyPhotos/Display/Animators/PngAnimator.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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 03 and 47) are overwritten in-place for each
198+
/// The width and height fields (bytes 03 and 47) 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

Src/FlyPhotos/Display/Controllers/CanvasController.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using FlyPhotos.Display.State;
1414
using FlyPhotos.Infra.Configuration;
1515
using FlyPhotos.Infra.Utils;
16-
using Microsoft.Graphics.Canvas;
1716
using Microsoft.Graphics.Canvas.Brushes;
1817
using Microsoft.Graphics.Canvas.UI.Xaml;
1918
using Microsoft.UI;
@@ -35,7 +34,7 @@ namespace FlyPhotos.Display.Controllers;
3534
///
3635
/// All public methods must be called on the UI (DispatcherQueue) thread.
3736
/// </summary>
38-
internal class CanvasController : ICanvasController
37+
internal partial class CanvasController : ICanvasController
3938
{
4039
public event Action<int> OnZoomChanged;
4140
public event Action<bool> OnFitToScreenStateChanged;
@@ -76,7 +75,6 @@ internal class CanvasController : ICanvasController
7675
private bool _isMultiPageActive;
7776

7877
// W2D-owned: set inside the ZoomOutOnExit action, read in WaitForPanZoomAnimationAsync.
79-
private bool _isGoingToExit;
8078

8179
// W2D-owned: true while ZoomAtPointPrecision ticks are arriving (right-click continuous zoom).
8280
// Treated as animating so Draw uses mip-based quality. Cleared 700 ms after the last tick.
@@ -367,7 +365,6 @@ public void ZoomOutOnExit(double exitAnimationDuration)
367365
var canvasSize = _d2dCanvas.GetSize();
368366
EnqueueW2dAction(() =>
369367
{
370-
_isGoingToExit = true;
371368
_canvasViewManager.ZoomOutOnExit(exitAnimationDuration, canvasSize);
372369
});
373370
}
@@ -555,12 +552,11 @@ private void D2dCanvas_SizeChanged(object sender, SizeChangedEventArgs args)
555552
public Task WaitForPanZoomAnimationAsync(int timeoutMs)
556553
{
557554
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
558-
Action handler = null;
559-
handler = () =>
555+
void handler()
560556
{
561557
_canvasViewManager.AnimationCompleted -= handler;
562558
tcs.TrySetResult();
563-
};
559+
}
564560
// Subscribe on the W2D thread so the add is ordered relative to animation ticks and the
565561
// permanent subscriber — no cross-thread race on the event delegate.
566562
EnqueueW2dAction(() => _canvasViewManager.AnimationCompleted += handler);

Src/FlyPhotos/Display/Controllers/CanvasViewManager.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,9 @@ private void StartZoomAnimation(float targetScale, Point zoomAnchor)
876876
/// Starts (or re-targets) a spring that drives scale and pan together (fit / 100% / step).
877877
/// Velocity carries forward across re-targets, as with <see cref="StartZoomAnimation"/>.
878878
/// </summary>
879+
/// <param name="targetScale">The scale the spring should settle at.</param>
880+
/// <param name="targetPosition">The image-centre position the spring should settle at.</param>
881+
/// <param name="targetCanvasSize">Canvas size used to compute the settled layout.</param>
879882
/// <param name="forceReseed">
880883
/// When true, the spring's internal scale AND pan state are re-seeded from the live view even if a
881884
/// spring is already running, and velocities are zeroed. Used by the launch open-zoom, which is a

Src/FlyPhotos/Display/Controllers/PhotoDisplayController.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using FlyPhotos.Display.ImageReading;
1414
using FlyPhotos.Display.State;
1515
using FlyPhotos.Infra.Configuration;
16-
using FlyPhotos.Infra.Utils;
1716
using FlyPhotos.Services;
1817
using Microsoft.Graphics.Canvas;
1918
using Microsoft.Graphics.Canvas.UI.Xaml;
@@ -29,7 +28,7 @@ internal partial class PhotoDisplayController
2928

3029
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
3130

32-
private sealed class PhotoCacheTier : IDisposable
31+
private sealed partial class PhotoCacheTier : IDisposable
3332
{
3433
public readonly ConcurrentStack<int> Queue = new();
3534
public readonly AutoResetEvent Signal = new(false);
@@ -503,7 +502,7 @@ private static async Task<RandomAccessStreamReference> GetCanvasBitmapAsAccessSt
503502

504503
public async Task Fly(NavDirection direction)
505504
{
506-
_keyPressCounter++;
505+
Interlocked.Increment(ref _keyPressCounter);
507506
var keys = _sortedPhotoKeys;
508507
if (keys.Count <= 1) return;
509508
int currentPosition = _photoSessionState.CurrentPhotoListPosition;

Src/FlyPhotos/Display/ImageReading/GifReader.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ internal static class GifReader
2525
{
2626
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
2727

28-
// Identity transform reused across calls avoids a COM object allocation per decode.
28+
// Identity transform reused across calls avoids a COM object allocation per decode.
2929
private static readonly BitmapTransform IdentityTransform = new();
3030

3131
/// <summary>
@@ -62,17 +62,17 @@ internal static class GifReader
6262
/// <returns>
6363
/// A tuple of (<c>success</c>, <see cref="HqDisplayItem" />):
6464
/// <list type="bullet">
65-
/// <item><see cref="StaticHqDisplayItem" /> for single-frame GIF files.</item>
65+
/// <item><see cref="StaticHqDisplayItem" /> for single-frame GIF files.</item>
6666
/// <item>
67-
/// <see cref="AnimatedHqDisplayItem" /> for multi-frame (animated) GIF files,
67+
/// <see cref="AnimatedHqDisplayItem" /> for multi-frame (animated) GIF files,
6868
/// carrying the first decoded frame and the raw file bytes for the animator.
6969
/// </item>
7070
/// </list>
7171
/// On failure, returns <c>(false, HqDisplayItem.Empty())</c> and logs the error.
7272
/// </returns>
7373
/// <remarks>
74-
/// Frame 0 pixels are decoded via <see cref="BitmapFrame.GetPixelDataAsync" /> on the
75-
/// same decoder instance used for the frame-count check no second decode pass needed.
74+
/// Frame 0 pixels are decoded via <see cref="BitmapFrame.GetPixelDataAsync()" /> on the
75+
/// same decoder instance used for the frame-count check no second decode pass needed.
7676
/// EXIF orientation is applied so the resulting bitmap is always correctly oriented.
7777
/// For animated files, the stream is rewound and re-read as a raw byte array for the
7878
/// animator; this is cheaper than a second full pixel decode.
@@ -85,7 +85,7 @@ internal static class GifReader
8585

8686
// One decoder, one stream read.
8787
// GetPixelDataAsync extracts frame 0 pixels directly from the decoder we already
88-
// have no second BitmapDecoder or second CanvasBitmap.LoadAsync needed.
88+
// have no second BitmapDecoder or second CanvasBitmap.LoadAsync needed.
8989
var decoder = await BitmapDecoder.CreateAsync(BitmapDecoder.GifDecoderId, stream);
9090
var frame0 = await decoder.GetFrameAsync(0);
9191
var pixelProvider = await frame0.GetPixelDataAsync(
@@ -105,7 +105,7 @@ internal static class GifReader
105105

106106
if (decoder.FrameCount > 1) // Animated GIF
107107
{
108-
// Seeking back to read the raw bytes is a simple memcpy far cheaper
108+
// Seeking back to read the raw bytes is a simple memcpy far cheaper
109109
// than a second pixel decode pass.
110110
stream.Seek(0);
111111
var bytes = await StorageOps.GetInMemByteArray(stream);

Src/FlyPhotos/Display/ImageReading/NativeAvifReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal static class NativeAvifReader
2424
/// By extracting the file into a Byte Array immediately, we ensure the file is unlocked from the filesystem for
2525
/// subsequent deletion.
2626
/// </summary>
27-
/// <param name="ctrl">The ICanvasResourceCreatorWithDpi surface context used for creating Win2D bitmaps.</param>
27+
/// <param name="canvas">The ICanvasResourceCreatorWithDpi surface context used for creating Win2D bitmaps.</param>
2828
/// <param name="inputPath">The absolute path to the .avif or .heic file.</param>
2929
/// <returns>A tuple of (success, HqDisplayItem).</returns>
3030
public static async Task<(bool, HqDisplayItem)> GetHq(ICanvasResourceCreatorWithDpi canvas, string inputPath)

Src/FlyPhotos/Display/ImageReading/WebpReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal static class WebpReader
7272
/// On failure, returns <c>(false, HqDisplayItem.Empty())</c> and logs the error.
7373
/// </returns>
7474
/// <remarks>
75-
/// Frame 0 pixels are decoded via <see cref="BitmapFrame.GetPixelDataAsync"/> on the
75+
/// Frame 0 pixels are decoded via <see cref="BitmapFrame.GetPixelDataAsync()"/> on the
7676
/// same decoder instance used for the frame-count check — no second decode pass needed.
7777
/// EXIF orientation is applied so the resulting bitmap is always correctly oriented.
7878
/// For animated files, the stream is rewound and re-read as a raw byte array for the

Src/FlyPhotos/Display/ImageRendering/StaticImageRenderer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ private void KickOffMipGeneration()
5555
{
5656
_mipGenCts = new CancellationTokenSource();
5757
var token = _mipGenCts.Token;
58-
_ = Task.Run(() => GenerateMipChainAsync(token), token);
58+
_ = Task.Run(() => GenerateMipChain(token), token);
5959
}
6060

61-
private async Task GenerateMipChainAsync(CancellationToken token)
61+
private void GenerateMipChain(CancellationToken token)
6262
{
6363
const int MaxLevels = 5;
6464
const float MinDimension = 64f;

Src/FlyPhotos/Infra/Interop/NativeRustBridge.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ internal static partial class NativeRustBridge
4141
/// <summary>
4242
/// Returns a heap-allocated array of null-terminated C strings listing every
4343
/// RAW file extension that rawler can decode (e.g. "ARW", "CR2", …).
44-
/// The number of entries is written to <paramref name="count"/>.
44+
/// The number of entries is written to <paramref name="size"/>.
4545
/// </summary>
4646
/// <remarks>
4747
/// The returned strings are UPPER-CASE and do NOT include a leading dot.
@@ -52,7 +52,7 @@ internal static partial class NativeRustBridge
5252
internal static partial nint rawler_get_supported_formats(out int size);
5353

5454
/// <summary>
55-
/// Frees a buffer previously returned by <see cref="get_supported_formats"/>.
55+
/// Frees a buffer previously returned by <see cref="rawler_get_supported_formats"/>.
5656
/// <paramref name="size"/> MUST be the same value that was written to the
5757
/// <c>size</c> out-param of <c>get_supported_formats</c>.
5858
/// </summary>

Src/FlyPhotos/Infra/Interop/Win32Methods.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ internal struct WINDOWPLACEMENT
291291
/// <see cref="System.Runtime.InteropServices.Marshal.Release"/> on this pointer after use.
292292
/// </param>
293293
/// <returns>
294-
/// An HRESULT. Pass to <see cref="System.Runtime.InteropServices.Marshal.ThrowExceptionForHR"/>
294+
/// An HRESULT. Pass to <see cref="System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(int)"/>
295295
/// to convert failures into managed exceptions.
296296
/// </returns>
297297
[LibraryImport("shcore.dll", StringMarshalling = StringMarshalling.Utf16)]

0 commit comments

Comments
 (0)